QNetworkAccessManager: Configurable number of HTTP1 TCP connections
Introduces new class QHttp1Configuration. [ChangeLog][QtNetwork][QHttp1Configuration] New class. Fixes: QTBUG-25280 Fixes: QTBUG-108215 Change-Id: Ide6cee23946e5001befb8fab34edf10b8a66e02b Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
parent
a09c33e1f7
commit
be05bb749e
@ -121,6 +121,7 @@ qt_internal_extend_target(Network CONDITION QT_FEATURE_http
|
||||
access/http2/huffman.cpp access/http2/huffman_p.h
|
||||
access/qabstractprotocolhandler.cpp access/qabstractprotocolhandler_p.h
|
||||
access/qdecompresshelper.cpp access/qdecompresshelper_p.h
|
||||
access/qhttp1configuration.cpp access/qhttp1configuration.h
|
||||
access/qhttp2configuration.cpp access/qhttp2configuration.h
|
||||
access/qhttp2protocolhandler.cpp access/qhttp2protocolhandler_p.h
|
||||
access/qhttpmultipart.cpp access/qhttpmultipart.h access/qhttpmultipart_p.h
|
||||
|
125
src/network/access/qhttp1configuration.cpp
Normal file
125
src/network/access/qhttp1configuration.cpp
Normal file
@ -0,0 +1,125 @@
|
||||
// Copyright (C) 2022 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#include "qhttp1configuration.h"
|
||||
|
||||
#include "qdebug.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
/*!
|
||||
\class QHttp1Configuration
|
||||
\brief The QHttp1Configuration class controls HTTP/1 parameters and settings.
|
||||
\since 6.5
|
||||
|
||||
\reentrant
|
||||
\inmodule QtNetwork
|
||||
\ingroup network
|
||||
\ingroup shared
|
||||
|
||||
QHttp1Configuration controls HTTP/1 parameters and settings that
|
||||
QNetworkAccessManager will use to send requests and process responses.
|
||||
|
||||
\note The configuration must be set before the first request
|
||||
was sent to a given host (and thus an HTTP/1 session established).
|
||||
|
||||
\sa QNetworkRequest::setHttp1Configuration(), QNetworkRequest::http1Configuration(), QNetworkAccessManager
|
||||
*/
|
||||
|
||||
class QHttp1ConfigurationPrivate : public QSharedData
|
||||
{
|
||||
public:
|
||||
unsigned numberOfConnectionsPerHost = 6; // QHttpNetworkConnectionPrivate::defaultHttpChannelCount
|
||||
};
|
||||
|
||||
/*!
|
||||
Default constructs a QHttp1Configuration object.
|
||||
*/
|
||||
QHttp1Configuration::QHttp1Configuration()
|
||||
: d(new QHttp1ConfigurationPrivate)
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
Copy-constructs this QHttp1Configuration.
|
||||
*/
|
||||
QHttp1Configuration::QHttp1Configuration(const QHttp1Configuration &) = default;
|
||||
|
||||
/*!
|
||||
Move-constructs this QHttp1Configuration from \a other
|
||||
*/
|
||||
QHttp1Configuration::QHttp1Configuration(QHttp1Configuration &&other) noexcept
|
||||
{
|
||||
swap(other);
|
||||
}
|
||||
|
||||
/*!
|
||||
Copy-assigns \a other to this QHttp1Configuration.
|
||||
*/
|
||||
QHttp1Configuration &QHttp1Configuration::operator=(const QHttp1Configuration &) = default;
|
||||
|
||||
/*!
|
||||
Move-assigns \a other to this QHttp1Configuration.
|
||||
*/
|
||||
QHttp1Configuration &QHttp1Configuration::operator=(QHttp1Configuration &&) noexcept = default;
|
||||
|
||||
/*!
|
||||
Destructor.
|
||||
*/
|
||||
QHttp1Configuration::~QHttp1Configuration()
|
||||
= default;
|
||||
|
||||
/*!
|
||||
Sets number of connections (default 6) to a http(s)://host:port
|
||||
\sa numberOfConnectionsPerHost
|
||||
*/
|
||||
void QHttp1Configuration::setNumberOfConnectionsPerHost(unsigned number)
|
||||
{
|
||||
if (number == 0) {
|
||||
return;
|
||||
}
|
||||
d->numberOfConnectionsPerHost = number;
|
||||
}
|
||||
/*!
|
||||
Returns the number of connections (default 6) to a http(s)://host:port
|
||||
\sa setNumberOfConnectionsPerHost
|
||||
*/
|
||||
unsigned QHttp1Configuration::numberOfConnectionsPerHost() const
|
||||
{
|
||||
return d->numberOfConnectionsPerHost;
|
||||
}
|
||||
|
||||
/*!
|
||||
Swaps this configuration with the \a other configuration.
|
||||
*/
|
||||
void QHttp1Configuration::swap(QHttp1Configuration &other) noexcept
|
||||
{
|
||||
d.swap(other.d);
|
||||
}
|
||||
|
||||
/*!
|
||||
\fn bool QHttp1Configuration::operator==(const QHttp1Configuration &lhs, const QHttp1Configuration &rhs) noexcept
|
||||
|
||||
Returns \c true if \a lhs and \a rhs represent the same set of HTTP/1
|
||||
parameters.
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn bool QHttp1Configuration::operator!=(const QHttp1Configuration &lhs, const QHttp1Configuration &rhs) noexcept
|
||||
|
||||
Returns \c true if \a lhs and \a rhs do not represent the same set of
|
||||
HTTP/1 parameters.
|
||||
*/
|
||||
|
||||
/*!
|
||||
\internal
|
||||
*/
|
||||
bool QHttp1Configuration::isEqual(const QHttp1Configuration &other) const noexcept
|
||||
{
|
||||
if (d == other.d)
|
||||
return true;
|
||||
|
||||
return d->numberOfConnectionsPerHost == other.d->numberOfConnectionsPerHost;
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
51
src/network/access/qhttp1configuration.h
Normal file
51
src/network/access/qhttp1configuration.h
Normal file
@ -0,0 +1,51 @@
|
||||
// Copyright (C) 2022 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#ifndef QHTTP1CONFIGURATION_H
|
||||
#define QHTTP1CONFIGURATION_H
|
||||
|
||||
#include <QtNetwork/qtnetworkglobal.h>
|
||||
|
||||
#include <QtCore/qshareddata.h>
|
||||
|
||||
#ifndef Q_CLANG_QDOC
|
||||
QT_REQUIRE_CONFIG(http);
|
||||
#endif
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class QHttp1ConfigurationPrivate;
|
||||
class Q_NETWORK_EXPORT QHttp1Configuration
|
||||
{
|
||||
public:
|
||||
QHttp1Configuration();
|
||||
QHttp1Configuration(const QHttp1Configuration &other);
|
||||
QHttp1Configuration(QHttp1Configuration &&other) noexcept;
|
||||
|
||||
QHttp1Configuration &operator=(const QHttp1Configuration &other);
|
||||
QHttp1Configuration &operator=(QHttp1Configuration &&other) noexcept;
|
||||
|
||||
~QHttp1Configuration();
|
||||
|
||||
void setNumberOfConnectionsPerHost(unsigned amount);
|
||||
unsigned numberOfConnectionsPerHost() const;
|
||||
|
||||
void swap(QHttp1Configuration &other) noexcept;
|
||||
|
||||
private:
|
||||
QSharedDataPointer<QHttp1ConfigurationPrivate> d;
|
||||
|
||||
bool isEqual(const QHttp1Configuration &other) const noexcept;
|
||||
|
||||
friend bool operator==(const QHttp1Configuration &lhs, const QHttp1Configuration &rhs) noexcept
|
||||
{ return lhs.isEqual(rhs); }
|
||||
friend bool operator!=(const QHttp1Configuration &lhs, const QHttp1Configuration &rhs) noexcept
|
||||
{ return !lhs.isEqual(rhs); }
|
||||
|
||||
};
|
||||
|
||||
Q_DECLARE_SHARED(QHttp1Configuration)
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // QHTTP1CONFIGURATION_H
|
@ -32,6 +32,7 @@ QT_BEGIN_NAMESPACE
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
// Note: Only used from auto tests, normal usage is via QHttp1Configuration
|
||||
const int QHttpNetworkConnectionPrivate::defaultHttpChannelCount = 6;
|
||||
|
||||
// The pipeline length. So there will be 4 requests in flight.
|
||||
@ -69,7 +70,7 @@ QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(quint16 connectionC
|
||||
QHttpNetworkConnection::ConnectionType type)
|
||||
: state(RunningState), networkLayerState(Unknown),
|
||||
hostName(hostName), port(port), encrypt(encrypt), delayIpv4(true),
|
||||
activeChannelCount(connectionCount), channelCount(connectionCount)
|
||||
channelCount(connectionCount)
|
||||
#ifndef QT_NO_NETWORKPROXY
|
||||
, networkProxy(QNetworkProxy::NoProxy)
|
||||
#endif
|
||||
@ -77,6 +78,14 @@ QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(quint16 connectionC
|
||||
, connectionType(type)
|
||||
{
|
||||
channels = new QHttpNetworkConnectionChannel[channelCount];
|
||||
|
||||
activeChannelCount = (type == QHttpNetworkConnection::ConnectionTypeHTTP2 ||
|
||||
type == QHttpNetworkConnection::ConnectionTypeHTTP2Direct)
|
||||
? 1 : connectionCount;
|
||||
// We allocate all 6 channels even if it's an HTTP/2-enabled
|
||||
// connection: in case the protocol negotiation via NPN/ALPN fails,
|
||||
// we will have normally working HTTP/1.1.
|
||||
Q_ASSERT(channelCount >= activeChannelCount);
|
||||
}
|
||||
|
||||
|
||||
|
@ -145,9 +145,9 @@ class QNetworkAccessCachedHttpConnection: public QHttpNetworkConnection,
|
||||
{
|
||||
// Q_OBJECT
|
||||
public:
|
||||
QNetworkAccessCachedHttpConnection(const QString &hostName, quint16 port, bool encrypt,
|
||||
QNetworkAccessCachedHttpConnection(quint16 connectionCount, const QString &hostName, quint16 port, bool encrypt,
|
||||
QHttpNetworkConnection::ConnectionType connectionType)
|
||||
: QHttpNetworkConnection(hostName, port, encrypt, connectionType)
|
||||
: QHttpNetworkConnection(connectionCount, hostName, port, encrypt, /*parent=*/nullptr, connectionType)
|
||||
{
|
||||
setExpires(true);
|
||||
setShareable(true);
|
||||
@ -297,7 +297,7 @@ void QHttpThreadDelegate::startRequest()
|
||||
if (!httpConnection) {
|
||||
// no entry in cache; create an object
|
||||
// the http object is actually a QHttpNetworkConnection
|
||||
httpConnection = new QNetworkAccessCachedHttpConnection(urlCopy.host(), urlCopy.port(), ssl,
|
||||
httpConnection = new QNetworkAccessCachedHttpConnection(http1Parameters.numberOfConnectionsPerHost(), urlCopy.host(), urlCopy.port(), ssl,
|
||||
connectionType);
|
||||
if (connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2
|
||||
|| connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) {
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include <QNetworkReply>
|
||||
#include "qhttpnetworkrequest_p.h"
|
||||
#include "qhttpnetworkconnection_p.h"
|
||||
#include "qhttp1configuration.h"
|
||||
#include "qhttp2configuration.h"
|
||||
#include <QSharedPointer>
|
||||
#include <QScopedPointer>
|
||||
@ -82,6 +83,7 @@ public:
|
||||
qint64 removedContentLength;
|
||||
QNetworkReply::NetworkError incomingErrorCode;
|
||||
QString incomingErrorDetail;
|
||||
QHttp1Configuration http1Parameters;
|
||||
QHttp2Configuration http2Parameters;
|
||||
|
||||
bool isCompressed;
|
||||
|
@ -784,6 +784,7 @@ void QNetworkReplyHttpImplPrivate::postRequest(const QNetworkRequest &newHttpReq
|
||||
QHttpThreadDelegate *delegate = new QHttpThreadDelegate;
|
||||
// Propagate Http/2 settings:
|
||||
delegate->http2Parameters = request.http2Configuration();
|
||||
delegate->http1Parameters = request.http1Configuration();
|
||||
|
||||
if (request.attribute(QNetworkRequest::ConnectionCacheExpiryTimeoutSecondsAttribute).isValid())
|
||||
delegate->connectionCacheExpiryTimeoutSeconds = request.attribute(QNetworkRequest::ConnectionCacheExpiryTimeoutSecondsAttribute).toInt();
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "qnetworkcookie.h"
|
||||
#include "qsslconfiguration.h"
|
||||
#if QT_CONFIG(http) || defined(Q_QDOC)
|
||||
#include "qhttp1configuration.h"
|
||||
#include "qhttp2configuration.h"
|
||||
#include "private/http2protocol_p.h"
|
||||
#endif
|
||||
@ -439,6 +440,7 @@ public:
|
||||
#endif
|
||||
peerVerifyName = other.peerVerifyName;
|
||||
#if QT_CONFIG(http)
|
||||
h1Configuration = other.h1Configuration;
|
||||
h2Configuration = other.h2Configuration;
|
||||
decompressedSafetyCheckThreshold = other.decompressedSafetyCheckThreshold;
|
||||
#endif
|
||||
@ -454,6 +456,7 @@ public:
|
||||
maxRedirectsAllowed == other.maxRedirectsAllowed &&
|
||||
peerVerifyName == other.peerVerifyName
|
||||
#if QT_CONFIG(http)
|
||||
&& h1Configuration == other.h1Configuration
|
||||
&& h2Configuration == other.h2Configuration
|
||||
&& decompressedSafetyCheckThreshold == other.decompressedSafetyCheckThreshold
|
||||
#endif
|
||||
@ -470,6 +473,7 @@ public:
|
||||
int maxRedirectsAllowed;
|
||||
QString peerVerifyName;
|
||||
#if QT_CONFIG(http)
|
||||
QHttp1Configuration h1Configuration;
|
||||
QHttp2Configuration h2Configuration;
|
||||
qint64 decompressedSafetyCheckThreshold = 10ll * 1024ll * 1024ll;
|
||||
#endif
|
||||
@ -854,6 +858,30 @@ void QNetworkRequest::setPeerVerifyName(const QString &peerName)
|
||||
}
|
||||
|
||||
#if QT_CONFIG(http) || defined(Q_QDOC)
|
||||
/*!
|
||||
\since 6.5
|
||||
|
||||
Returns the current parameters that QNetworkAccessManager is
|
||||
using for the underlying HTTP/1 connection of this request.
|
||||
|
||||
\sa setHttp1Configuration
|
||||
*/
|
||||
QHttp1Configuration QNetworkRequest::http1Configuration() const
|
||||
{
|
||||
return d->h1Configuration;
|
||||
}
|
||||
/*!
|
||||
\since 6.5
|
||||
|
||||
Sets request's HTTP/1 parameters from \a configuration.
|
||||
|
||||
\sa http1Configuration, QNetworkAccessManager, QHttp1Configuration
|
||||
*/
|
||||
void QNetworkRequest::setHttp1Configuration(const QHttp1Configuration &configuration)
|
||||
{
|
||||
d->h1Configuration = configuration;
|
||||
}
|
||||
|
||||
/*!
|
||||
\since 5.14
|
||||
|
||||
|
@ -14,6 +14,7 @@ QT_BEGIN_NAMESPACE
|
||||
|
||||
class QSslConfiguration;
|
||||
class QHttp2Configuration;
|
||||
class QHttp1Configuration;
|
||||
|
||||
class QNetworkRequestPrivate;
|
||||
class Q_NETWORK_EXPORT QNetworkRequest
|
||||
@ -144,6 +145,9 @@ public:
|
||||
QString peerVerifyName() const;
|
||||
void setPeerVerifyName(const QString &peerName);
|
||||
#if QT_CONFIG(http) || defined(Q_QDOC)
|
||||
QHttp1Configuration http1Configuration() const;
|
||||
void setHttp1Configuration(const QHttp1Configuration &configuration);
|
||||
|
||||
QHttp2Configuration http2Configuration() const;
|
||||
void setHttp2Configuration(const QHttp2Configuration &configuration);
|
||||
|
||||
|
@ -44,6 +44,7 @@
|
||||
#include <QtNetwork/qnetworkdiskcache.h>
|
||||
#include <QtNetwork/qnetworkrequest.h>
|
||||
#include <QtNetwork/qnetworkreply.h>
|
||||
#include <QtNetwork/QHttp1Configuration>
|
||||
#include <QtNetwork/qnetworkcookie.h>
|
||||
#include <QtNetwork/QNetworkCookieJar>
|
||||
#include <QtNetwork/QHttpPart>
|
||||
@ -65,6 +66,7 @@ Q_DECLARE_METATYPE(QSharedPointer<char>)
|
||||
#endif
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
# include <sys/types.h>
|
||||
@ -445,6 +447,7 @@ private Q_SLOTS:
|
||||
void varyingCacheExpiry_data();
|
||||
void varyingCacheExpiry();
|
||||
|
||||
void amountOfHttp1ConnectionsQtbug25280_data();
|
||||
void amountOfHttp1ConnectionsQtbug25280();
|
||||
|
||||
void dontInsertPartialContentIntoTheCache();
|
||||
@ -8120,10 +8123,18 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
void tst_QNetworkReply::amountOfHttp1ConnectionsQtbug25280_data()
|
||||
{
|
||||
QTest::addColumn<int>("amount");
|
||||
QTest::addRow("default") << 6;
|
||||
QTest::addRow("minimize") << 1;
|
||||
QTest::addRow("increase") << 12;
|
||||
}
|
||||
|
||||
// Also kind of QTBUG-8468
|
||||
void tst_QNetworkReply::amountOfHttp1ConnectionsQtbug25280()
|
||||
{
|
||||
const int amount = 6;
|
||||
QFETCH(const int, amount);
|
||||
QNetworkAccessManager manager; // function local instance
|
||||
Qtbug25280Server server(tst_QNetworkReply::httpEmpty200Response);
|
||||
server.doClose = false;
|
||||
@ -8131,11 +8142,16 @@ void tst_QNetworkReply::amountOfHttp1ConnectionsQtbug25280()
|
||||
QUrl url(QLatin1String("http://127.0.0.1")); // not "localhost" to prevent "Happy Eyeballs"
|
||||
// from skewing the counting
|
||||
url.setPort(server.serverPort());
|
||||
std::optional<QHttp1Configuration> http1Configuration;
|
||||
if (amount != 6) // don't set if it's the default
|
||||
http1Configuration.emplace().setNumberOfConnectionsPerHost(amount);
|
||||
constexpr int NumRequests = 200; // send a lot more than we have sockets
|
||||
int finished = 0;
|
||||
std::array<std::unique_ptr<QNetworkReply>, NumRequests> replies;
|
||||
for (auto &reply : replies) {
|
||||
QNetworkRequest request(url);
|
||||
if (http1Configuration)
|
||||
request.setHttp1Configuration(*http1Configuration);
|
||||
reply.reset(manager.get(request));
|
||||
QObject::connect(reply.get(), &QNetworkReply::finished,
|
||||
[&finished] { ++finished; });
|
||||
|
Loading…
Reference in New Issue
Block a user