summaryrefslogtreecommitdiffstats
path: root/src/network/access/http2
diff options
context:
space:
mode:
authorTimur Pocheptsov <timur.pocheptsov@qt.io>2019-08-08 16:12:46 +0200
committerTimur Pocheptsov <timur.pocheptsov@qt.io>2019-08-20 07:19:43 +0200
commit8052755fd7581d70802f651d88b7af8447432d75 (patch)
tree078faf26de1608c7b2cdf1f1b36807d8f3f9f63b /src/network/access/http2
parentdcdfb6908db0f83cbc4e550859f56ee58a6b3420 (diff)
Add means to configure HTTP/2 protocol handler
Similar to TLS configuration that we can use on QNetworkRequest, we can configure different options in our HTTP/2 handling by providing QNetworkAccessManager with h2 configuration. Previously, it was only possible internally in our auto-test - a hack with QObject's properties and a private class. Now it's time to provide a public API for this. [ChangeLog][QtNetwork][QNetworkRequest] Add an ability to configure HTTP/2 protocol Change-Id: I80266a74f6dcdfabb7fc05ed1dce17897bcda886 Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
Diffstat (limited to 'src/network/access/http2')
-rw-r--r--src/network/access/http2/http2frames.cpp4
-rw-r--r--src/network/access/http2/http2protocol.cpp114
-rw-r--r--src/network/access/http2/http2protocol_p.h64
3 files changed, 39 insertions, 143 deletions
diff --git a/src/network/access/http2/http2frames.cpp b/src/network/access/http2/http2frames.cpp
index e695b4dd9e..ce33505683 100644
--- a/src/network/access/http2/http2frames.cpp
+++ b/src/network/access/http2/http2frames.cpp
@@ -305,7 +305,7 @@ FrameStatus FrameReader::read(QAbstractSocket &socket)
return status;
}
- if (Http2PredefinedParameters::maxFrameSize < frame.payloadSize())
+ if (Http2PredefinedParameters::maxPayloadSize < frame.payloadSize())
return FrameStatus::sizeError;
frame.buffer.resize(frame.payloadSize() + frameHeaderSize);
@@ -388,7 +388,7 @@ void FrameWriter::setPayloadSize(quint32 size)
auto &buffer = frame.buffer;
Q_ASSERT(buffer.size() >= frameHeaderSize);
- Q_ASSERT(size < maxPayloadSize);
+ Q_ASSERT(size <= maxPayloadSize);
buffer[0] = size >> 16;
buffer[1] = size >> 8;
diff --git a/src/network/access/http2/http2protocol.cpp b/src/network/access/http2/http2protocol.cpp
index 0be72042c6..0290f9ac00 100644
--- a/src/network/access/http2/http2protocol.cpp
+++ b/src/network/access/http2/http2protocol.cpp
@@ -43,6 +43,8 @@
#include "private/qhttpnetworkrequest_p.h"
#include "private/qhttpnetworkreply_p.h"
+#include <access/qhttp2configuration.h>
+
#include <QtCore/qbytearray.h>
#include <QtCore/qstring.h>
@@ -62,88 +64,25 @@ const char Http2clientPreface[clientPrefaceLength] =
0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a,
0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a};
-// 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
+Frame configurationToSettingsFrame(const QHttp2Configuration &config)
{
- // 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;
+ // 6.5 SETTINGS
+ FrameWriter builder(FrameType::SETTINGS, FrameFlag::EMPTY, connectionStreamID);
+ // Server push:
+ builder.append(Settings::ENABLE_PUSH_ID);
+ builder.append(int(config.serverPushEnabled()));
+ // Stream receive window size:
+ builder.append(Settings::INITIAL_WINDOW_SIZE_ID);
+ builder.append(config.streamReceiveWindowSize());
+
+ // TODO: Max frame size; in future, if the need
+ // is proven, we can also set decoding table size
+ // and header list size. For now, defaults suffice.
+ return builder.outboundFrame();
}
-QByteArray ProtocolParameters::settingsFrameToBase64() const
+QByteArray settingsFrameToBase64(const Frame &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));
@@ -157,20 +96,7 @@ QByteArray ProtocolParameters::settingsFrameToBase64() const
return wrapper.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
}
-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
+void appendProtocolUpgradeHeaders(const QHttp2Configuration &config, QHttpNetworkRequest *request)
{
Q_ASSERT(request);
// RFC 2616, 14.10
@@ -184,8 +110,10 @@ void ProtocolParameters::addProtocolUpgradeHeaders(QHttpNetworkRequest *request)
request->setHeaderField("Connection", value);
// This we just (re)write.
request->setHeaderField("Upgrade", "h2c");
+
+ const Frame frame(configurationToSettingsFrame(config));
// This we just (re)write.
- request->setHeaderField("HTTP2-Settings", settingsFrameToBase64());
+ request->setHeaderField("HTTP2-Settings", settingsFrameToBase64(frame));
}
void qt_error(quint32 errorCode, QNetworkReply::NetworkError &error,
diff --git a/src/network/access/http2/http2protocol_p.h b/src/network/access/http2/http2protocol_p.h
index 7142d6f1fa..b0af5aa919 100644
--- a/src/network/access/http2/http2protocol_p.h
+++ b/src/network/access/http2/http2protocol_p.h
@@ -55,12 +55,14 @@
#include <QtCore/qloggingcategory.h>
#include <QtCore/qmetatype.h>
#include <QtCore/qglobal.h>
+#include <QtCore/qmap.h>
// Different HTTP/2 constants/values as defined by RFC 7540.
QT_BEGIN_NAMESPACE
class QHttpNetworkRequest;
+class QHttp2Configuration;
class QHttpNetworkReply;
class QByteArray;
class QString;
@@ -118,13 +120,19 @@ enum Http2PredefinedParameters
connectionStreamID = 0, // HTTP/2, 5.1.1
frameHeaderSize = 9, // HTTP/2, 4.1
- // It's our max frame size we send in SETTINGS frame,
- // it's also the default one and we also use it to later
- // validate incoming frames:
- maxFrameSize = 16384, // HTTP/2 6.5.2
+ // The initial allowed payload size. We would use it as an
+ // upper limit for a frame payload we send, until our peer
+ // updates us with a larger SETTINGS_MAX_FRAME_SIZE.
- defaultSessionWindowSize = 65535, // HTTP/2 6.5.2
+ // The initial maximum payload size that an HTTP/2 frame
+ // can contain is 16384. It's also the minimal size that
+ // can be advertised via 'SETTINGS' frames. A real frame
+ // can have a payload smaller than 16384.
+ minPayloadLimit = 16384, // HTTP/2 6.5.2
+ // The maximum allowed payload size.
maxPayloadSize = (1 << 24) - 1, // HTTP/2 6.5.2
+
+ defaultSessionWindowSize = 65535, // HTTP/2 6.5.2
// Using 1000 (rather arbitrarily), just to
// impose *some* upper limit:
maxPeerConcurrentStreams = 1000,
@@ -145,48 +153,9 @@ const quint32 lastValidStreamID((quint32(1) << 31) - 1); // HTTP/2, 5.1.1
const qint32 maxSessionReceiveWindowSize((quint32(1) << 31) - 1);
const qint32 qtDefaultStreamReceiveWindowSize = maxSessionReceiveWindowSize / maxConcurrentStreams;
-// The class ProtocolParameters allows client code to customize HTTP/2 protocol
-// handler, if needed. Normally, we use our own default parameters (see below).
-// In 5.10 we can also use setProperty/property on a QNAM object to pass the
-// non-default values to the protocol handler. In 5.11 this will probably become
-// a public API.
-
-using RawSettings = QMap<Settings, quint32>;
-
-struct Q_AUTOTEST_EXPORT ProtocolParameters
-{
- ProtocolParameters();
-
- bool validate() const;
- QByteArray settingsFrameToBase64() const;
- struct Frame settingsFrame() const;
- void addProtocolUpgradeHeaders(QHttpNetworkRequest *request) const;
-
- // HPACK:
- // TODO: for now we ignore them (fix it for 5.11, would require changes in HPACK)
- bool useHuffman = true;
- bool indexStrings = true;
-
- // This parameter is not negotiated via SETTINGS frames, so we have it
- // as a member and will convey it to our peer as a WINDOW_UPDATE frame.
- // Note, some servers do not accept our WINDOW_UPDATE from the default
- // 64 KB to the possible maximum. Let's use a half of it:
- qint32 maxSessionReceiveWindowSize = Http2::maxSessionReceiveWindowSize / 2;
-
- // This is our default SETTINGS frame:
- //
- // SETTINGS_INITIAL_WINDOW_SIZE: (2^31 - 1) / 100
- // SETTINGS_ENABLE_PUSH: 0.
- //
- // Note, whenever we skip some value in our SETTINGS frame, our peer
- // will assume the defaults recommended by RFC 7540, which in general
- // are good enough, although we (and most browsers) prefer to work
- // with larger window sizes.
- RawSettings settingsFrameData;
-};
-
-// TODO: remove in 5.11
-extern const Q_AUTOTEST_EXPORT char *http2ParametersPropertyName;
+struct Frame configurationToSettingsFrame(const QHttp2Configuration &configuration);
+QByteArray settingsFrameToBase64(const Frame &settingsFrame);
+void appendProtocolUpgradeHeaders(const QHttp2Configuration &configuration, QHttpNetworkRequest *request);
extern const Q_AUTOTEST_EXPORT char Http2clientPreface[clientPrefaceLength];
@@ -235,6 +204,5 @@ Q_DECLARE_LOGGING_CATEGORY(QT_HTTP2)
QT_END_NAMESPACE
Q_DECLARE_METATYPE(Http2::Settings)
-Q_DECLARE_METATYPE(Http2::ProtocolParameters)
#endif