From 76a6b3294223f52568cd8c6190edceedbdca70ce Mon Sep 17 00:00:00 2001 From: Timur Pocheptsov Date: Wed, 18 Oct 2017 11:59:21 +0200 Subject: HTTP/2 - make protocol settings configurable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Reviewed-by: Edward Welbourne --- src/network/access/http2/http2protocol.cpp | 129 ++++++++++++++++----- src/network/access/http2/http2protocol_p.h | 61 ++++++++-- src/network/access/qhttp2protocolhandler.cpp | 65 ++++++++--- src/network/access/qhttp2protocolhandler_p.h | 34 ++++-- src/network/access/qhttpnetworkconnection.cpp | 17 +++ src/network/access/qhttpnetworkconnection_p.h | 6 + .../access/qhttpnetworkconnectionchannel.cpp | 4 +- src/network/access/qhttpthreaddelegate.cpp | 8 +- src/network/access/qhttpthreaddelegate_p.h | 2 + src/network/access/qnetworkreplyhttpimpl.cpp | 4 + 10 files changed, 262 insertions(+), 68 deletions(-) (limited to 'src') 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 diff --git a/src/network/access/http2/http2protocol_p.h b/src/network/access/http2/http2protocol_p.h index c64e960002..f7a89859d3 100644 --- a/src/network/access/http2/http2protocol_p.h +++ b/src/network/access/http2/http2protocol_p.h @@ -53,6 +53,7 @@ #include #include +#include #include // Different HTTP/2 constants/values as defined by RFC 7540. @@ -61,6 +62,7 @@ QT_BEGIN_NAMESPACE class QHttpNetworkRequest; class QHttpNetworkReply; +class QByteArray; class QString; namespace Http2 @@ -137,14 +139,54 @@ const quint32 lastValidStreamID((quint32(1) << 31) - 1); // HTTP/2, 5.1.1 // sending WINDOW_UPDATE frames on a stream/session all the time, for each // 2 DATE frames of size 16K (also default) we'll send a WINDOW_UPDATE frame // for a given stream and have a download speed order of magnitude lower than -// our own HTTP/1.1 protocol handler. We choose a bigger window size (normally, -// HTTP/2 servers are not afraid to immediately set it to possible max anyway) -// and split this window size between our concurrent streams. -const qint32 initialSessionReceiveWindowSize = defaultSessionWindowSize * 10000; -const qint32 initialStreamReceiveWindowSize = initialSessionReceiveWindowSize / maxConcurrentStreams; +// our own HTTP/1.1 protocol handler. We choose a bigger window size: normally, +// HTTP/2 servers are not afraid to immediately set it to the possible max, +// we do the same and split this window size between our concurrent streams. +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; + +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: + qint32 maxSessionReceiveWindowSize = Http2::maxSessionReceiveWindowSize; + + // 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; extern const Q_AUTOTEST_EXPORT char Http2clientPreface[clientPrefaceLength]; -void prepare_for_protocol_upgrade(QHttpNetworkRequest &request); enum class FrameStatus { @@ -182,14 +224,15 @@ enum Http2Error void qt_error(quint32 errorCode, QNetworkReply::NetworkError &error, QString &errorString); QString qt_error_string(quint32 errorCode); QNetworkReply::NetworkError qt_error(quint32 errorCode); -bool is_PUSH_PROMISE_enabled(); bool is_protocol_upgraded(const QHttpNetworkReply &reply); -struct Frame default_SETTINGS_frame(); -} +} // namespace Http2 Q_DECLARE_LOGGING_CATEGORY(QT_HTTP2) QT_END_NAMESPACE +Q_DECLARE_METATYPE(Http2::Settings) +Q_DECLARE_METATYPE(Http2::ProtocolParameters) + #endif diff --git a/src/network/access/qhttp2protocolhandler.cpp b/src/network/access/qhttp2protocolhandler.cpp index 461f2429b3..4b330c491a 100644 --- a/src/network/access/qhttp2protocolhandler.cpp +++ b/src/network/access/qhttp2protocolhandler.cpp @@ -168,9 +168,34 @@ QHttp2ProtocolHandler::QHttp2ProtocolHandler(QHttpNetworkConnectionChannel *chan decoder(HPack::FieldLookupTable::DefaultSize), encoder(HPack::FieldLookupTable::DefaultSize, true) { - Q_ASSERT(channel); + Q_ASSERT(channel && m_connection); + continuedFrames.reserve(20); - pushPromiseEnabled = is_PUSH_PROMISE_enabled(); + + const ProtocolParameters params(m_connection->http2Parameters()); + Q_ASSERT(params.validate()); + + maxSessionReceiveWindowSize = params.maxSessionReceiveWindowSize; + + const RawSettings &data = params.settingsFrameData; + for (auto param = data.cbegin(), end = data.cend(); param != end; ++param) { + switch (param.key()) { + case Settings::INITIAL_WINDOW_SIZE_ID: + streamInitialReceiveWindowSize = param.value(); + break; + case Settings::ENABLE_PUSH_ID: + pushPromiseEnabled = param.value(); + break; + case Settings::HEADER_TABLE_SIZE_ID: + case Settings::MAX_CONCURRENT_STREAMS_ID: + case Settings::MAX_FRAME_SIZE_ID: + case Settings::MAX_HEADER_LIST_SIZE_ID: + // These other settings are just recommendations to our peer. We + // only check they are not crazy in ProtocolParameters::validate(). + default: + break; + } + } if (!channel->ssl) { // We upgraded from HTTP/1.1 to HTTP/2. channel->request was already sent @@ -360,20 +385,25 @@ bool QHttp2ProtocolHandler::sendClientPreface() if (prefaceSent) return true; - const qint64 written = m_socket->write(Http2clientPreface, + const qint64 written = m_socket->write(Http2::Http2clientPreface, Http2::clientPrefaceLength); if (written != Http2::clientPrefaceLength) return false; // 6.5 SETTINGS - frameWriter.setOutboundFrame(default_SETTINGS_frame()); + const ProtocolParameters params(m_connection->http2Parameters()); + Q_ASSERT(params.validate()); + frameWriter.setOutboundFrame(params.settingsFrame()); Q_ASSERT(frameWriter.outboundFrame().payloadSize()); if (!frameWriter.write(*m_socket)) return false; - sessionRecvWindowSize = Http2::initialSessionReceiveWindowSize; - const auto delta = Http2::initialSessionReceiveWindowSize - Http2::defaultSessionWindowSize; + sessionReceiveWindowSize = maxSessionReceiveWindowSize; + // ProtocolParameters::validate does not allow maxSessionReceiveWindowSize + // to be smaller than defaultSessionWindowSize, so everything is OK here with + // 'delta': + const auto delta = maxSessionReceiveWindowSize - Http2::defaultSessionWindowSize; if (!sendWINDOW_UPDATE(Http2::connectionStreamID, delta)) return false; @@ -523,10 +553,10 @@ void QHttp2ProtocolHandler::handleDATA() if (!activeStreams.contains(streamID) && !streamWasReset(streamID)) return connectionError(ENHANCE_YOUR_CALM, "DATA on invalid stream"); - if (qint32(inboundFrame.payloadSize()) > sessionRecvWindowSize) + if (qint32(inboundFrame.payloadSize()) > sessionReceiveWindowSize) return connectionError(FLOW_CONTROL_ERROR, "Flow control error"); - sessionRecvWindowSize -= inboundFrame.payloadSize(); + sessionReceiveWindowSize -= inboundFrame.payloadSize(); if (activeStreams.contains(streamID)) { auto &stream = activeStreams[streamID]; @@ -545,20 +575,20 @@ void QHttp2ProtocolHandler::handleDATA() if (inboundFrame.flags().testFlag(FrameFlag::END_STREAM)) { finishStream(stream); deleteActiveStream(stream.streamID); - } else if (stream.recvWindow < Http2::initialStreamReceiveWindowSize / 2) { + } else if (stream.recvWindow < streamInitialReceiveWindowSize / 2) { QMetaObject::invokeMethod(this, "sendWINDOW_UPDATE", Qt::QueuedConnection, Q_ARG(quint32, stream.streamID), - Q_ARG(quint32, Http2::initialStreamReceiveWindowSize - stream.recvWindow)); - stream.recvWindow = Http2::initialStreamReceiveWindowSize; + Q_ARG(quint32, streamInitialReceiveWindowSize - stream.recvWindow)); + stream.recvWindow = streamInitialReceiveWindowSize; } } } - if (sessionRecvWindowSize < Http2::initialSessionReceiveWindowSize / 2) { + if (sessionReceiveWindowSize < maxSessionReceiveWindowSize / 2) { QMetaObject::invokeMethod(this, "sendWINDOW_UPDATE", Qt::QueuedConnection, Q_ARG(quint32, connectionStreamID), - Q_ARG(quint32, Http2::initialSessionReceiveWindowSize - sessionRecvWindowSize)); - sessionRecvWindowSize = Http2::initialSessionReceiveWindowSize; + Q_ARG(quint32, maxSessionReceiveWindowSize - sessionReceiveWindowSize)); + sessionReceiveWindowSize = maxSessionReceiveWindowSize; } } @@ -1199,7 +1229,7 @@ quint32 QHttp2ProtocolHandler::createNewStream(const HttpMessagePair &message, b const Stream newStream(message, newStreamID, streamInitialSendWindowSize, - Http2::initialStreamReceiveWindowSize); + streamInitialReceiveWindowSize); if (!uploadDone) { if (auto src = newStream.data()) { @@ -1394,8 +1424,7 @@ bool QHttp2ProtocolHandler::tryReserveStream(const Http2::Frame &pushPromiseFram promise.reservedID = reservedID; promise.pushHeader = requestHeader; - activeStreams.insert(reservedID, Stream(urlKey, reservedID, - Http2::initialStreamReceiveWindowSize)); + activeStreams.insert(reservedID, Stream(urlKey, reservedID, streamInitialReceiveWindowSize)); return true; } @@ -1429,7 +1458,7 @@ void QHttp2ProtocolHandler::initReplyFromPushPromise(const HttpMessagePair &mess // Let's pretent we're sending a request now: Stream closedStream(message, promise.reservedID, streamInitialSendWindowSize, - Http2::initialStreamReceiveWindowSize); + streamInitialReceiveWindowSize); closedStream.state = Stream::halfClosedLocal; activeStreams.insert(promise.reservedID, closedStream); promisedStream = &activeStreams[promise.reservedID]; diff --git a/src/network/access/qhttp2protocolhandler_p.h b/src/network/access/qhttp2protocolhandler_p.h index b52f8ae10c..a006663491 100644 --- a/src/network/access/qhttp2protocolhandler_p.h +++ b/src/network/access/qhttp2protocolhandler_p.h @@ -172,20 +172,38 @@ private: bool continuationExpected = false; std::vector continuedFrames; - // Peer's max number of streams ... - quint32 maxConcurrentStreams = Http2::maxConcurrentStreams; - // Control flow: - // Signed integer, it can become negative (it's still a valid window size): - qint32 sessionRecvWindowSize = Http2::initialSessionReceiveWindowSize; - - // Updated by SETTINGS and WINDOW_UPDATE. + // This is how many concurrent streams our peer expects from us: + // 100 is the default value, can be updated by the server's SETTINGS + // frame(s): + quint32 maxConcurrentStreams = Http2::maxConcurrentStreams; + // While we allow sending SETTTINGS_MAX_CONCURRENT_STREAMS to limit our peer, + // it's just a hint and we do not actually enforce it (and we can continue + // sending requests and creating streams while maxConcurrentStreams allows). + + // This is the max value, we set it in a ctor from Http2::ProtocolParameters, + // it does not change after that. + qint32 maxSessionReceiveWindowSize = Http2::defaultSessionWindowSize; + + // Our session receive window size, default is 64Kb. We'll update it from QNAM's + // Http2::ProtocolParameters. Signed integer since it can become negative + // (it's still a valid window size). + qint32 sessionReceiveWindowSize = Http2::defaultSessionWindowSize; + // Our per-stream receive window size, default is 64 Kb, will be updated + // from QNAM's Http2::ProtocolParameters. Again, signed - can become negative. + qint32 streamInitialReceiveWindowSize = Http2::defaultSessionWindowSize; + + // These are our peer's receive window sizes, they will be updated by the + // peer's SETTINGS and WINDOW_UPDATE frames. qint32 sessionSendWindowSize = Http2::defaultSessionWindowSize; qint32 streamInitialSendWindowSize = Http2::defaultSessionWindowSize; - // It's unlimited by default, but can be changed via SETTINGS. + // Our peer's header size limitations. It's unlimited by default, but can + // be changed via peer's SETTINGS frame. quint32 maxHeaderListSize = (std::numeric_limits::max)(); + // While we can send SETTINGS_MAX_HEADER_LIST_SIZE value (our limit on + // the headers size), we never enforce it, it's just a hint to our peer. Q_INVOKABLE void resumeSuspendedStreams(); // Our stream IDs (all odd), the first valid will be 1. diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp index 0b474ba116..e6a15ccfc4 100644 --- a/src/network/access/qhttpnetworkconnection.cpp +++ b/src/network/access/qhttpnetworkconnection.cpp @@ -1437,6 +1437,23 @@ void QHttpNetworkConnection::setConnectionType(ConnectionType type) d->connectionType = type; } +Http2::ProtocolParameters QHttpNetworkConnection::http2Parameters() const +{ + Q_D(const QHttpNetworkConnection); + return d->http2Parameters; +} + +void QHttpNetworkConnection::setHttp2Parameters(const Http2::ProtocolParameters ¶ms) +{ + Q_D(QHttpNetworkConnection); + if (params.validate()) { + d->http2Parameters = params; + } else { + qCWarning(QT_HTTP2) + << "invalid HTTP/2 parameters, falling back to defaults instead"; + } +} + // SSL support below #ifndef QT_NO_SSL void QHttpNetworkConnection::setSslConfiguration(const QSslConfiguration &config) diff --git a/src/network/access/qhttpnetworkconnection_p.h b/src/network/access/qhttpnetworkconnection_p.h index f01a2318a5..d3450417aa 100644 --- a/src/network/access/qhttpnetworkconnection_p.h +++ b/src/network/access/qhttpnetworkconnection_p.h @@ -67,6 +67,7 @@ #include #include #include +#include #include @@ -139,6 +140,9 @@ public: ConnectionType connectionType(); void setConnectionType(ConnectionType type); + Http2::ProtocolParameters http2Parameters() const; + void setHttp2Parameters(const Http2::ProtocolParameters ¶ms); + #ifndef QT_NO_SSL void setSslConfiguration(const QSslConfiguration &config); void ignoreSslErrors(int channel = -1); @@ -282,6 +286,8 @@ public: QSharedPointer networkSession; #endif + Http2::ProtocolParameters http2Parameters; + friend class QHttpNetworkConnectionChannel; }; diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp index 2dc3f80998..094a48a603 100644 --- a/src/network/access/qhttpnetworkconnectionchannel.cpp +++ b/src/network/access/qhttpnetworkconnectionchannel.cpp @@ -907,7 +907,9 @@ void QHttpNetworkConnectionChannel::_q_connected() if (tryProtocolUpgrade) { // Let's augment our request with some magic headers and try to // switch to HTTP/2. - Http2::prepare_for_protocol_upgrade(request); + const Http2::ProtocolParameters params(connection->http2Parameters()); + Q_ASSERT(params.validate()); + params.addProtocolUpgradeHeaders(&request); } sendRequest(); } diff --git a/src/network/access/qhttpthreaddelegate.cpp b/src/network/access/qhttpthreaddelegate.cpp index 3d17664ed4..3204f8da33 100644 --- a/src/network/access/qhttpthreaddelegate.cpp +++ b/src/network/access/qhttpthreaddelegate.cpp @@ -292,7 +292,6 @@ void QHttpThreadDelegate::startRequest() QHttpNetworkConnection::ConnectionType connectionType = httpRequest.isHTTP2Allowed() ? QHttpNetworkConnection::ConnectionTypeHTTP2 : QHttpNetworkConnection::ConnectionTypeHTTP; - #ifndef QT_NO_SSL if (ssl && !incomingSslConfiguration.data()) incomingSslConfiguration.reset(new QSslConfiguration); @@ -334,7 +333,11 @@ void QHttpThreadDelegate::startRequest() httpConnection = new QNetworkAccessCachedHttpConnection(urlCopy.host(), urlCopy.port(), ssl, connectionType, networkSession); -#endif +#endif // QT_NO_BEARERMANAGEMENT + if (connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2 + && http2Parameters.validate()) { + httpConnection->setHttp2Parameters(http2Parameters); + } // else we ignore invalid parameters and use our own defaults. #ifndef QT_NO_SSL // Set the QSslConfiguration from this QNetworkRequest. if (ssl) @@ -360,7 +363,6 @@ void QHttpThreadDelegate::startRequest() } } - // Send the request to the connection httpReply = httpConnection->sendRequest(httpRequest); httpReply->setParent(this); diff --git a/src/network/access/qhttpthreaddelegate_p.h b/src/network/access/qhttpthreaddelegate_p.h index 2f6954aa3b..da115d6710 100644 --- a/src/network/access/qhttpthreaddelegate_p.h +++ b/src/network/access/qhttpthreaddelegate_p.h @@ -66,6 +66,7 @@ #include #include "private/qnoncontiguousbytedevice_p.h" #include "qnetworkaccessauthenticationmanager_p.h" +#include #ifndef QT_NO_HTTP @@ -115,6 +116,7 @@ public: qint64 removedContentLength; QNetworkReply::NetworkError incomingErrorCode; QString incomingErrorDetail; + Http2::ProtocolParameters http2Parameters; #ifndef QT_NO_BEARERMANAGEMENT QSharedPointer networkSession; #endif diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp index 1e68ee52dc..46a2f2f208 100644 --- a/src/network/access/qnetworkreplyhttpimpl.cpp +++ b/src/network/access/qnetworkreplyhttpimpl.cpp @@ -783,6 +783,10 @@ void QNetworkReplyHttpImplPrivate::postRequest(const QNetworkRequest &newHttpReq // Create the HTTP thread delegate QHttpThreadDelegate *delegate = new QHttpThreadDelegate; + // Propagate Http/2 settings if any + const QVariant blob(manager->property(Http2::http2ParametersPropertyName)); + if (blob.isValid() && blob.canConvert()) + delegate->http2Parameters = blob.value(); #ifndef QT_NO_BEARERMANAGEMENT delegate->networkSession = managerPrivate->getNetworkSession(); #endif -- cgit v1.2.3