From ec62033bc25ed60a6bb9286d07e4f4485800b068 Mon Sep 17 00:00:00 2001 From: Timur Pocheptsov Date: Thu, 1 Aug 2019 15:01:27 +0200 Subject: tst_http2::connectToHost - add a fix for SecureTransport backend One of the tests above was unsetting a variable that enforces the use of a temporary keychain. We have to set it back, otherwise the test is failing. What surprises me though - why I had this problem only locally and not on CI? Apparently, SecureTransport is not covered by our configurations ... Change-Id: I0ff1e3e304632869391ed61213c245b949d8c778 Reviewed-by: Volker Hilsheimer --- tests/auto/network/access/http2/tst_http2.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'tests/auto/network/access/http2/tst_http2.cpp') diff --git a/tests/auto/network/access/http2/tst_http2.cpp b/tests/auto/network/access/http2/tst_http2.cpp index ce685a1b1c..10dad25337 100644 --- a/tests/auto/network/access/http2/tst_http2.cpp +++ b/tests/auto/network/access/http2/tst_http2.cpp @@ -591,6 +591,19 @@ void tst_Http2::connectToHost() #if QT_CONFIG(ssl) Q_ASSERT(!clearTextHTTP2 || connectionType != H2Type::h2Alpn); + +#if QT_CONFIG(securetransport) + // Normally on macOS we use plain text only for SecureTransport + // does not support ALPN on the server side. With 'direct encrytped' + // we have to use TLS sockets (== private key) and thus suppress a + // keychain UI asking for permission to use a private key. + // Our CI has this, but somebody testing locally - will have a problem. + qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", QByteArray("1")); + auto envRollback = qScopeGuard([](){ + qunsetenv("QT_SSL_USE_TEMPORARY_KEYCHAIN"); + }); +#endif // QT_CONFIG(securetransport) + #else Q_ASSERT(connectionType == H2Type::h2c || connectionType == H2Type::h2cDirect); Q_ASSERT(targetServer->isClearText()); -- cgit v1.2.3 From 36cc171b9314bf77fc84d4273dceb6264aef7134 Mon Sep 17 00:00:00 2001 From: Timur Pocheptsov Date: Tue, 6 Aug 2019 07:50:14 +0200 Subject: tst_http2::connectToHost - fix flakiness some assumptions were incorrect: our test server immediately sends its SETTINGS frame, as a result we have to reply with client preface + SETTINGS(ACK). So QVERIFY(!prefaceOK) was wrong from the beginning and was only passing by pure luck. Change-Id: Ie43f0d4ac41deb0e5339badaae6149a9b2f9d9b3 Reviewed-by: Timur Pocheptsov Reviewed-by: Volker Hilsheimer --- tests/auto/network/access/http2/tst_http2.cpp | 3 --- 1 file changed, 3 deletions(-) (limited to 'tests/auto/network/access/http2/tst_http2.cpp') diff --git a/tests/auto/network/access/http2/tst_http2.cpp b/tests/auto/network/access/http2/tst_http2.cpp index 10dad25337..4b4b8d541a 100644 --- a/tests/auto/network/access/http2/tst_http2.cpp +++ b/tests/auto/network/access/http2/tst_http2.cpp @@ -648,9 +648,6 @@ void tst_Http2::connectToHost() eventLoop.exitLoop(); QCOMPARE(reply->error(), QNetworkReply::NoError); QVERIFY(reply->isFinished()); - // Nothing must be sent yet: - QVERIFY(!prefaceOK); - QVERIFY(!serverGotSettingsACK); // Nothing received back: QVERIFY(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).isNull()); QCOMPARE(reply->readAll().size(), 0); -- cgit v1.2.3 From 8052755fd7581d70802f651d88b7af8447432d75 Mon Sep 17 00:00:00 2001 From: Timur Pocheptsov Date: Thu, 8 Aug 2019 16:12:46 +0200 Subject: 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 --- tests/auto/network/access/http2/tst_http2.cpp | 68 +++++++++++++++++---------- 1 file changed, 43 insertions(+), 25 deletions(-) (limited to 'tests/auto/network/access/http2/tst_http2.cpp') diff --git a/tests/auto/network/access/http2/tst_http2.cpp b/tests/auto/network/access/http2/tst_http2.cpp index 945ea1b448..6a0dc6db02 100644 --- a/tests/auto/network/access/http2/tst_http2.cpp +++ b/tests/auto/network/access/http2/tst_http2.cpp @@ -32,8 +32,10 @@ #include #include +#include #include #include + #include #include #include @@ -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()) -- cgit v1.2.3 From 8d302aea33d54d7930fc700ab44a55e058d7bfcc Mon Sep 17 00:00:00 2001 From: Timur Pocheptsov Date: Tue, 20 Aug 2019 12:09:42 +0200 Subject: HTTP/2: use a non-default MAX_FRAME_SIZE And send it in our 'SETTINGS' frame. Add an auto-test for this and (as a bonus) - fix a bug accidentally introduced by the previous change. Task-number: QTBUG-77412 Change-Id: I4277ff47e8d8d3b6b8666fbcd7dc73c827f349c0 Reviewed-by: Volker Hilsheimer --- tests/auto/network/access/http2/tst_http2.cpp | 70 +++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) (limited to 'tests/auto/network/access/http2/tst_http2.cpp') diff --git a/tests/auto/network/access/http2/tst_http2.cpp b/tests/auto/network/access/http2/tst_http2.cpp index 6a0dc6db02..e24a06bc34 100644 --- a/tests/auto/network/access/http2/tst_http2.cpp +++ b/tests/auto/network/access/http2/tst_http2.cpp @@ -82,6 +82,8 @@ RawSettings qt_H2ConfigurationToSettings(const QHttp2Configuration &config = qt_ RawSettings settings; settings[Http2::Settings::ENABLE_PUSH_ID] = config.serverPushEnabled(); settings[Http2::Settings::INITIAL_WINDOW_SIZE_ID] = config.streamReceiveWindowSize(); + if (config.maxFrameSize() != Http2::minPayloadLimit) + settings[Http2::Settings::MAX_FRAME_SIZE_ID] = config.maxFrameSize(); return settings; } @@ -107,6 +109,7 @@ private slots: void earlyResponse(); void connectToHost_data(); void connectToHost(); + void maxFrameSize(); protected slots: // Slots to listen to our in-process server: @@ -696,6 +699,73 @@ void tst_Http2::connectToHost() QVERIFY(reply->isFinished()); } +void tst_Http2::maxFrameSize() +{ +#if !QT_CONFIG(ssl) + QSKIP("TLS support is needed for this test"); +#endif // QT_CONFIG(ssl) + + // Here we test we send 'MAX_FRAME_SIZE' setting in our + // 'SETTINGS'. If done properly, our server will not chunk + // the payload into several DATA frames. + +#if QT_CONFIG(securetransport) + // Normally on macOS we use plain text only for SecureTransport + // does not support ALPN on the server side. With 'direct encrytped' + // we have to use TLS sockets (== private key) and thus suppress a + // keychain UI asking for permission to use a private key. + // Our CI has this, but somebody testing locally - will have a problem. + qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", QByteArray("1")); + auto envRollback = qScopeGuard([](){ + qunsetenv("QT_SSL_USE_TEMPORARY_KEYCHAIN"); + }); +#endif // QT_CONFIG(securetransport) + + auto connectionType = H2Type::h2Alpn; + auto attribute = QNetworkRequest::HTTP2AllowedAttribute; + if (clearTextHTTP2) { + connectionType = H2Type::h2Direct; + attribute = QNetworkRequest::Http2DirectAttribute; + } + + auto h2Config = qt_defaultH2Configuration(); + h2Config.setMaxFrameSize(Http2::minPayloadLimit * 3); + + serverPort = 0; + nRequests = 1; + + ServerPtr srv(newServer(defaultServerSettings, connectionType, + qt_H2ConfigurationToSettings(h2Config))); + srv->setResponseBody(QByteArray(Http2::minPayloadLimit * 2, 'q')); + QMetaObject::invokeMethod(srv.data(), "startServer", Qt::QueuedConnection); + runEventLoop(); + QVERIFY(serverPort != 0); + + const QSignalSpy frameCounter(srv.data(), &Http2Server::sendingData); + auto url = requestUrl(connectionType); + url.setPath(QString("/stream1.html")); + + QNetworkRequest request(url); + request.setAttribute(attribute, QVariant(true)); + request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("text/plain")); + request.setHttp2Configuration(h2Config); + + QNetworkReply *reply = manager->get(request); + reply->ignoreSslErrors(); + connect(reply, &QNetworkReply::finished, this, &tst_Http2::replyFinished); + + runEventLoop(); + STOP_ON_FAILURE + + // Normally, with a 16kb limit, our server would split such + // a response into 3 'DATA' frames (16kb + 16kb + 0|END_STREAM). + QCOMPARE(frameCounter.count(), 1); + + QVERIFY(nRequests == 0); + QVERIFY(prefaceOK); + QVERIFY(serverGotSettingsACK); +} + void tst_Http2::serverStarted(quint16 port) { serverPort = port; -- cgit v1.2.3