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:
parent
d55712153a
commit
8d302aea33
@ -75,9 +75,13 @@ Frame configurationToSettingsFrame(const QHttp2Configuration &config)
|
|||||||
builder.append(Settings::INITIAL_WINDOW_SIZE_ID);
|
builder.append(Settings::INITIAL_WINDOW_SIZE_ID);
|
||||||
builder.append(config.streamReceiveWindowSize());
|
builder.append(config.streamReceiveWindowSize());
|
||||||
|
|
||||||
// TODO: Max frame size; in future, if the need
|
if (config.maxFrameSize() != minPayloadLimit) {
|
||||||
// is proven, we can also set decoding table size
|
builder.append(Settings::MAX_FRAME_SIZE_ID);
|
||||||
// and header list size. For now, defaults suffice.
|
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();
|
return builder.outboundFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,7 +218,7 @@ void Http2Server::sendDATA(quint32 streamID, quint32 windowSize)
|
|||||||
|
|
||||||
quint32 bytesToSend = std::min<quint32>(windowSize, responseBody.size() - offset);
|
quint32 bytesToSend = std::min<quint32>(windowSize, responseBody.size() - offset);
|
||||||
quint32 bytesSent = 0;
|
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 uchar *src = reinterpret_cast<const uchar *>(responseBody.constData() + offset);
|
||||||
const bool last = offset + bytesToSend == quint32(responseBody.size());
|
const bool last = offset + bytesToSend == quint32(responseBody.size());
|
||||||
|
|
||||||
@ -236,6 +236,10 @@ void Http2Server::sendDATA(quint32 streamID, quint32 windowSize)
|
|||||||
src += chunkSize;
|
src += chunkSize;
|
||||||
bytesToSend -= chunkSize;
|
bytesToSend -= chunkSize;
|
||||||
bytesSent += chunkSize;
|
bytesSent += chunkSize;
|
||||||
|
if (frameSizeLimit != Http2::minPayloadLimit) {
|
||||||
|
// Our test is probably interested in how many DATA frames were sent.
|
||||||
|
emit sendingData();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (interrupted.loadAcquire())
|
if (interrupted.loadAcquire())
|
||||||
|
@ -128,6 +128,7 @@ Q_SIGNALS:
|
|||||||
void receivedRequest(quint32 streamID);
|
void receivedRequest(quint32 streamID);
|
||||||
void receivedData(quint32 streamID);
|
void receivedData(quint32 streamID);
|
||||||
void windowUpdate(quint32 streamID);
|
void windowUpdate(quint32 streamID);
|
||||||
|
void sendingData();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void connectionEstablished();
|
void connectionEstablished();
|
||||||
|
@ -82,6 +82,8 @@ RawSettings qt_H2ConfigurationToSettings(const QHttp2Configuration &config = qt_
|
|||||||
RawSettings settings;
|
RawSettings settings;
|
||||||
settings[Http2::Settings::ENABLE_PUSH_ID] = config.serverPushEnabled();
|
settings[Http2::Settings::ENABLE_PUSH_ID] = config.serverPushEnabled();
|
||||||
settings[Http2::Settings::INITIAL_WINDOW_SIZE_ID] = config.streamReceiveWindowSize();
|
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;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,6 +109,7 @@ private slots:
|
|||||||
void earlyResponse();
|
void earlyResponse();
|
||||||
void connectToHost_data();
|
void connectToHost_data();
|
||||||
void connectToHost();
|
void connectToHost();
|
||||||
|
void maxFrameSize();
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
// Slots to listen to our in-process server:
|
// Slots to listen to our in-process server:
|
||||||
@ -696,6 +699,73 @@ void tst_Http2::connectToHost()
|
|||||||
QVERIFY(reply->isFinished());
|
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)
|
void tst_Http2::serverStarted(quint16 port)
|
||||||
{
|
{
|
||||||
serverPort = port;
|
serverPort = port;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user