diff options
-rw-r--r-- | src/network/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/network/access/qhttp1configuration.cpp | 125 | ||||
-rw-r--r-- | src/network/access/qhttp1configuration.h | 51 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkconnection.cpp | 11 | ||||
-rw-r--r-- | src/network/access/qhttpthreaddelegate.cpp | 6 | ||||
-rw-r--r-- | src/network/access/qhttpthreaddelegate_p.h | 2 | ||||
-rw-r--r-- | src/network/access/qnetworkreplyhttpimpl.cpp | 1 | ||||
-rw-r--r-- | src/network/access/qnetworkrequest.cpp | 28 | ||||
-rw-r--r-- | src/network/access/qnetworkrequest.h | 4 | ||||
-rw-r--r-- | tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp | 18 |
10 files changed, 242 insertions, 5 deletions
diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index 48f0e737f0c..20a8280c6dd 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -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 diff --git a/src/network/access/qhttp1configuration.cpp b/src/network/access/qhttp1configuration.cpp new file mode 100644 index 00000000000..1116467dbab --- /dev/null +++ b/src/network/access/qhttp1configuration.cpp @@ -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 diff --git a/src/network/access/qhttp1configuration.h b/src/network/access/qhttp1configuration.h new file mode 100644 index 00000000000..bc67fc7232d --- /dev/null +++ b/src/network/access/qhttp1configuration.h @@ -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 diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp index 73bf8bebc38..dc9c9f610f4 100644 --- a/src/network/access/qhttpnetworkconnection.cpp +++ b/src/network/access/qhttpnetworkconnection.cpp @@ -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); } diff --git a/src/network/access/qhttpthreaddelegate.cpp b/src/network/access/qhttpthreaddelegate.cpp index 4a11b828a8a..c5c35d522a3 100644 --- a/src/network/access/qhttpthreaddelegate.cpp +++ b/src/network/access/qhttpthreaddelegate.cpp @@ -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) { diff --git a/src/network/access/qhttpthreaddelegate_p.h b/src/network/access/qhttpthreaddelegate_p.h index d16edf3b0f4..c9202b61c4a 100644 --- a/src/network/access/qhttpthreaddelegate_p.h +++ b/src/network/access/qhttpthreaddelegate_p.h @@ -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; diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp index 58e768583db..9d1fad5c696 100644 --- a/src/network/access/qnetworkreplyhttpimpl.cpp +++ b/src/network/access/qnetworkreplyhttpimpl.cpp @@ -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(); diff --git a/src/network/access/qnetworkrequest.cpp b/src/network/access/qnetworkrequest.cpp index 238ed2eddaf..6e771f6bc77 100644 --- a/src/network/access/qnetworkrequest.cpp +++ b/src/network/access/qnetworkrequest.cpp @@ -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 @@ -855,6 +859,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 Returns the current parameters that QNetworkAccessManager is diff --git a/src/network/access/qnetworkrequest.h b/src/network/access/qnetworkrequest.h index 8dd16e16d79..3476aa02736 100644 --- a/src/network/access/qnetworkrequest.h +++ b/src/network/access/qnetworkrequest.h @@ -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); diff --git a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp index 4c67317e9c9..1123b18c09a 100644 --- a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp +++ b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp @@ -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; }); |