From 8052755fd7581d70802f651d88b7af8447432d75 Mon Sep 17 00:00:00 2001 From: Timur Pocheptsov Date: Thu, 8 Aug 2019 16:12:46 +0200 Subject: 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 --- src/network/access/access.pri | 6 +- src/network/access/http2/http2frames.cpp | 4 +- src/network/access/http2/http2protocol.cpp | 114 ++----- src/network/access/http2/http2protocol_p.h | 64 +--- src/network/access/qhttp2configuration.cpp | 343 +++++++++++++++++++++ src/network/access/qhttp2configuration.h | 99 ++++++ src/network/access/qhttp2protocolhandler.cpp | 44 +-- src/network/access/qhttp2protocolhandler_p.h | 25 +- src/network/access/qhttpnetworkconnection.cpp | 11 +- src/network/access/qhttpnetworkconnection_p.h | 8 +- .../access/qhttpnetworkconnectionchannel.cpp | 6 +- src/network/access/qhttpthreaddelegate.cpp | 4 +- src/network/access/qhttpthreaddelegate_p.h | 3 +- src/network/access/qnetworkaccessmanager.cpp | 3 +- src/network/access/qnetworkreplyhttpimpl.cpp | 6 +- src/network/access/qnetworkrequest.cpp | 58 +++- src/network/access/qnetworkrequest.h | 4 + tests/auto/network/access/http2/http2srv.cpp | 6 +- tests/auto/network/access/http2/http2srv.h | 11 +- tests/auto/network/access/http2/tst_http2.cpp | 68 ++-- 20 files changed, 644 insertions(+), 243 deletions(-) create mode 100644 src/network/access/qhttp2configuration.cpp create mode 100644 src/network/access/qhttp2configuration.h diff --git a/src/network/access/access.pri b/src/network/access/access.pri index 8a92308f12..87fb82b1a7 100644 --- a/src/network/access/access.pri +++ b/src/network/access/access.pri @@ -24,7 +24,8 @@ HEADERS += \ access/qabstractnetworkcache.h \ access/qnetworkfile_p.h \ access/qhsts_p.h \ - access/qhstspolicy.h + access/qhstspolicy.h \ + access/qhttp2configuration.h SOURCES += \ access/qnetworkaccessauthenticationmanager.cpp \ @@ -44,7 +45,8 @@ SOURCES += \ access/qabstractnetworkcache.cpp \ access/qnetworkfile.cpp \ access/qhsts.cpp \ - access/qhstspolicy.cpp + access/qhstspolicy.cpp \ + access/qhttp2configuration.cpp qtConfig(ftp) { HEADERS += \ 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 + #include #include @@ -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 #include #include +#include // 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; - -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 diff --git a/src/network/access/qhttp2configuration.cpp b/src/network/access/qhttp2configuration.cpp new file mode 100644 index 0000000000..14c9d6dc29 --- /dev/null +++ b/src/network/access/qhttp2configuration.cpp @@ -0,0 +1,343 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qhttp2configuration.h" + +#include "private/http2protocol_p.h" +#include "private/hpack_p.h" + +#include "qdebug.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QHttp2Configuration + \brief The QHttp2Configuration class controls HTTP/2 parameters and settings + \since 5.14 + + \reentrant + \inmodule QtNetwork + \ingroup network + \ingroup shared + + QHttp2Configuration controls HTTP/2 parameters and settings that + QNetworkAccessManager will use to send requests and process responses + when the HTTP/2 protocol is enabled. + + The HTTP/2 parameters that QHttp2Configuration currently supports include: + + \list + \li The session window size for connection-level flow control. + Will be sent to a remote peer when needed as 'WINDOW_UPDATE' + frames on the stream with an identifier 0. + \li The stream receiving window size for stream-level flow control. + Sent as 'SETTINGS_INITIAL_WINDOW_SIZE' parameter in the initial + SETTINGS frame and, when needed, 'WINDOW_UPDATE' frames will be + sent on streams that QNetworkAccessManager opens. + \li The maximum frame size. This parameter limits the maximum payload + a frame coming from the remote peer can have. Sent by QNetworkAccessManager + as 'SETTINGS_MAX_FRAME_SIZE' parameter in the initial 'SETTINGS' + frame. + \li The server push. Allows to enable or disable server push. Sent + as 'SETTINGS_ENABLE_PUSH' parameter in the initial 'SETTINGS' + frame. + \endlist + + The QHttp2Configuration class also controls some of the parameters + affecting the header compression algorithm (HPACK). They include: + + \list + \li Huffman string compression. + \li Indexing strings. + \endlist + + \note The configuration must be set before the first request + was sent to a given host (and thus an HTTP/2 session established). + + \note Details about flow control, server push and 'SETTINGS' + can be found in \l {https://httpwg.org/specs/rfc7540.html}{RFC 7540}. + Different modes and parameters of the HPACK compression algorithm + are described in \l {https://httpwg.org/specs/rfc7541.html}{RFC 7541}. + + \sa QNetworkRequest::setHttp2Configuration(), QNetworkRequest::http2Configuration(), QNetworkAccessManager +*/ + +class QHttp2ConfigurationPrivate : public QSharedData +{ +public: + unsigned sessionWindowSize = Http2::defaultSessionWindowSize; + // The size below is quite a limiting default value, QNetworkRequest + // by default sets a larger number, an application can change this using + // QNetworkRequest::setHttp2Configuration. + unsigned streamWindowSize = Http2::defaultSessionWindowSize; + + unsigned maxFrameSize = Http2::minPayloadLimit; // Initial (default) value of 16Kb. + + bool pushEnabled = false; + // TODO: for now those two below are noop. + bool huffmanCompressionEnabled = true; + bool indexingEnabled = true; +}; + +/*! + Default constructs a QHttp2Configuration object. + + Such a configuration has the following values: + \list + \li Server push is disabled + \li Huffman string compression is enabled + \li String indexing is enabled + \li Window size for connection-level flow control is 65535 octets + \li Window size for stream-level flow control is 65535 octets + \li Frame size is 16384 octets + \endlist +*/ +QHttp2Configuration::QHttp2Configuration() + : d(new QHttp2ConfigurationPrivate) +{ +} + +/*! + Copy-constructs this QHttp2Configuration. +*/ +QHttp2Configuration::QHttp2Configuration(const QHttp2Configuration &) = default; + +/*! + Move-constructs this QHttp2Configuration from \a other +*/ +QHttp2Configuration::QHttp2Configuration(QHttp2Configuration &&other) noexcept +{ + swap(other); +} + +/*! + Copy-assigns to this QHttp2Configuration. +*/ +QHttp2Configuration &QHttp2Configuration::operator=(const QHttp2Configuration &) = default; + +/*! + Move-assigns to this QHttp2Configuration. +*/ +QHttp2Configuration &QHttp2Configuration::operator=(QHttp2Configuration &&) noexcept = default; + +/*! + Destructor. +*/ +QHttp2Configuration::~QHttp2Configuration() +{ +} + +/*! + If \a enable is \c true, a remote server can potentially + use server push to send reponses in advance. + + \sa serverPushEnabled +*/ +void QHttp2Configuration::setServerPushEnabled(bool enable) +{ + d->pushEnabled = enable; +} + +/*! + Returns true if server push was enabled. + + \note By default, QNetworkAccessManager disables server + push via the 'SETTINGS' frame. + + \sa setServerPushEnabled +*/ +bool QHttp2Configuration::serverPushEnabled() const +{ + return d->pushEnabled; +} + +/*! + If \a enable is \c true, HPACK compression will additionally + compress string using the Huffman coding. Enabled by default. + + \note This parameter only affects 'HEADERS' frames that + QNetworkAccessManager is sending. + + \sa huffmanCompressionEnabled +*/ +void QHttp2Configuration::setHuffmanCompressionEnabled(bool enable) +{ + d->huffmanCompressionEnabled = enable; +} + +/*! + Returns \c true if the Huffman coding in HPACK is enabled. + + \sa setHuffmanCompressionEnabled +*/ +bool QHttp2Configuration::huffmanCompressionEnabled() const +{ + return d->huffmanCompressionEnabled; +} + +/*! + If \a enable is \c true, HPACK compression will index strings + in its dynamic compression table. Enabled by default. + + \note This setting only has an affect on how QNetworkAccessManager + sending 'HEADERS' frames. + + \sa stringIndexingEnabled +*/ +void QHttp2Configuration::setStringIndexingEnabled(bool enable) +{ + d->indexingEnabled = enable; +} + +/*! + Returns \true if HPACK compression is indexing strings. + + \sa setStringIndexingEnabled +*/ +bool QHttp2Configuration::stringIndexingEnabled() const +{ + return d->indexingEnabled; +} + +/*! + Sets the window size for connection-level flow control. + \a size cannot be 0 and must not exceed 2147483647 octets. + + \sa sessionReceiveWindowSize +*/ +bool QHttp2Configuration::setSessionReceiveWindowSize(unsigned size) +{ + if (!size || size > Http2::maxSessionReceiveWindowSize) { // RFC-7540, 6.9 + qCWarning(QT_HTTP2) << "Invalid session window size"; + return false; + } + + d->sessionWindowSize = size; + return true; +} + +/*! + Returns the window size for connection-level flow control. + The default value QNetworkAccessManager will be using is + 2147483647 octets. +*/ +unsigned QHttp2Configuration::sessionReceiveWindowSize() const +{ + return d->sessionWindowSize; +} + +/*! + Sets the window size for stream-level flow control. + \a size cannot be 0 and must not exceed 2147483647 octets. + + \sa streamReceiveWindowSize + */ +bool QHttp2Configuration::setStreamReceiveWindowSize(unsigned size) +{ + if (!size || size > Http2::maxSessionReceiveWindowSize) { // RFC-7540, 6.9 + qCWarning(QT_HTTP2) << "Invalid stream window size"; + return false; + } + + d->streamWindowSize = size; + return true; +} + +/*! + Returns the window size for stream-level flow control. + The default value QNetworkAccessManager will be using is + 21474836 octets. +*/ +unsigned QHttp2Configuration::streamReceiveWindowSize() const +{ + return d->streamWindowSize; +} + +/*! + Sets the maximum frame size that QNetworkAccessManager + will advertise to the server when sending its initial SETTINGS frame. + \note While this \a size is required to be within a range between + 16384 and 16777215 inclusive, the actual payload size in frames + that carry payload maybe be less than 16384. +*/ +bool QHttp2Configuration::setMaxFrameSize(unsigned size) +{ + if (size < Http2::minPayloadLimit || size > Http2::maxPayloadSize) { + qCWarning(QT_HTTP2) << "Maximum frame size to advertise is invalid"; + return false; + } + + d->maxFrameSize = size; + return true; +} + +/*! + The maximum payload size that HTTP/2 frames can + have. The default (initial) value is 16384 octets. +*/ +unsigned QHttp2Configuration::maxFrameSize() const +{ + return d->maxFrameSize; +} + +/*! + Swaps this configuration with the \a other configuration. +*/ +void QHttp2Configuration::swap(QHttp2Configuration &other) noexcept +{ + d.swap(other.d); +} + +/*! + Returns \c true if \a lhs and \a rhs have the same set of HTTP/2 + parameters. +*/ +bool operator==(const QHttp2Configuration &lhs, const QHttp2Configuration &rhs) +{ + if (lhs.d == rhs.d) + return true; + + return lhs.d->pushEnabled == rhs.d->pushEnabled + && lhs.d->huffmanCompressionEnabled == rhs.d->huffmanCompressionEnabled + && lhs.d->indexingEnabled == rhs.d->indexingEnabled + && lhs.d->sessionWindowSize == rhs.d->sessionWindowSize + && lhs.d->streamWindowSize == rhs.d->streamWindowSize; +} + +QT_END_NAMESPACE diff --git a/src/network/access/qhttp2configuration.h b/src/network/access/qhttp2configuration.h new file mode 100644 index 0000000000..2a5e8c9341 --- /dev/null +++ b/src/network/access/qhttp2configuration.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QHTTP2CONFIGURATION_H +#define QHTTP2CONFIGURATION_H + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QHttp2ConfigurationPrivate; +class Q_NETWORK_EXPORT QHttp2Configuration +{ + friend Q_NETWORK_EXPORT bool operator==(const QHttp2Configuration &lhs, const QHttp2Configuration &rhs); + +public: + QHttp2Configuration(); + QHttp2Configuration(const QHttp2Configuration &other); + QHttp2Configuration(QHttp2Configuration &&other) noexcept; + QHttp2Configuration &operator = (const QHttp2Configuration &other); + QHttp2Configuration &operator = (QHttp2Configuration &&other) noexcept; + + ~QHttp2Configuration(); + + void setServerPushEnabled(bool enable); + bool serverPushEnabled() const; + + void setHuffmanCompressionEnabled(bool enable); + bool huffmanCompressionEnabled() const; + + void setStringIndexingEnabled(bool enable); + bool stringIndexingEnabled() const; + + bool setSessionReceiveWindowSize(unsigned size); + unsigned sessionReceiveWindowSize() const; + + bool setStreamReceiveWindowSize(unsigned size); + unsigned streamReceiveWindowSize() const; + + bool setMaxFrameSize(unsigned size); + unsigned maxFrameSize() const; + + void swap(QHttp2Configuration &other) noexcept; + +private: + + QSharedDataPointer d; +}; + +Q_DECLARE_SHARED(QHttp2Configuration) + +Q_NETWORK_EXPORT bool operator==(const QHttp2Configuration &lhs, const QHttp2Configuration &rhs); + +inline bool operator!=(const QHttp2Configuration &lhs, const QHttp2Configuration &rhs) +{ + return !(lhs == rhs); +} + +QT_END_NAMESPACE + +#endif // QHTTP2CONFIGURATION_H diff --git a/src/network/access/qhttp2protocolhandler.cpp b/src/network/access/qhttp2protocolhandler.cpp index 0ece5b7179..b8a415000a 100644 --- a/src/network/access/qhttp2protocolhandler.cpp +++ b/src/network/access/qhttp2protocolhandler.cpp @@ -40,6 +40,7 @@ #include "qhttpnetworkconnection_p.h" #include "qhttp2protocolhandler_p.h" +#include "http2/http2frames_p.h" #include "http2/bitstreams_p.h" #include @@ -51,6 +52,8 @@ #include #include +#include + #ifndef QT_NO_NETWORKPROXY #include #endif @@ -172,30 +175,10 @@ QHttp2ProtocolHandler::QHttp2ProtocolHandler(QHttpNetworkConnectionChannel *chan Q_ASSERT(channel && m_connection); continuedFrames.reserve(20); - 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; - } - } + const auto h2Config = m_connection->http2Parameters(); + maxSessionReceiveWindowSize = h2Config.sessionReceiveWindowSize(); + pushPromiseEnabled = h2Config.serverPushEnabled(); + streamInitialReceiveWindowSize = h2Config.streamReceiveWindowSize(); if (!channel->ssl && m_connection->connectionType() != QHttpNetworkConnection::ConnectionTypeHTTP2Direct) { // We upgraded from HTTP/1.1 to HTTP/2. channel->request was already sent @@ -422,20 +405,17 @@ bool QHttp2ProtocolHandler::sendClientPreface() return false; // 6.5 SETTINGS - const ProtocolParameters params(m_connection->http2Parameters()); - Q_ASSERT(params.validate()); - frameWriter.setOutboundFrame(params.settingsFrame()); + frameWriter.setOutboundFrame(Http2::configurationToSettingsFrame(m_connection->http2Parameters())); Q_ASSERT(frameWriter.outboundFrame().payloadSize()); if (!frameWriter.write(*m_socket)) return false; sessionReceiveWindowSize = maxSessionReceiveWindowSize; - // ProtocolParameters::validate does not allow maxSessionReceiveWindowSize - // to be smaller than defaultSessionWindowSize, so everything is OK here with - // 'delta': + // We only send WINDOW_UPDATE for the connection if the size differs from the + // default 64 KB: const auto delta = maxSessionReceiveWindowSize - Http2::defaultSessionWindowSize; - if (!sendWINDOW_UPDATE(Http2::connectionStreamID, delta)) + if (delta && !sendWINDOW_UPDATE(Http2::connectionStreamID, delta)) return false; prefaceSent = true; @@ -1069,7 +1049,7 @@ bool QHttp2ProtocolHandler::acceptSetting(Http2::Settings identifier, quint32 ne } if (identifier == Settings::MAX_FRAME_SIZE_ID) { - if (newValue < Http2::maxFrameSize || newValue > Http2::maxPayloadSize) { + if (newValue < Http2::minPayloadLimit || newValue > Http2::maxPayloadSize) { connectionError(PROTOCOL_ERROR, "SETTGINGS max frame size is out of range"); return false; } diff --git a/src/network/access/qhttp2protocolhandler_p.h b/src/network/access/qhttp2protocolhandler_p.h index b582123961..1943827e23 100644 --- a/src/network/access/qhttp2protocolhandler_p.h +++ b/src/network/access/qhttp2protocolhandler_p.h @@ -55,6 +55,8 @@ #include #include +#include + #include #include #include @@ -163,8 +165,9 @@ private: static const std::deque::size_type maxRecycledStreams; std::deque recycledStreams; - // Peer's max frame size. - quint32 maxFrameSize = Http2::maxFrameSize; + // Peer's max frame size (this min is the default value + // we start with, that can be updated by SETTINGS frame): + quint32 maxFrameSize = Http2::minPayloadLimit; Http2::FrameReader frameReader; Http2::Frame inboundFrame; @@ -176,28 +179,28 @@ private: // Control flow: - // 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): + // This is how many concurrent streams our peer allows us, 100 is the + // initial 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. + // This is our (client-side) maximum possible receive window size, we set + // it in a ctor from QHttp2Configuration, it does not change after that. + // The default is 64Kb: 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 + // Our session current receive window size, updated in a ctor from + // QHttp2Configuration. 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. + // from QHttp2Configuration. 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. + // peer's SETTINGS and WINDOW_UPDATE frames, defaults presumed to be 64Kb. qint32 sessionSendWindowSize = Http2::defaultSessionWindowSize; qint32 streamInitialSendWindowSize = Http2::defaultSessionWindowSize; diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp index 1f1de478ea..13be1aa6b5 100644 --- a/src/network/access/qhttpnetworkconnection.cpp +++ b/src/network/access/qhttpnetworkconnection.cpp @@ -1456,21 +1456,16 @@ void QHttpNetworkConnection::setConnectionType(ConnectionType type) d->connectionType = type; } -Http2::ProtocolParameters QHttpNetworkConnection::http2Parameters() const +QHttp2Configuration QHttpNetworkConnection::http2Parameters() const { Q_D(const QHttpNetworkConnection); return d->http2Parameters; } -void QHttpNetworkConnection::setHttp2Parameters(const Http2::ProtocolParameters ¶ms) +void QHttpNetworkConnection::setHttp2Parameters(const QHttp2Configuration ¶ms) { Q_D(QHttpNetworkConnection); - if (params.validate()) { - d->http2Parameters = params; - } else { - qCWarning(QT_HTTP2) - << "invalid HTTP/2 parameters, falling back to defaults instead"; - } + d->http2Parameters = params; } // SSL support below diff --git a/src/network/access/qhttpnetworkconnection_p.h b/src/network/access/qhttpnetworkconnection_p.h index 85d89f20c2..6808a0c0ac 100644 --- a/src/network/access/qhttpnetworkconnection_p.h +++ b/src/network/access/qhttpnetworkconnection_p.h @@ -57,6 +57,8 @@ #include #include +#include + #include #include #include @@ -142,8 +144,8 @@ public: ConnectionType connectionType(); void setConnectionType(ConnectionType type); - Http2::ProtocolParameters http2Parameters() const; - void setHttp2Parameters(const Http2::ProtocolParameters ¶ms); + QHttp2Configuration http2Parameters() const; + void setHttp2Parameters(const QHttp2Configuration ¶ms); #ifndef QT_NO_SSL void setSslConfiguration(const QSslConfiguration &config); @@ -294,7 +296,7 @@ public: QSharedPointer networkSession; #endif - Http2::ProtocolParameters http2Parameters; + QHttp2Configuration http2Parameters; QString peerVerifyName; // If network status monitoring is enabled, we activate connectionMonitor diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp index 3c9d53c5b5..716ea6c8b2 100644 --- a/src/network/access/qhttpnetworkconnectionchannel.cpp +++ b/src/network/access/qhttpnetworkconnectionchannel.cpp @@ -40,6 +40,7 @@ #include "qhttpnetworkconnectionchannel_p.h" #include "qhttpnetworkconnection_p.h" +#include "qhttp2configuration.h" #include "private/qnoncontiguousbytedevice_p.h" #include @@ -48,6 +49,7 @@ #include #include #include +#include #ifndef QT_NO_SSL # include @@ -947,9 +949,7 @@ void QHttpNetworkConnectionChannel::_q_connected() if (tryProtocolUpgrade) { // Let's augment our request with some magic headers and try to // switch to HTTP/2. - const Http2::ProtocolParameters params(connection->http2Parameters()); - Q_ASSERT(params.validate()); - params.addProtocolUpgradeHeaders(&request); + Http2::appendProtocolUpgradeHeaders(connection->http2Parameters(), &request); } sendRequest(); } diff --git a/src/network/access/qhttpthreaddelegate.cpp b/src/network/access/qhttpthreaddelegate.cpp index acc551a7c9..1900397eab 100644 --- a/src/network/access/qhttpthreaddelegate.cpp +++ b/src/network/access/qhttpthreaddelegate.cpp @@ -352,9 +352,9 @@ void QHttpThreadDelegate::startRequest() networkSession); #endif // QT_NO_BEARERMANAGEMENT if (connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2 - && http2Parameters.validate()) { + || connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) { 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) diff --git a/src/network/access/qhttpthreaddelegate_p.h b/src/network/access/qhttpthreaddelegate_p.h index 6184b39b30..355d1afc30 100644 --- a/src/network/access/qhttpthreaddelegate_p.h +++ b/src/network/access/qhttpthreaddelegate_p.h @@ -62,6 +62,7 @@ #include #include "qhttpnetworkrequest_p.h" #include "qhttpnetworkconnection_p.h" +#include "qhttp2configuration.h" #include #include #include "private/qnoncontiguousbytedevice_p.h" @@ -116,7 +117,7 @@ public: qint64 removedContentLength; QNetworkReply::NetworkError incomingErrorCode; QString incomingErrorDetail; - Http2::ProtocolParameters http2Parameters; + QHttp2Configuration http2Parameters; #ifndef QT_NO_BEARERMANAGEMENT QSharedPointer networkSession; #endif diff --git a/src/network/access/qnetworkaccessmanager.cpp b/src/network/access/qnetworkaccessmanager.cpp index 29192ae7b0..fdc3cd3b3a 100644 --- a/src/network/access/qnetworkaccessmanager.cpp +++ b/src/network/access/qnetworkaccessmanager.cpp @@ -70,6 +70,7 @@ #include "QtNetwork/private/qauthenticator_p.h" #include "QtNetwork/qsslconfiguration.h" #include "QtNetwork/qnetworkconfigmanager.h" +#include "QtNetwork/private/http2protocol_p.h" #if QT_CONFIG(http) #include "qhttpmultipart.h" @@ -489,6 +490,7 @@ QNetworkAccessManager::QNetworkAccessManager(QObject *parent) qRegisterMetaType >(); Q_D(QNetworkAccessManager); + if (QNetworkStatusMonitor::isEnabled()) { connect(&d->statusMonitor, SIGNAL(onlineStateChanged(bool)), SLOT(_q_onlineStateChanged(bool))); @@ -1178,7 +1180,6 @@ QSharedPointer QNetworkAccessManagerPrivate::getNetworkSession( #endif // QT_NO_BEARERMANAGEMENT - #ifndef QT_NO_SSL /*! \since 5.2 diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp index b9651b35d2..12dfb1c269 100644 --- a/src/network/access/qnetworkreplyhttpimpl.cpp +++ b/src/network/access/qnetworkreplyhttpimpl.cpp @@ -797,10 +797,8 @@ 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(); + // Propagate Http/2 settings: + delegate->http2Parameters = request.http2Configuration(); #ifndef QT_NO_BEARERMANAGEMENT if (!QNetworkStatusMonitor::isEnabled()) delegate->networkSession = managerPrivate->getNetworkSession(); diff --git a/src/network/access/qnetworkrequest.cpp b/src/network/access/qnetworkrequest.cpp index 37f64c3f52..e3976db642 100644 --- a/src/network/access/qnetworkrequest.cpp +++ b/src/network/access/qnetworkrequest.cpp @@ -42,6 +42,8 @@ #include "qplatformdefs.h" #include "qnetworkcookie.h" #include "qsslconfiguration.h" +#include "qhttp2configuration.h" +#include "private/http2protocol_p.h" #include "QtCore/qshareddata.h" #include "QtCore/qlocale.h" #include "QtCore/qdatetime.h" @@ -445,6 +447,7 @@ public: sslConfiguration = new QSslConfiguration(*other.sslConfiguration); #endif peerVerifyName = other.peerVerifyName; + h2Configuration = other.h2Configuration; } inline bool operator==(const QNetworkRequestPrivate &other) const @@ -454,7 +457,8 @@ public: rawHeaders == other.rawHeaders && attributes == other.attributes && maxRedirectsAllowed == other.maxRedirectsAllowed && - peerVerifyName == other.peerVerifyName; + peerVerifyName == other.peerVerifyName && + h2Configuration == other.h2Configuration; // don't compare cookedHeaders } @@ -465,6 +469,7 @@ public: #endif int maxRedirectsAllowed; QString peerVerifyName; + QHttp2Configuration h2Configuration; }; /*! @@ -476,6 +481,13 @@ public: QNetworkRequest::QNetworkRequest() : d(new QNetworkRequestPrivate) { + // Initial values proposed by RFC 7540 are quite draconian, + // so unless an application will set its own parameters, we + // make stream window size larger and increase (via WINDOW_UPDATE) + // the session window size. These are our 'defaults': + d->h2Configuration.setStreamReceiveWindowSize(Http2::qtDefaultStreamReceiveWindowSize); + d->h2Configuration.setSessionReceiveWindowSize(Http2::maxSessionReceiveWindowSize); + d->h2Configuration.setServerPushEnabled(false); } /*! @@ -835,6 +847,50 @@ void QNetworkRequest::setPeerVerifyName(const QString &peerName) d->peerVerifyName = peerName; } +/*! + \since 5.14 + + Returns the current parameters that QNetworkAccessManager is + using for this request and its underlying HTTP/2 connection. + This is either a configuration previously set by an application + or a default configuration. + + The default values that QNetworkAccessManager is using are: + + \list + \li Window size for connection-level flowcontrol is 2147483647 octets + \li Window size for stream-level flowcontrol is 21474836 octets + \li Max frame size is 16384 + \endlist + + By default, server push is disabled, Huffman compression and + string indexing are enabled. + + \sa setHttp2Configuration +*/ +QHttp2Configuration QNetworkRequest::http2Configuration() const +{ + return d->h2Configuration; +} + +/*! + \since 5.14 + + Sets request's HTTP/2 parameters from \a configuration. + + \note The configuration must be set prior to making a request. + \note HTTP/2 multiplexes several streams in a single HTTP/2 + connection. This implies that QNetworkAccessManager will use + the configuration found in the first request from a series + of requests sent to the same host. + + \sa http2Configuration, QNetworkAccessManager, QHttp2Configuration +*/ +void QNetworkRequest::setHttp2Configuration(const QHttp2Configuration &configuration) +{ + d->h2Configuration = configuration; +} + static QByteArray headerName(QNetworkRequest::KnownHeaders header) { switch (header) { diff --git a/src/network/access/qnetworkrequest.h b/src/network/access/qnetworkrequest.h index 8ad4ab41c0..463dabef83 100644 --- a/src/network/access/qnetworkrequest.h +++ b/src/network/access/qnetworkrequest.h @@ -49,6 +49,7 @@ QT_BEGIN_NAMESPACE class QSslConfiguration; +class QHttp2Configuration; class QNetworkRequestPrivate; class Q_NETWORK_EXPORT QNetworkRequest @@ -175,6 +176,9 @@ public: QString peerVerifyName() const; void setPeerVerifyName(const QString &peerName); + + QHttp2Configuration http2Configuration() const; + void setHttp2Configuration(const QHttp2Configuration &configuration); private: QSharedDataPointer d; friend class QNetworkRequestPrivate; diff --git a/tests/auto/network/access/http2/http2srv.cpp b/tests/auto/network/access/http2/http2srv.cpp index 5a99d4e50c..1ddb6aa77d 100644 --- a/tests/auto/network/access/http2/http2srv.cpp +++ b/tests/auto/network/access/http2/http2srv.cpp @@ -76,7 +76,7 @@ void fill_push_header(const HttpHeader &originalRequest, HttpHeader &promisedReq } -Http2Server::Http2Server(H2Type type, const Http2::RawSettings &ss, const Http2::RawSettings &cs) +Http2Server::Http2Server(H2Type type, const RawSettings &ss, const RawSettings &cs) : connectionType(type), serverSettings(ss), expectedClientSettings(cs) @@ -218,7 +218,7 @@ void Http2Server::sendDATA(quint32 streamID, quint32 windowSize) quint32 bytesToSend = std::min(windowSize, responseBody.size() - offset); quint32 bytesSent = 0; - const quint32 frameSizeLimit(clientSetting(Settings::MAX_FRAME_SIZE_ID, Http2::maxFrameSize)); + const quint32 frameSizeLimit(clientSetting(Settings::MAX_FRAME_SIZE_ID, Http2::maxPayloadSize)); const uchar *src = reinterpret_cast(responseBody.constData() + offset); const bool last = offset + bytesToSend == quint32(responseBody.size()); @@ -767,7 +767,7 @@ void Http2Server::sendResponse(quint32 streamID, bool emptyBody) Q_ASSERT(activeRequests.find(streamID) != activeRequests.end()); const quint32 maxFrameSize(clientSetting(Settings::MAX_FRAME_SIZE_ID, - Http2::maxFrameSize)); + Http2::maxPayloadSize)); if (pushPromiseEnabled) { // A real server supporting PUSH_PROMISE will probably first send diff --git a/tests/auto/network/access/http2/http2srv.h b/tests/auto/network/access/http2/http2srv.h index 4ef4b25101..9cb846b0b3 100644 --- a/tests/auto/network/access/http2/http2srv.h +++ b/tests/auto/network/access/http2/http2srv.h @@ -42,6 +42,7 @@ #include #include #include +#include #include #include @@ -69,13 +70,15 @@ enum class H2Type { h2cDirect, // Clear text direct }; +using RawSettings = QMap; + class Http2Server : public QTcpServer { Q_OBJECT public: - Http2Server(H2Type type, const Http2::RawSettings &serverSettings, - const Http2::RawSettings &clientSettings); + Http2Server(H2Type type, const RawSettings &serverSettings, + const RawSettings &clientSettings); ~Http2Server(); @@ -147,8 +150,8 @@ private: bool settingsSent = false; bool waitingClientAck = false; - Http2::RawSettings serverSettings; - Http2::RawSettings expectedClientSettings; + RawSettings serverSettings; + RawSettings expectedClientSettings; bool connectionError = false; diff --git a/tests/auto/network/access/http2/tst_http2.cpp b/tests/auto/network/access/http2/tst_http2.cpp index 945ea1b448..6a0dc6db02 100644 --- a/tests/auto/network/access/http2/tst_http2.cpp +++ b/tests/auto/network/access/http2/tst_http2.cpp @@ -32,8 +32,10 @@ #include #include +#include #include #include + #include #include #include @@ -66,6 +68,24 @@ Q_DECLARE_METATYPE(QNetworkRequest::Attribute) QT_BEGIN_NAMESPACE +QHttp2Configuration qt_defaultH2Configuration() +{ + QHttp2Configuration config; + config.setStreamReceiveWindowSize(Http2::qtDefaultStreamReceiveWindowSize); + config.setSessionReceiveWindowSize(Http2::maxSessionReceiveWindowSize); + config.setServerPushEnabled(false); + return config; +} + +RawSettings qt_H2ConfigurationToSettings(const QHttp2Configuration &config = qt_defaultH2Configuration()) +{ + RawSettings settings; + settings[Http2::Settings::ENABLE_PUSH_ID] = config.serverPushEnabled(); + settings[Http2::Settings::INITIAL_WINDOW_SIZE_ID] = config.streamReceiveWindowSize(); + return settings; +} + + class tst_Http2 : public QObject { Q_OBJECT @@ -110,12 +130,13 @@ private: // small payload. void runEventLoop(int ms = 5000); void stopEventLoop(); - Http2Server *newServer(const Http2::RawSettings &serverSettings, H2Type connectionType, - const Http2::ProtocolParameters &clientSettings = {}); + Http2Server *newServer(const RawSettings &serverSettings, H2Type connectionType, + const RawSettings &clientSettings = qt_H2ConfigurationToSettings()); // Send a get or post request, depending on a payload (empty or not). void sendRequest(int streamNumber, QNetworkRequest::Priority priority = QNetworkRequest::NormalPriority, - const QByteArray &payload = QByteArray()); + const QByteArray &payload = QByteArray(), + const QHttp2Configuration &clientConfiguration = qt_defaultH2Configuration()); QUrl requestUrl(H2Type connnectionType) const; quint16 serverPort = 0; @@ -131,14 +152,14 @@ private: bool prefaceOK = false; bool serverGotSettingsACK = false; - static const Http2::RawSettings defaultServerSettings; + static const RawSettings defaultServerSettings; }; #define STOP_ON_FAILURE \ if (QTest::currentTestFailed()) \ return; -const Http2::RawSettings tst_Http2::defaultServerSettings{{Http2::Settings::MAX_CONCURRENT_STREAMS_ID, 100}}; +const RawSettings tst_Http2::defaultServerSettings{{Http2::Settings::MAX_CONCURRENT_STREAMS_ID, 100}}; namespace { @@ -308,18 +329,15 @@ void tst_Http2::flowControlClientSide() nRequests = 10; windowUpdates = 0; - Http2::ProtocolParameters params; + QHttp2Configuration params; // A small window size for a session, and even a smaller one per stream - // this will result in WINDOW_UPDATE frames both on connection stream and // per stream. - params.maxSessionReceiveWindowSize = Http2::defaultSessionWindowSize * 5; - params.settingsFrameData[Settings::INITIAL_WINDOW_SIZE_ID] = Http2::defaultSessionWindowSize; - // Inform our manager about non-default settings: - manager->setProperty(Http2::http2ParametersPropertyName, QVariant::fromValue(params)); - - const Http2::RawSettings serverSettings = {{Settings::MAX_CONCURRENT_STREAMS_ID, quint32(3)}}; - ServerPtr srv(newServer(serverSettings, defaultConnectionType(), params)); + params.setSessionReceiveWindowSize(Http2::defaultSessionWindowSize * 5); + params.setStreamReceiveWindowSize(Http2::defaultSessionWindowSize); + const RawSettings serverSettings = {{Settings::MAX_CONCURRENT_STREAMS_ID, quint32(3)}}; + ServerPtr srv(newServer(serverSettings, defaultConnectionType(), qt_H2ConfigurationToSettings(params))); const QByteArray respond(int(Http2::defaultSessionWindowSize * 10), 'x'); srv->setResponseBody(respond); @@ -330,7 +348,7 @@ void tst_Http2::flowControlClientSide() QVERIFY(serverPort != 0); for (int i = 0; i < nRequests; ++i) - sendRequest(i); + sendRequest(i, QNetworkRequest::NormalPriority, {}, params); runEventLoop(120000); STOP_ON_FAILURE @@ -359,7 +377,7 @@ void tst_Http2::flowControlServerSide() serverPort = 0; nRequests = 10; - const Http2::RawSettings serverSettings = {{Settings::MAX_CONCURRENT_STREAMS_ID, 7}}; + const RawSettings serverSettings = {{Settings::MAX_CONCURRENT_STREAMS_ID, 7}}; ServerPtr srv(newServer(serverSettings, defaultConnectionType())); @@ -392,12 +410,11 @@ void tst_Http2::pushPromise() serverPort = 0; nRequests = 1; - Http2::ProtocolParameters params; + QHttp2Configuration params; // Defaults are good, except ENABLE_PUSH: - params.settingsFrameData[Settings::ENABLE_PUSH_ID] = 1; - manager->setProperty(Http2::http2ParametersPropertyName, QVariant::fromValue(params)); + params.setServerPushEnabled(true); - ServerPtr srv(newServer(defaultServerSettings, defaultConnectionType(), params)); + ServerPtr srv(newServer(defaultServerSettings, defaultConnectionType(), qt_H2ConfigurationToSettings(params))); srv->enablePushPromise(true, QByteArray("/script.js")); QMetaObject::invokeMethod(srv.data(), "startServer", Qt::QueuedConnection); @@ -410,6 +427,7 @@ void tst_Http2::pushPromise() QNetworkRequest request(url); request.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, QVariant(true)); + request.setHttp2Configuration(params); auto reply = manager->get(request); connect(reply, &QNetworkReply::finished, this, &tst_Http2::replyFinished); @@ -689,7 +707,6 @@ void tst_Http2::clearHTTP2State() windowUpdates = 0; prefaceOK = false; serverGotSettingsACK = false; - manager->setProperty(Http2::http2ParametersPropertyName, QVariant()); } void tst_Http2::runEventLoop(int ms) @@ -702,12 +719,11 @@ void tst_Http2::stopEventLoop() eventLoop.exitLoop(); } -Http2Server *tst_Http2::newServer(const Http2::RawSettings &serverSettings, H2Type connectionType, - const Http2::ProtocolParameters &clientSettings) +Http2Server *tst_Http2::newServer(const RawSettings &serverSettings, H2Type connectionType, + const RawSettings &clientSettings) { using namespace Http2; - auto srv = new Http2Server(connectionType, serverSettings, - clientSettings.settingsFrameData); + auto srv = new Http2Server(connectionType, serverSettings, clientSettings); using Srv = Http2Server; using Cl = tst_Http2; @@ -729,7 +745,8 @@ Http2Server *tst_Http2::newServer(const Http2::RawSettings &serverSettings, H2Ty void tst_Http2::sendRequest(int streamNumber, QNetworkRequest::Priority priority, - const QByteArray &payload) + const QByteArray &payload, + const QHttp2Configuration &h2Config) { auto url = requestUrl(defaultConnectionType()); url.setPath(QString("/stream%1.html").arg(streamNumber)); @@ -739,6 +756,7 @@ void tst_Http2::sendRequest(int streamNumber, request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, QVariant(true)); request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("text/plain")); request.setPriority(priority); + request.setHttp2Configuration(h2Config); QNetworkReply *reply = nullptr; if (payload.size()) -- cgit v1.2.3