diff options
author | Mårten Nordheim <marten.nordheim@qt.io> | 2021-11-26 13:15:46 +0100 |
---|---|---|
committer | Mårten Nordheim <marten.nordheim@qt.io> | 2021-12-04 08:20:52 +0000 |
commit | 9909ec0bc63325fc115d9d84ab01c08333fd8c53 (patch) | |
tree | 82ed9aaaddcbb11315439d51c3409b67676f3154 | |
parent | 5074344c9c1fb9a510f333d470d83e8da94072f9 (diff) |
QNAM: Reintroduce h2c with an attribute
[ChangeLog][QtNetwork][QNetworkRequest] Added
QNetworkRequest::Http2CleartextAllowedAttribute which controls whether
HTTP/2 cleartext (h2c) is allowed or not. The default is false. This
replaces the QT_NETWORK_H2C_ALLOWED environment variable.
Task-number: QTBUG-98642
Change-Id: I43ae1cc671788f6d2559cd316f6667b412c8e75e
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
-rw-r--r-- | src/network/access/qhttpnetworkrequest.cpp | 6 | ||||
-rw-r--r-- | src/network/access/qhttpnetworkrequest_p.h | 1 | ||||
-rw-r--r-- | src/network/access/qnetworkreplyhttpimpl.cpp | 6 | ||||
-rw-r--r-- | src/network/access/qnetworkrequest.cpp | 17 | ||||
-rw-r--r-- | src/network/access/qnetworkrequest.h | 1 | ||||
-rw-r--r-- | tests/auto/network/access/http2/tst_http2.cpp | 82 |
6 files changed, 107 insertions, 6 deletions
diff --git a/src/network/access/qhttpnetworkrequest.cpp b/src/network/access/qhttpnetworkrequest.cpp index f419ee2673..cfcface0d2 100644 --- a/src/network/access/qhttpnetworkrequest.cpp +++ b/src/network/access/qhttpnetworkrequest.cpp @@ -61,6 +61,7 @@ QHttpNetworkRequestPrivate::QHttpNetworkRequestPrivate(const QHttpNetworkRequest pipeliningAllowed(other.pipeliningAllowed), http2Allowed(other.http2Allowed), http2Direct(other.http2Direct), + h2cAllowed(other.h2cAllowed), withCredentials(other.withCredentials), ssl(other.ssl), preConnect(other.preConnect), @@ -85,6 +86,7 @@ bool QHttpNetworkRequestPrivate::operator==(const QHttpNetworkRequestPrivate &ot && (pipeliningAllowed == other.pipeliningAllowed) && (http2Allowed == other.http2Allowed) && (http2Direct == other.http2Direct) + && (h2cAllowed == other.h2cAllowed) // we do not clear the customVerb in setOperation && (operation != QHttpNetworkRequest::Custom || (customVerb == other.customVerb)) && (withCredentials == other.withCredentials) @@ -367,12 +369,12 @@ void QHttpNetworkRequest::setHTTP2Direct(bool b) bool QHttpNetworkRequest::isH2cAllowed() const { - return qEnvironmentVariableIsSet("QT_NETWORK_H2C_ALLOWED"); + return d->h2cAllowed; } void QHttpNetworkRequest::setH2cAllowed(bool b) { - Q_UNUSED(b); + d->h2cAllowed = b; } bool QHttpNetworkRequest::withCredentials() const diff --git a/src/network/access/qhttpnetworkrequest_p.h b/src/network/access/qhttpnetworkrequest_p.h index f4c1d7fb8c..bf6db875af 100644 --- a/src/network/access/qhttpnetworkrequest_p.h +++ b/src/network/access/qhttpnetworkrequest_p.h @@ -182,6 +182,7 @@ public: bool pipeliningAllowed; bool http2Allowed; bool http2Direct; + bool h2cAllowed = false; bool withCredentials; bool ssl; bool preConnect; diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp index 502692084a..4e06a0dc08 100644 --- a/src/network/access/qnetworkreplyhttpimpl.cpp +++ b/src/network/access/qnetworkreplyhttpimpl.cpp @@ -790,6 +790,12 @@ void QNetworkReplyHttpImplPrivate::postRequest(const QNetworkRequest &newHttpReq allowed.isValid() && allowed.canConvert<bool>()) { httpRequest.setHTTP2Allowed(allowed.value<bool>()); } + auto h2cAttribute = request.attribute(QNetworkRequest::Http2CleartextAllowedAttribute); + // ### Qt7: Stop checking the environment variable + if (h2cAttribute.toBool() + || (!h2cAttribute.isValid() && qEnvironmentVariableIsSet("QT_NETWORK_H2C_ALLOWED"))) { + httpRequest.setH2cAllowed(true); + } if (request.attribute(QNetworkRequest::Http2DirectAttribute).toBool()) { // Intentionally mutually exclusive - cannot be both direct and 'allowed' diff --git a/src/network/access/qnetworkrequest.cpp b/src/network/access/qnetworkrequest.cpp index 3e9c12ae9e..64d35aa278 100644 --- a/src/network/access/qnetworkrequest.cpp +++ b/src/network/access/qnetworkrequest.cpp @@ -272,7 +272,8 @@ QT_BEGIN_NAMESPACE Requests only, type: QMetaType::Bool (default: true) Indicates whether the QNetworkAccessManager code is allowed to use HTTP/2 with this request. This applies - to SSL requests or 'cleartext' HTTP/2. + to SSL requests or 'cleartext' HTTP/2 if Http2CleartextAllowedAttribute + is set. \value Http2WasUsedAttribute Replies only, type: QMetaType::Bool (default: false) @@ -304,8 +305,9 @@ QT_BEGIN_NAMESPACE If set, this attribute will force QNetworkAccessManager to use HTTP/2 protocol without initial HTTP/2 protocol negotiation. Use of this attribute implies prior knowledge that a particular - server supports HTTP/2. The attribute works with SSL or 'cleartext' - HTTP/2. If a server turns out to not support HTTP/2, when HTTP/2 direct + server supports HTTP/2. The attribute works with SSL or with 'cleartext' + HTTP/2 if Http2CleartextAllowedAttribute is set. + If a server turns out to not support HTTP/2, when HTTP/2 direct was specified, QNetworkAccessManager gives up, without attempting to fall back to HTTP/1.1. If both Http2AllowedAttribute and Http2DirectAttribute are set, Http2DirectAttribute takes priority. @@ -325,6 +327,15 @@ QT_BEGIN_NAMESPACE be closed after the last pending request had been processed. (This value was introduced in 6.3.) + \value Http2CleartextAllowedAttribute + Requests only, type: QMetaType::Bool (default: false) + If set, this attribute will tell QNetworkAccessManager to attempt + an upgrade to HTTP/2 over cleartext (also known as h2c). + Until Qt 7 the default value for this attribute can be overridden + to true by setting the QT_NETWORK_H2C_ALLOWED environment variable. + This attribute is ignored if the Http2AllowedAttribute is not set. + (This value was introduced in 6.3.) + \value User Special type. Additional information can be passed in QVariants with types ranging from User to UserMax. The default diff --git a/src/network/access/qnetworkrequest.h b/src/network/access/qnetworkrequest.h index bdcb1c80a9..5d5ae292c8 100644 --- a/src/network/access/qnetworkrequest.h +++ b/src/network/access/qnetworkrequest.h @@ -98,6 +98,7 @@ public: ResourceTypeAttribute, // internal AutoDeleteReplyOnFinishAttribute, ConnectionCacheExpiryTimeoutSecondsAttribute, + Http2CleartextAllowedAttribute, User = 1000, UserMax = 32767 diff --git a/tests/auto/network/access/http2/tst_http2.cpp b/tests/auto/network/access/http2/tst_http2.cpp index 4a6e34eb7b..34b7e8fa37 100644 --- a/tests/auto/network/access/http2/tst_http2.cpp +++ b/tests/auto/network/access/http2/tst_http2.cpp @@ -115,6 +115,9 @@ private slots: void authenticationRequired_data(); void authenticationRequired(); + void h2cAllowedAttribute_data(); + void h2cAllowedAttribute(); + protected slots: // Slots to listen to our in-process server: void serverStarted(quint16 port); @@ -223,7 +226,6 @@ tst_Http2::~tst_Http2() void tst_Http2::init() { manager.reset(new QNetworkAccessManager); - qputenv("QT_NETWORK_H2C_ALLOWED", "1"); } void tst_Http2::singleRequest_data() @@ -277,6 +279,7 @@ void tst_Http2::singleRequest() url.setPath("/index.html"); QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true); QFETCH(const QNetworkRequest::Attribute, h2Attribute); request.setAttribute(h2Attribute, QVariant(true)); @@ -452,6 +455,7 @@ void tst_Http2::pushPromise() url.setPath("/index.html"); QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true); request.setAttribute(QNetworkRequest::Http2AllowedAttribute, QVariant(true)); request.setHttp2Configuration(params); @@ -478,6 +482,7 @@ void tst_Http2::pushPromise() url.setPath("/script.js"); QNetworkRequest promisedRequest(url); + request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true); promisedRequest.setAttribute(QNetworkRequest::Http2AllowedAttribute, QVariant(true)); reply = manager->get(promisedRequest); connect(reply, &QNetworkReply::finished, this, &tst_Http2::replyFinished); @@ -532,6 +537,7 @@ void tst_Http2::goaway() for (int i = 0; i < nRequests; ++i) { url.setPath(QString("/%1").arg(i)); QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true); request.setAttribute(QNetworkRequest::Http2AllowedAttribute, QVariant(true)); replies[i] = manager->get(request); QCOMPARE(replies[i]->error(), QNetworkReply::NoError); @@ -673,6 +679,7 @@ void tst_Http2::connectToHost() auto copyUrl = url; copyUrl.setScheme(QLatin1String("preconnect-https")); QNetworkRequest request(copyUrl); + request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true); request.setAttribute(requestAttribute, true); reply = manager->get(request); // Since we're using self-signed certificates, ignore SSL errors: @@ -685,6 +692,7 @@ void tst_Http2::connectToHost() auto copyUrl = url; copyUrl.setScheme(QLatin1String("preconnect-http")); QNetworkRequest request(copyUrl); + request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true); request.setAttribute(requestAttribute, true); reply = manager->get(request); } @@ -705,6 +713,7 @@ void tst_Http2::connectToHost() QCOMPARE(nRequests, 1); QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true); request.setAttribute(requestAttribute, QVariant(true)); reply = manager->get(request); connect(reply, &QNetworkReply::finished, this, &tst_Http2::replyFinished); @@ -770,6 +779,7 @@ void tst_Http2::maxFrameSize() url.setPath(QString("/stream1.html")); QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true); request.setAttribute(attribute, QVariant(true)); request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("text/plain")); request.setHttp2Configuration(h2Config); @@ -916,6 +926,7 @@ void tst_Http2::moreActivitySignals() auto url = requestUrl(connectionType); url.setPath(QString("/stream1.html")); QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true); QFETCH(const QNetworkRequest::Attribute, h2Attribute); request.setAttribute(h2Attribute, QVariant(true)); request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("text/plain")); @@ -1036,6 +1047,7 @@ void tst_Http2::contentEncoding() url.setPath("/index.html"); QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true); QFETCH(const QNetworkRequest::Attribute, h2Attribute); request.setAttribute(h2Attribute, QVariant(true)); @@ -1093,6 +1105,7 @@ void tst_Http2::authenticationRequired() auto url = requestUrl(defaultConnectionType()); url.setPath("/index.html"); QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true); QByteArray expectedBody = "Hello, World!"; request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); @@ -1147,6 +1160,72 @@ void tst_Http2::authenticationRequired() QTRY_VERIFY(serverGotSettingsACK); } +void tst_Http2::h2cAllowedAttribute_data() +{ + QTest::addColumn<bool>("h2cAllowed"); + QTest::addColumn<bool>("useAttribute"); // true: use attribute, false: use environment variable + QTest::addColumn<bool>("success"); + + QTest::addRow("h2c-not-allowed") << false << false << false; + // Use the attribute to enable/disable the H2C: + QTest::addRow("attribute") << true << true << true; + // Use the QT_NETWORK_H2C_ALLOWED environment variable to enable/disable the H2C: + QTest::addRow("environment-variable") << true << false << true; +} + +void tst_Http2::h2cAllowedAttribute() +{ + QFETCH(const bool, h2cAllowed); + QFETCH(const bool, useAttribute); + QFETCH(const bool, success); + + clearHTTP2State(); + serverPort = 0; + + ServerPtr targetServer(newServer(defaultServerSettings, H2Type::h2c)); + targetServer->setResponseBody("Hello"); + + QMetaObject::invokeMethod(targetServer.data(), "startServer", Qt::QueuedConnection); + runEventLoop(); + + QVERIFY(serverPort != 0); + + nRequests = 1; + + auto url = requestUrl(H2Type::h2c); + url.setPath("/index.html"); + QNetworkRequest request(url); + if (h2cAllowed) { + if (useAttribute) + request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true); + else + qputenv("QT_NETWORK_H2C_ALLOWED", "1"); + } + auto envCleanup = qScopeGuard([]() { qunsetenv("QT_NETWORK_H2C_ALLOWED"); }); + + QScopedPointer<QNetworkReply> reply; + reply.reset(manager->get(request)); + + if (success) + connect(reply.get(), &QNetworkReply::finished, this, &tst_Http2::replyFinished); + else + connect(reply.get(), &QNetworkReply::errorOccurred, this, &tst_Http2::replyFinishedWithError); + + // Since we're using self-signed certificates, + // ignore SSL errors: + reply->ignoreSslErrors(); + + runEventLoop(); + STOP_ON_FAILURE + + if (!success) { + QCOMPARE(reply->error(), QNetworkReply::ConnectionRefusedError); + } else { + QCOMPARE(reply->readAll(), QByteArray("Hello")); + QTRY_VERIFY(serverGotSettingsACK); + } +} + void tst_Http2::serverStarted(quint16 port) { serverPort = port; @@ -1204,6 +1283,7 @@ void tst_Http2::sendRequest(int streamNumber, url.setPath(QString("/stream%1.html").arg(streamNumber)); QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true); request.setAttribute(QNetworkRequest::Http2AllowedAttribute, QVariant(true)); request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("text/plain")); |