summaryrefslogtreecommitdiffstats
path: root/src/network/access/http2/http2protocol.cpp
diff options
context:
space:
mode:
authorTimur Pocheptsov <timur.pocheptsov@qt.io>2017-10-18 11:59:21 +0200
committerTimur Pocheptsov <timur.pocheptsov@qt.io>2017-10-26 04:13:40 +0000
commit76a6b3294223f52568cd8c6190edceedbdca70ce (patch)
tree4455b5b12b72c27238272903c47cf4da275e8dd7 /src/network/access/http2/http2protocol.cpp
parentf174d31667dca184439f520b9624a1471d9556a6 (diff)
HTTP/2 - make protocol settings configurablev5.10.0-beta3
1. Recently we have updated our receive window size to a larger value. Unfortunately, this also results in auto-test pumping through more data, which probably takes more time on CI. At the moment we do not have any public API on QNAM's level to customize HTTP/2 parameters (aka 5.10/FF and so on). So we use the fact that QNAM is QObject and we can set a property on it. This property is our Http2::ProtocolParameters object that allows us to configure: - HPACK parameters (in 5.10 - noop) - session receive window size - different SETTINGS as described by RFC 7540, 6.5.2. 2. Undocumented environment variable to set ENABLE_PUSH is not needed anymore. 3. In 5.11 Http2::ProtocolParameter will become a public API and we'll introduce a new setter in QNAM. Change-Id: If08fd5e09e7c0b61cf9700b426b60b5837b6b2e6 Reviewed-by: MÃ¥rten Nordheim <marten.nordheim@qt.io> Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Diffstat (limited to 'src/network/access/http2/http2protocol.cpp')
-rw-r--r--src/network/access/http2/http2protocol.cpp129
1 files changed, 100 insertions, 29 deletions
diff --git a/src/network/access/http2/http2protocol.cpp b/src/network/access/http2/http2protocol.cpp
index bb3d6bf575..f51af4be5c 100644
--- a/src/network/access/http2/http2protocol.cpp
+++ b/src/network/access/http2/http2protocol.cpp
@@ -62,9 +62,88 @@ const char Http2clientPreface[clientPrefaceLength] =
0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a,
0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a};
-QByteArray default_SETTINGS_to_Base64()
+// TODO: (in 5.11) - remove it!
+const char *http2ParametersPropertyName = "QT_HTTP2_PARAMETERS_PROPERTY";
+
+ProtocolParameters::ProtocolParameters()
+{
+ settingsFrameData[Settings::INITIAL_WINDOW_SIZE_ID] = qtDefaultStreamReceiveWindowSize;
+ settingsFrameData[Settings::ENABLE_PUSH_ID] = 0;
+}
+
+bool ProtocolParameters::validate() const
+{
+ // 0. Huffman/indexing: any values are valid and allowed.
+
+ // 1. Session receive window size (client side): HTTP/2 starts from the
+ // default value of 64Kb, if a client code tries to set lesser value,
+ // the delta would become negative, but this is not allowed.
+ if (maxSessionReceiveWindowSize < qint32(defaultSessionWindowSize)) {
+ qCWarning(QT_HTTP2, "Session receive window must be at least 65535 bytes");
+ return false;
+ }
+
+ // 2. HEADER_TABLE_SIZE: we do not validate HEADER_TABLE_SIZE, considering
+ // all values as valid. RFC 7540 and 7541 do not provide any lower/upper
+ // limits. If it's 0 - we do not index anything, if it's too huge - a user
+ // who provided such a value can potentially have a huge memory footprint,
+ // up to them to decide.
+
+ // 3. SETTINGS_ENABLE_PUSH: RFC 7540, 6.5.2, a value other than 0 or 1 will
+ // be treated by our peer as a PROTOCOL_ERROR.
+ if (settingsFrameData.contains(Settings::ENABLE_PUSH_ID)
+ && settingsFrameData[Settings::ENABLE_PUSH_ID] > 1) {
+ qCWarning(QT_HTTP2, "SETTINGS_ENABLE_PUSH can be only 0 or 1");
+ return false;
+ }
+
+ // 4. SETTINGS_MAX_CONCURRENT_STREAMS : RFC 7540 recommends 100 as the lower
+ // limit, says nothing about the upper limit. The RFC allows 0, but this makes
+ // no sense to us at all: there is no way a user can change this later and
+ // we'll not be able to get any responses on such a connection.
+ if (settingsFrameData.contains(Settings::MAX_CONCURRENT_STREAMS_ID)
+ && !settingsFrameData[Settings::MAX_CONCURRENT_STREAMS_ID]) {
+ qCWarning(QT_HTTP2, "MAX_CONCURRENT_STREAMS must be a positive number");
+ return false;
+ }
+
+ // 5. SETTINGS_INITIAL_WINDOW_SIZE.
+ if (settingsFrameData.contains(Settings::INITIAL_WINDOW_SIZE_ID)) {
+ const quint32 value = settingsFrameData[Settings::INITIAL_WINDOW_SIZE_ID];
+ // RFC 7540, 6.5.2 (the upper limit). The lower limit is our own - we send
+ // SETTINGS frame only once and will not be able to change this 0, thus
+ // we'll suspend all streams.
+ if (!value || value > quint32(maxSessionReceiveWindowSize)) {
+ qCWarning(QT_HTTP2, "INITIAL_WINDOW_SIZE must be in the range "
+ "(0, 2^31-1]");
+ return false;
+ }
+ }
+
+ // 6. SETTINGS_MAX_FRAME_SIZE: RFC 7540, 6.5.2, a value outside of the range
+ // [2^14-1, 2^24-1] will be treated by our peer as a PROTOCOL_ERROR.
+ if (settingsFrameData.contains(Settings::MAX_FRAME_SIZE_ID)) {
+ const quint32 value = settingsFrameData[Settings::INITIAL_WINDOW_SIZE_ID];
+ if (value < maxFrameSize || value > maxPayloadSize) {
+ qCWarning(QT_HTTP2, "MAX_FRAME_SIZE must be in the range [2^14, 2^24-1]");
+ return false;
+ }
+ }
+
+ // For SETTINGS_MAX_HEADER_LIST_SIZE RFC 7540 does not provide any specific
+ // numbers. It's clear, if a value is too small, no header can ever be sent
+ // by our peer at all. The default value is unlimited and we normally do not
+ // change this.
+ //
+ // Note: the size is calculated as the length of uncompressed (no HPACK)
+ // name + value + 32 bytes.
+
+ return true;
+}
+
+QByteArray ProtocolParameters::settingsFrameToBase64() const
{
- Frame frame(default_SETTINGS_frame());
+ Frame frame(settingsFrame());
// SETTINGS frame's payload consists of pairs:
// 2-byte-identifier | 4-byte-value == multiple of 6.
Q_ASSERT(frame.payloadSize() && !(frame.payloadSize() % 6));
@@ -78,21 +157,35 @@ QByteArray default_SETTINGS_to_Base64()
return wrapper.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
}
-void prepare_for_protocol_upgrade(QHttpNetworkRequest &request)
+Frame ProtocolParameters::settingsFrame() const
+{
+ // 6.5 SETTINGS
+ FrameWriter builder(FrameType::SETTINGS, FrameFlag::EMPTY, connectionStreamID);
+ for (auto it = settingsFrameData.cbegin(), end = settingsFrameData.cend();
+ it != end; ++it) {
+ builder.append(it.key());
+ builder.append(it.value());
+ }
+
+ return builder.outboundFrame();
+}
+
+void ProtocolParameters::addProtocolUpgradeHeaders(QHttpNetworkRequest *request) const
{
+ Q_ASSERT(request);
// RFC 2616, 14.10
// RFC 7540, 3.2
- QByteArray value(request.headerField("Connection"));
+ QByteArray value(request->headerField("Connection"));
// We _append_ 'Upgrade':
if (value.size())
value += ", ";
value += "Upgrade, HTTP2-Settings";
- request.setHeaderField("Connection", value);
+ request->setHeaderField("Connection", value);
// This we just (re)write.
- request.setHeaderField("Upgrade", "h2c");
+ request->setHeaderField("Upgrade", "h2c");
// This we just (re)write.
- request.setHeaderField("HTTP2-Settings", default_SETTINGS_to_Base64());
+ request->setHeaderField("HTTP2-Settings", settingsFrameToBase64());
}
void qt_error(quint32 errorCode, QNetworkReply::NetworkError &error,
@@ -188,13 +281,6 @@ QNetworkReply::NetworkError qt_error(quint32 errorCode)
return error;
}
-bool is_PUSH_PROMISE_enabled()
-{
- bool ok = false;
- const int env = qEnvironmentVariableIntValue("QT_HTTP2_ENABLE_PUSH_PROMISE", &ok);
- return ok && env;
-}
-
bool is_protocol_upgraded(const QHttpNetworkReply &reply)
{
if (reply.statusCode() == 101) {
@@ -209,21 +295,6 @@ bool is_protocol_upgraded(const QHttpNetworkReply &reply)
return false;
}
-Frame default_SETTINGS_frame()
-{
- // 6.5 SETTINGS
- FrameWriter builder(FrameType::SETTINGS, FrameFlag::EMPTY, connectionStreamID);
- // MAX frame size (16 kb), disable/enable PUSH_PROMISE
- builder.append(Settings::MAX_FRAME_SIZE_ID);
- builder.append(quint32(maxFrameSize));
- builder.append(Settings::INITIAL_WINDOW_SIZE_ID);
- builder.append(initialStreamReceiveWindowSize);
- builder.append(Settings::ENABLE_PUSH_ID);
- builder.append(quint32(is_PUSH_PROMISE_enabled()));
-
- return builder.outboundFrame();
-}
-
} // namespace Http2
QT_END_NAMESPACE