Add means to configure HTTP/2 protocol handler

Similar to TLS configuration that we can use on QNetworkRequest,
we can configure different options in our HTTP/2 handling by
providing QNetworkAccessManager with h2 configuration. Previously,
it was only possible internally in our auto-test - a hack with
QObject's properties and a private class. Now it's time to provide
a public API for this.

[ChangeLog][QtNetwork][QNetworkRequest] Add an ability to configure HTTP/2 protocol

Change-Id: I80266a74f6dcdfabb7fc05ed1dce17897bcda886
Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
This commit is contained in:
Timur Pocheptsov 2019-08-08 16:12:46 +02:00
parent dcdfb6908d
commit 8052755fd7
20 changed files with 644 additions and 243 deletions

View File

@ -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 += \

View File

@ -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;

View File

@ -43,6 +43,8 @@
#include "private/qhttpnetworkrequest_p.h"
#include "private/qhttpnetworkreply_p.h"
#include <access/qhttp2configuration.h>
#include <QtCore/qbytearray.h>
#include <QtCore/qstring.h>
@ -62,88 +64,25 @@ const char Http2clientPreface[clientPrefaceLength] =
0x2e, 0x30, 0x0d, 0x0a, 0x0d, 0x0a,
0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a};
// TODO: (in 5.11) - remove it!
const char *http2ParametersPropertyName = "QT_HTTP2_PARAMETERS_PROPERTY";
ProtocolParameters::ProtocolParameters()
Frame configurationToSettingsFrame(const QHttp2Configuration &config)
{
settingsFrameData[Settings::INITIAL_WINDOW_SIZE_ID] = qtDefaultStreamReceiveWindowSize;
settingsFrameData[Settings::ENABLE_PUSH_ID] = 0;
// 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();
}
bool ProtocolParameters::validate() const
QByteArray settingsFrameToBase64(const Frame &frame)
{
// 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(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,

View File

@ -55,12 +55,14 @@
#include <QtCore/qloggingcategory.h>
#include <QtCore/qmetatype.h>
#include <QtCore/qglobal.h>
#include <QtCore/qmap.h>
// Different HTTP/2 constants/values as defined by RFC 7540.
QT_BEGIN_NAMESPACE
class QHttpNetworkRequest;
class QHttp2Configuration;
class QHttpNetworkReply;
class QByteArray;
class QString;
@ -118,13 +120,19 @@ enum Http2PredefinedParameters
connectionStreamID = 0, // HTTP/2, 5.1.1
frameHeaderSize = 9, // HTTP/2, 4.1
// It's our max frame size we send in SETTINGS frame,
// it's also the default one and we also use it to later
// validate incoming frames:
maxFrameSize = 16384, // HTTP/2 6.5.2
// The initial allowed payload size. We would use it as an
// upper limit for a frame payload we send, until our peer
// updates us with a larger SETTINGS_MAX_FRAME_SIZE.
// 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
maxPayloadSize = (1 << 24) - 1, // HTTP/2 6.5.2
// Using 1000 (rather arbitrarily), just to
// impose *some* upper limit:
maxPeerConcurrentStreams = 1000,
@ -145,48 +153,9 @@ const quint32 lastValidStreamID((quint32(1) << 31) - 1); // HTTP/2, 5.1.1
const qint32 maxSessionReceiveWindowSize((quint32(1) << 31) - 1);
const qint32 qtDefaultStreamReceiveWindowSize = maxSessionReceiveWindowSize / maxConcurrentStreams;
// The class ProtocolParameters allows client code to customize HTTP/2 protocol
// handler, if needed. Normally, we use our own default parameters (see below).
// In 5.10 we can also use setProperty/property on a QNAM object to pass the
// non-default values to the protocol handler. In 5.11 this will probably become
// a public API.
using RawSettings = QMap<Settings, quint32>;
struct Q_AUTOTEST_EXPORT ProtocolParameters
{
ProtocolParameters();
bool validate() const;
QByteArray settingsFrameToBase64() const;
struct Frame settingsFrame() const;
void addProtocolUpgradeHeaders(QHttpNetworkRequest *request) const;
// HPACK:
// TODO: for now we ignore them (fix it for 5.11, would require changes in HPACK)
bool useHuffman = true;
bool indexStrings = true;
// This parameter is not negotiated via SETTINGS frames, so we have it
// as a member and will convey it to our peer as a WINDOW_UPDATE frame.
// Note, some servers do not accept our WINDOW_UPDATE from the default
// 64 KB to the possible maximum. Let's use a half of it:
qint32 maxSessionReceiveWindowSize = Http2::maxSessionReceiveWindowSize / 2;
// This is our default SETTINGS frame:
//
// SETTINGS_INITIAL_WINDOW_SIZE: (2^31 - 1) / 100
// SETTINGS_ENABLE_PUSH: 0.
//
// Note, whenever we skip some value in our SETTINGS frame, our peer
// will assume the defaults recommended by RFC 7540, which in general
// are good enough, although we (and most browsers) prefer to work
// with larger window sizes.
RawSettings settingsFrameData;
};
// TODO: remove in 5.11
extern const Q_AUTOTEST_EXPORT char *http2ParametersPropertyName;
struct Frame configurationToSettingsFrame(const QHttp2Configuration &configuration);
QByteArray settingsFrameToBase64(const Frame &settingsFrame);
void appendProtocolUpgradeHeaders(const QHttp2Configuration &configuration, QHttpNetworkRequest *request);
extern const Q_AUTOTEST_EXPORT char Http2clientPreface[clientPrefaceLength];
@ -235,6 +204,5 @@ Q_DECLARE_LOGGING_CATEGORY(QT_HTTP2)
QT_END_NAMESPACE
Q_DECLARE_METATYPE(Http2::Settings)
Q_DECLARE_METATYPE(Http2::ProtocolParameters)
#endif

View File

@ -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

View File

@ -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 <QtNetwork/qtnetworkglobal.h>
#include <QtCore/qshareddata.h>
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<QHttp2ConfigurationPrivate> 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

View File

@ -40,6 +40,7 @@
#include "qhttpnetworkconnection_p.h"
#include "qhttp2protocolhandler_p.h"
#include "http2/http2frames_p.h"
#include "http2/bitstreams_p.h"
#include <private/qnoncontiguousbytedevice_p.h>
@ -51,6 +52,8 @@
#include <QtCore/qlist.h>
#include <QtCore/qurl.h>
#include <qhttp2configuration.h>
#ifndef QT_NO_NETWORKPROXY
#include <QtNetwork/qnetworkproxy.h>
#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;
}

View File

@ -55,6 +55,8 @@
#include <private/qabstractprotocolhandler_p.h>
#include <private/qhttpnetworkrequest_p.h>
#include <access/qhttp2configuration.h>
#include <private/http2protocol_p.h>
#include <private/http2streams_p.h>
#include <private/http2frames_p.h>
@ -163,8 +165,9 @@ private:
static const std::deque<quint32>::size_type maxRecycledStreams;
std::deque<quint32> 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;

View File

@ -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 &params)
void QHttpNetworkConnection::setHttp2Parameters(const QHttp2Configuration &params)
{
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

View File

@ -57,6 +57,8 @@
#include <QtNetwork/qabstractsocket.h>
#include <QtNetwork/qnetworksession.h>
#include <qhttp2configuration.h>
#include <private/qobject_p.h>
#include <qauthenticator.h>
#include <qnetworkproxy.h>
@ -142,8 +144,8 @@ public:
ConnectionType connectionType();
void setConnectionType(ConnectionType type);
Http2::ProtocolParameters http2Parameters() const;
void setHttp2Parameters(const Http2::ProtocolParameters &params);
QHttp2Configuration http2Parameters() const;
void setHttp2Parameters(const QHttp2Configuration &params);
#ifndef QT_NO_SSL
void setSslConfiguration(const QSslConfiguration &config);
@ -294,7 +296,7 @@ public:
QSharedPointer<QNetworkSession> networkSession;
#endif
Http2::ProtocolParameters http2Parameters;
QHttp2Configuration http2Parameters;
QString peerVerifyName;
// If network status monitoring is enabled, we activate connectionMonitor

View File

@ -40,6 +40,7 @@
#include "qhttpnetworkconnectionchannel_p.h"
#include "qhttpnetworkconnection_p.h"
#include "qhttp2configuration.h"
#include "private/qnoncontiguousbytedevice_p.h"
#include <qpair.h>
@ -48,6 +49,7 @@
#include <private/qhttp2protocolhandler_p.h>
#include <private/qhttpprotocolhandler_p.h>
#include <private/qspdyprotocolhandler_p.h>
#include <private/http2protocol_p.h>
#ifndef QT_NO_SSL
# include <private/qsslsocket_p.h>
@ -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();
}

View File

@ -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)

View File

@ -62,6 +62,7 @@
#include <QNetworkReply>
#include "qhttpnetworkrequest_p.h"
#include "qhttpnetworkconnection_p.h"
#include "qhttp2configuration.h"
#include <QSharedPointer>
#include <QScopedPointer>
#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<QNetworkSession> networkSession;
#endif

View File

@ -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<QSharedPointer<char> >();
Q_D(QNetworkAccessManager);
if (QNetworkStatusMonitor::isEnabled()) {
connect(&d->statusMonitor, SIGNAL(onlineStateChanged(bool)),
SLOT(_q_onlineStateChanged(bool)));
@ -1178,7 +1180,6 @@ QSharedPointer<QNetworkSession> QNetworkAccessManagerPrivate::getNetworkSession(
#endif // QT_NO_BEARERMANAGEMENT
#ifndef QT_NO_SSL
/*!
\since 5.2

View File

@ -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<Http2::ProtocolParameters>())
delegate->http2Parameters = blob.value<Http2::ProtocolParameters>();
// Propagate Http/2 settings:
delegate->http2Parameters = request.http2Configuration();
#ifndef QT_NO_BEARERMANAGEMENT
if (!QNetworkStatusMonitor::isEnabled())
delegate->networkSession = managerPrivate->getNetworkSession();

View File

@ -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) {

View File

@ -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<QNetworkRequestPrivate> d;
friend class QNetworkRequestPrivate;

View File

@ -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<quint32>(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<const uchar *>(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

View File

@ -42,6 +42,7 @@
#include <QtCore/qbytearray.h>
#include <QtCore/qatomic.h>
#include <QtCore/qglobal.h>
#include <QtCore/qmap.h>
#include <vector>
#include <map>
@ -69,13 +70,15 @@ enum class H2Type {
h2cDirect, // Clear text direct
};
using RawSettings = QMap<Http2::Settings, quint32>;
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;

View File

@ -32,8 +32,10 @@
#include <QtNetwork/private/http2protocol_p.h>
#include <QtNetwork/qnetworkaccessmanager.h>
#include <QtNetwork/qhttp2configuration.h>
#include <QtNetwork/qnetworkrequest.h>
#include <QtNetwork/qnetworkreply.h>
#include <QtCore/qglobal.h>
#include <QtCore/qobject.h>
#include <QtCore/qthread.h>
@ -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())