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 <volker.hilsheimer@qt.io>
This commit is contained in:
Timur Pocheptsov 2019-08-20 12:09:42 +02:00
parent d55712153a
commit 8d302aea33
4 changed files with 83 additions and 4 deletions

View File

@ -75,9 +75,13 @@ Frame configurationToSettingsFrame(const QHttp2Configuration &config)
builder.append(Settings::INITIAL_WINDOW_SIZE_ID);
builder.append(config.streamReceiveWindowSize());
// TODO: Max frame size; in future, if the need
// is proven, we can also set decoding table size
// and header list size. For now, defaults suffice.
if (config.maxFrameSize() != minPayloadLimit) {
builder.append(Settings::MAX_FRAME_SIZE_ID);
builder.append(config.maxFrameSize());
}
// TODO: In future, if the need is proven, we can
// also send decoding table size and header list size.
// For now, defaults suffice.
return builder.outboundFrame();
}

View File

@ -218,7 +218,7 @@ void Http2Server::sendDATA(quint32 streamID, quint32 windowSize)
quint32 bytesToSend = std::min<quint32>(windowSize, responseBody.size() - offset);
quint32 bytesSent = 0;
const quint32 frameSizeLimit(clientSetting(Settings::MAX_FRAME_SIZE_ID, Http2::maxPayloadSize));
const quint32 frameSizeLimit(clientSetting(Settings::MAX_FRAME_SIZE_ID, Http2::minPayloadLimit));
const uchar *src = reinterpret_cast<const uchar *>(responseBody.constData() + offset);
const bool last = offset + bytesToSend == quint32(responseBody.size());
@ -236,6 +236,10 @@ void Http2Server::sendDATA(quint32 streamID, quint32 windowSize)
src += chunkSize;
bytesToSend -= chunkSize;
bytesSent += chunkSize;
if (frameSizeLimit != Http2::minPayloadLimit) {
// Our test is probably interested in how many DATA frames were sent.
emit sendingData();
}
}
if (interrupted.loadAcquire())

View File

@ -128,6 +128,7 @@ Q_SIGNALS:
void receivedRequest(quint32 streamID);
void receivedData(quint32 streamID);
void windowUpdate(quint32 streamID);
void sendingData();
private slots:
void connectionEstablished();

View File

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