summaryrefslogtreecommitdiffstats
path: root/tests/auto/network/access/http2/tst_http2.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/network/access/http2/tst_http2.cpp')
-rw-r--r--tests/auto/network/access/http2/tst_http2.cpp148
1 files changed, 123 insertions, 25 deletions
diff --git a/tests/auto/network/access/http2/tst_http2.cpp b/tests/auto/network/access/http2/tst_http2.cpp
index 582a103b2e..771ddb01be 100644
--- a/tests/auto/network/access/http2/tst_http2.cpp
+++ b/tests/auto/network/access/http2/tst_http2.cpp
@@ -44,8 +44,8 @@
#endif // NO_OPENSSL
#endif // NO_SSL
-
#include <cstdlib>
+#include <string>
// At the moment our HTTP/2 imlpementation requires ALPN and this means OpenSSL.
#if !defined(QT_NO_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(OPENSSL_NO_TLSEXT)
@@ -68,6 +68,7 @@ private slots:
void multipleRequests();
void flowControlClientSide();
void flowControlServerSide();
+ void pushPromise();
protected slots:
// Slots to listen to our in-process server:
@@ -90,8 +91,8 @@ private:
// small payload.
void runEventLoop(int ms = 5000);
void stopEventLoop();
- // TODO: different parameters like client/server settings ...
- Http2Server *newServer(const Http2Settings &serverSettings);
+ Http2Server *newServer(const Http2Settings &serverSettings,
+ const Http2Settings &clientSettings = defaultClientSettings);
// Send a get or post request, depending on a payload (empty or not).
void sendRequest(int streamNumber,
QNetworkRequest::Priority priority = QNetworkRequest::NormalPriority,
@@ -105,15 +106,57 @@ private:
QTimer timer;
int nRequests = 0;
+ int nSentRequests = 0;
int windowUpdates = 0;
bool prefaceOK = false;
bool serverGotSettingsACK = false;
static const Http2Settings defaultServerSettings;
+ static const Http2Settings defaultClientSettings;
};
const Http2Settings tst_Http2::defaultServerSettings{{Http2::Settings::MAX_CONCURRENT_STREAMS_ID, 100}};
+const Http2Settings tst_Http2::defaultClientSettings{{Http2::Settings::MAX_FRAME_SIZE_ID, quint32(Http2::maxFrameSize)},
+ {Http2::Settings::ENABLE_PUSH_ID, quint32(0)}};
+
+namespace {
+
+// Our server lives/works on a different thread so we invoke its 'deleteLater'
+// instead of simple 'delete'.
+struct ServerDeleter
+{
+ static void cleanup(Http2Server *srv)
+ {
+ if (srv)
+ QMetaObject::invokeMethod(srv, "deleteLater", Qt::QueuedConnection);
+ }
+};
+
+using ServerPtr = QScopedPointer<Http2Server, ServerDeleter>;
+
+struct EnvVarGuard
+{
+ EnvVarGuard(const char *name, const QByteArray &value)
+ : varName(name),
+ prevValue(qgetenv(name))
+ {
+ Q_ASSERT(name);
+ qputenv(name, value);
+ }
+ ~EnvVarGuard()
+ {
+ if (prevValue.size())
+ qputenv(varName.c_str(), prevValue);
+ else
+ qunsetenv(varName.c_str());
+ }
+
+ const std::string varName;
+ const QByteArray prevValue;
+};
+
+} // unnamed namespace
tst_Http2::tst_Http2()
: workerThread(new QThread)
@@ -146,9 +189,9 @@ void tst_Http2::singleRequest()
serverPort = 0;
nRequests = 1;
- auto srv = newServer(defaultServerSettings);
+ ServerPtr srv(newServer(defaultServerSettings));
- QMetaObject::invokeMethod(srv, "startServer", Qt::QueuedConnection);
+ QMetaObject::invokeMethod(srv.data(), "startServer", Qt::QueuedConnection);
runEventLoop();
QVERIFY(serverPort != 0);
@@ -174,8 +217,6 @@ void tst_Http2::singleRequest()
QCOMPARE(reply->error(), QNetworkReply::NoError);
QVERIFY(reply->isFinished());
-
- QMetaObject::invokeMethod(srv, "deleteLater", Qt::QueuedConnection);
}
void tst_Http2::multipleRequests()
@@ -185,9 +226,9 @@ void tst_Http2::multipleRequests()
serverPort = 0;
nRequests = 10;
- auto srv = newServer(defaultServerSettings);
+ ServerPtr srv(newServer(defaultServerSettings));
- QMetaObject::invokeMethod(srv, "startServer", Qt::QueuedConnection);
+ QMetaObject::invokeMethod(srv.data(), "startServer", Qt::QueuedConnection);
runEventLoop();
QVERIFY(serverPort != 0);
@@ -198,8 +239,6 @@ void tst_Http2::multipleRequests()
QNetworkRequest::NormalPriority,
QNetworkRequest::LowPriority};
-
-
for (int i = 0; i < nRequests; ++i)
sendRequest(i, priorities[std::rand() % 3]);
@@ -208,8 +247,6 @@ void tst_Http2::multipleRequests()
QVERIFY(nRequests == 0);
QVERIFY(prefaceOK);
QVERIFY(serverGotSettingsACK);
-
- QMetaObject::invokeMethod(srv, "deleteLater", Qt::QueuedConnection);
}
void tst_Http2::flowControlClientSide()
@@ -230,12 +267,12 @@ void tst_Http2::flowControlClientSide()
const Http2Settings serverSettings = {{Settings::MAX_CONCURRENT_STREAMS_ID, 3}};
- auto srv = newServer(serverSettings);
+ ServerPtr srv(newServer(serverSettings));
const QByteArray respond(int(Http2::defaultSessionWindowSize * 50), 'x');
srv->setResponseBody(respond);
- QMetaObject::invokeMethod(srv, "startServer", Qt::QueuedConnection);
+ QMetaObject::invokeMethod(srv.data(), "startServer", Qt::QueuedConnection);
runEventLoop();
QVERIFY(serverPort != 0);
@@ -249,8 +286,6 @@ void tst_Http2::flowControlClientSide()
QVERIFY(prefaceOK);
QVERIFY(serverGotSettingsACK);
QVERIFY(windowUpdates > 0);
-
- QMetaObject::invokeMethod(srv, "deleteLater", Qt::QueuedConnection);
}
void tst_Http2::flowControlServerSide()
@@ -270,11 +305,11 @@ void tst_Http2::flowControlServerSide()
const Http2Settings serverSettings = {{Settings::MAX_CONCURRENT_STREAMS_ID, 7}};
- auto srv = newServer(serverSettings);
+ ServerPtr srv(newServer(serverSettings));
const QByteArray payload(int(Http2::defaultSessionWindowSize * 500), 'x');
- QMetaObject::invokeMethod(srv, "startServer", Qt::QueuedConnection);
+ QMetaObject::invokeMethod(srv.data(), "startServer", Qt::QueuedConnection);
runEventLoop();
QVERIFY(serverPort != 0);
@@ -287,9 +322,73 @@ void tst_Http2::flowControlServerSide()
QVERIFY(nRequests == 0);
QVERIFY(prefaceOK);
QVERIFY(serverGotSettingsACK);
+}
+
+void tst_Http2::pushPromise()
+{
+ // We will first send some request, the server should reply and also emulate
+ // PUSH_PROMISE sending us another response as promised.
+ using namespace Http2;
+
+ clearHTTP2State();
+
+ serverPort = 0;
+ nRequests = 1;
+
+ const EnvVarGuard env("QT_HTTP2_ENABLE_PUSH_PROMISE", "1");
+ const Http2Settings clientSettings{{Settings::MAX_FRAME_SIZE_ID, quint32(Http2::maxFrameSize)},
+ {Settings::ENABLE_PUSH_ID, quint32(1)}};
+
+ ServerPtr srv(newServer(defaultServerSettings, clientSettings));
+ srv->enablePushPromise(true, QByteArray("/script.js"));
- QMetaObject::invokeMethod(srv, "deleteLater", Qt::QueuedConnection);
- srv = nullptr;
+ QMetaObject::invokeMethod(srv.data(), "startServer", Qt::QueuedConnection);
+ runEventLoop();
+
+ QVERIFY(serverPort != 0);
+
+ const QString urlAsString((clearTextHTTP2 ? QString("http://127.0.0.1:%1/")
+ : QString("https://127.0.0.1:%1/")).arg(serverPort));
+ const QUrl requestUrl(urlAsString + "index.html");
+
+ QNetworkRequest request(requestUrl);
+ request.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, QVariant(true));
+
+ auto reply = manager.get(request);
+ connect(reply, &QNetworkReply::finished, this, &tst_Http2::replyFinished);
+ // Since we're using self-signed certificates, ignore SSL errors:
+ reply->ignoreSslErrors();
+
+ runEventLoop();
+
+ QVERIFY(nRequests == 0);
+ QVERIFY(prefaceOK);
+ QVERIFY(serverGotSettingsACK);
+
+ QCOMPARE(reply->error(), QNetworkReply::NoError);
+ QVERIFY(reply->isFinished());
+
+ // Now, the most interesting part!
+ nSentRequests = 0;
+ nRequests = 1;
+ // Create an additional request (let's say, we parsed reply and realized we
+ // need another resource):
+
+ const QUrl promisedUrl(urlAsString + "script.js");
+ QNetworkRequest promisedRequest(promisedUrl);
+ promisedRequest.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, QVariant(true));
+ reply = manager.get(promisedRequest);
+ connect(reply, &QNetworkReply::finished, this, &tst_Http2::replyFinished);
+ reply->ignoreSslErrors();
+
+ runEventLoop();
+
+ // Let's check that NO request was actually made:
+ QCOMPARE(nSentRequests, 0);
+ // Decreased by replyFinished():
+ QCOMPARE(nRequests, 0);
+ QCOMPARE(reply->error(), QNetworkReply::NoError);
+ QVERIFY(reply->isFinished());
}
void tst_Http2::serverStarted(quint16 port)
@@ -318,12 +417,10 @@ void tst_Http2::stopEventLoop()
eventLoop.quit();
}
-Http2Server *tst_Http2::newServer(const Http2Settings &serverSettings)
+Http2Server *tst_Http2::newServer(const Http2Settings &serverSettings,
+ const Http2Settings &clientSettings)
{
using namespace Http2;
- // Client's settings are fixed by qhttp2protocolhandler.
- const Http2Settings clientSettings = {{Settings::MAX_FRAME_SIZE_ID, quint32(Http2::maxFrameSize)},
- {Settings::ENABLE_PUSH_ID, quint32(0)}};
auto srv = new Http2Server(clearTextHTTP2, serverSettings, clientSettings);
using Srv = Http2Server;
@@ -397,6 +494,7 @@ void tst_Http2::decompressionFailed(quint32 streamID)
void tst_Http2::receivedRequest(quint32 streamID)
{
+ ++nSentRequests;
qDebug() << " server got a request on stream" << streamID;
Http2Server *srv = qobject_cast<Http2Server *>(sender());
Q_ASSERT(srv);