summaryrefslogtreecommitdiffstats
path: root/src/network/access
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
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')
-rw-r--r--src/network/access/http2/http2protocol.cpp129
-rw-r--r--src/network/access/http2/http2protocol_p.h61
-rw-r--r--src/network/access/qhttp2protocolhandler.cpp65
-rw-r--r--src/network/access/qhttp2protocolhandler_p.h34
-rw-r--r--src/network/access/qhttpnetworkconnection.cpp17
-rw-r--r--src/network/access/qhttpnetworkconnection_p.h6
-rw-r--r--src/network/access/qhttpnetworkconnectionchannel.cpp4
-rw-r--r--src/network/access/qhttpthreaddelegate.cpp8
-rw-r--r--src/network/access/qhttpthreaddelegate_p.h2
-rw-r--r--src/network/access/qnetworkreplyhttpimpl.cpp4
10 files changed, 262 insertions, 68 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
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 <QtNetwork/qnetworkreply.h>
#include <QtCore/qloggingcategory.h>
+#include <QtCore/qmetatype.h>
#include <QtCore/qglobal.h>
// 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<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:
+ 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<Http2::Frame> 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<quint32>::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 &params)
+{
+ 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 <private/qhttpnetworkheader_p.h>
#include <private/qhttpnetworkrequest_p.h>
#include <private/qhttpnetworkreply_p.h>
+#include <private/http2protocol_p.h>
#include <private/qhttpnetworkconnectionchannel_p.h>
@@ -139,6 +140,9 @@ public:
ConnectionType connectionType();
void setConnectionType(ConnectionType type);
+ Http2::ProtocolParameters http2Parameters() const;
+ void setHttp2Parameters(const Http2::ProtocolParameters &params);
+
#ifndef QT_NO_SSL
void setSslConfiguration(const QSslConfiguration &config);
void ignoreSslErrors(int channel = -1);
@@ -282,6 +286,8 @@ public:
QSharedPointer<QNetworkSession> 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 <QScopedPointer>
#include "private/qnoncontiguousbytedevice_p.h"
#include "qnetworkaccessauthenticationmanager_p.h"
+#include <QtNetwork/private/http2protocol_p.h>
#ifndef QT_NO_HTTP
@@ -115,6 +116,7 @@ public:
qint64 removedContentLength;
QNetworkReply::NetworkError incomingErrorCode;
QString incomingErrorDetail;
+ Http2::ProtocolParameters http2Parameters;
#ifndef QT_NO_BEARERMANAGEMENT
QSharedPointer<QNetworkSession> 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<Http2::ProtocolParameters>())
+ delegate->http2Parameters = blob.value<Http2::ProtocolParameters>();
#ifndef QT_NO_BEARERMANAGEMENT
delegate->networkSession = managerPrivate->getNetworkSession();
#endif