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>
This commit is contained in:
Mårten Nordheim 2021-11-26 13:15:46 +01:00
parent 5074344c9c
commit 9909ec0bc6
6 changed files with 107 additions and 6 deletions

View File

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

View File

@ -182,6 +182,7 @@ public:
bool pipeliningAllowed;
bool http2Allowed;
bool http2Direct;
bool h2cAllowed = false;
bool withCredentials;
bool ssl;
bool preConnect;

View File

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

View File

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

View File

@ -98,6 +98,7 @@ public:
ResourceTypeAttribute, // internal
AutoDeleteReplyOnFinishAttribute,
ConnectionCacheExpiryTimeoutSecondsAttribute,
Http2CleartextAllowedAttribute,
User = 1000,
UserMax = 32767

View File

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