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:
parent
dcdfb6908d
commit
8052755fd7
@ -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 += \
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
343
src/network/access/qhttp2configuration.cpp
Normal file
343
src/network/access/qhttp2configuration.cpp
Normal 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
|
99
src/network/access/qhttp2configuration.h
Normal file
99
src/network/access/qhttp2configuration.h
Normal 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
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 ¶ms);
|
||||
QHttp2Configuration http2Parameters() const;
|
||||
void setHttp2Parameters(const QHttp2Configuration ¶ms);
|
||||
|
||||
#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
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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())
|
||||
|
Loading…
Reference in New Issue
Block a user