summaryrefslogtreecommitdiffstats
path: root/tests/auto/network/access/http2/http2srv.cpp
diff options
context:
space:
mode:
authorTimur Pocheptsov <timur.pocheptsov@theqtcompany.com>2016-10-10 15:29:26 +0200
committerTimur Pocheptsov <timur.pocheptsov@theqtcompany.com>2016-10-21 14:27:06 +0000
commit512934f7e70592ed06a790fcb46dde1e435b488e (patch)
treef5fa91f8ce36289976e389ded3c155c45ac2ebb1 /tests/auto/network/access/http2/http2srv.cpp
parent016b5bc949b6dfb2f76db2e8b40a40e7eaee6828 (diff)
HTTP/2 - fix the handling of PUSH_PROMISE
HTTP/2 allows a server to pre-emptively send (or "push") responses (along with corresponding "promised" requests) to a client in association with a previous client-initiated request. This can be useful when the server knows the client will need to have those responses available in order to fully process the response to the original request. Server push is semantically equivalent to a server responding to a request; however, in this case, that request is also sent by the server, as a PUSH_PROMISE frame. The PUSH_PROMISE frame includes a header block that contains a complete set of request header fields that the server attributes to the request. After sending the PUSH_PROMISE frame, the server can begin delivering the pushed response as a response on a server-initiated stream that uses the promised stream identifier. This patch: - fixes the HPACK decompression of PUSH_PROMISE frames; - allows a user to enable PUSH_PROMISE; - processes and caches pushed data for promised streams; - updates auto-test - emulates a simple PUSH_PROMISE scenario. Change-Id: Ic4850863a5e3895320baac3871a723fc091b4aca Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Diffstat (limited to 'tests/auto/network/access/http2/http2srv.cpp')
-rw-r--r--tests/auto/network/access/http2/http2srv.cpp63
1 files changed, 59 insertions, 4 deletions
diff --git a/tests/auto/network/access/http2/http2srv.cpp b/tests/auto/network/access/http2/http2srv.cpp
index f919937fc3..9d68b5c798 100644
--- a/tests/auto/network/access/http2/http2srv.cpp
+++ b/tests/auto/network/access/http2/http2srv.cpp
@@ -63,6 +63,16 @@ inline bool is_valid_client_stream(quint32 streamID)
return (streamID & 0x1) && streamID <= std::numeric_limits<qint32>::max();
}
+void fill_push_header(const HttpHeader &originalRequest, HttpHeader &promisedRequest)
+{
+ for (const auto &field : originalRequest) {
+ if (field.name == QByteArray(":authority") ||
+ field.name == QByteArray(":scheme")) {
+ promisedRequest.push_back(field);
+ }
+ }
+}
+
}
Http2Server::Http2Server(bool h2c, const Http2Settings &ss, const Http2Settings &cs)
@@ -96,6 +106,12 @@ Http2Server::~Http2Server()
{
}
+void Http2Server::enablePushPromise(bool pushEnabled, const QByteArray &path)
+{
+ pushPromiseEnabled = pushEnabled;
+ pushPath = path;
+}
+
void Http2Server::setResponseBody(const QByteArray &body)
{
responseBody = body;
@@ -112,7 +128,6 @@ void Http2Server::startServer()
emit serverStarted(serverPort());
}
-
void Http2Server::sendServerSettings()
{
Q_ASSERT(socket);
@@ -206,7 +221,7 @@ void Http2Server::incomingConnection(qintptr socketDescriptor)
if (clearTextHTTP2) {
socket.reset(new QTcpSocket);
const bool set = socket->setSocketDescriptor(socketDescriptor);
- Q_UNUSED(set) Q_ASSERT(set);
+ Q_ASSERT(set);
// Stop listening:
close();
QMetaObject::invokeMethod(this, "connectionEstablished",
@@ -531,6 +546,48 @@ void Http2Server::sendResponse(quint32 streamID, bool emptyBody)
{
Q_ASSERT(activeRequests.find(streamID) != activeRequests.end());
+ const quint32 maxFrameSize(clientSetting(Settings::MAX_FRAME_SIZE_ID,
+ Http2::maxFrameSize));
+
+ if (pushPromiseEnabled) {
+ // A real server supporting PUSH_PROMISE will probably first send
+ // PUSH_PROMISE and then a normal response (to a real request),
+ // so that a client parsing this response and discovering another
+ // resource it needs, will _already_ have this additional resource
+ // in PUSH_PROMISE.
+ lastPromisedStream += 2;
+
+ writer.start(FrameType::PUSH_PROMISE, FrameFlag::END_HEADERS, streamID);
+ writer.append(lastPromisedStream);
+
+ HttpHeader pushHeader;
+ fill_push_header(activeRequests[streamID], pushHeader);
+ pushHeader.push_back(HeaderField(":method", "GET"));
+ pushHeader.push_back(HeaderField(":path", pushPath));
+
+ // Now interesting part, let's make it into 'stream':
+ activeRequests[lastPromisedStream] = pushHeader;
+
+ HPack::BitOStream ostream(writer.outboundFrame().buffer);
+ const bool result = encoder.encodeRequest(ostream, pushHeader);
+ Q_ASSERT(result);
+
+ // Well, it's not HEADERS, it's PUSH_PROMISE with ... HEADERS block.
+ // Should work.
+ writer.writeHEADERS(*socket, maxFrameSize);
+ qDebug() << "server sent a PUSH_PROMISE on" << lastPromisedStream;
+
+ if (responseBody.isEmpty())
+ responseBody = QByteArray("I PROMISE (AND PUSH) YOU ...");
+
+ // Now we send this promised data as a normal response on our reserved
+ // stream (disabling PUSH_PROMISE for the moment to avoid recursion):
+ pushPromiseEnabled = false;
+ sendResponse(lastPromisedStream, false);
+ pushPromiseEnabled = true;
+ // Now we'll continue with _normal_ response.
+ }
+
writer.start(FrameType::HEADERS, FrameFlag::END_HEADERS, streamID);
if (emptyBody)
writer.addFlag(FrameFlag::END_STREAM);
@@ -544,9 +601,7 @@ void Http2Server::sendResponse(quint32 streamID, bool emptyBody)
HPack::BitOStream ostream(writer.outboundFrame().buffer);
const bool result = encoder.encodeResponse(ostream, header);
Q_ASSERT(result);
- Q_UNUSED(result)
- const quint32 maxFrameSize(clientSetting(Settings::MAX_FRAME_SIZE_ID, Http2::maxFrameSize));
writer.writeHEADERS(*socket, maxFrameSize);
if (!emptyBody) {