summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDennis Oberst <dennis.oberst@qt.io>2025-12-08 13:56:11 +0100
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2025-12-14 14:56:49 +0000
commit1d353eec82dbefc6e42b4d3099c8b41a156b8ec9 (patch)
tree052c37dba1f519284a28ecfd94fa43c34aaf9da9
parent309351ed853e3693fcc5ddd61c685dc79266bdbd (diff)
QHttp2Stream: add Configuration with downloadBuffer option6.11
Especially useful for clients who don't use the PUSH_PROMISE functionality and don't require buffering the data. We add the QHttp2Stream::Configuration struct to allow customizing stream behavior. Every stream comes with a configuration and the default constructed objects represents that. The configuration is per-stream and does not change during the lifetime. Add a testcase with further extensibility in mind for further stream configurations. Task-number: QTBUG-142473 Pick-to: 6.10 Change-Id: I1e862f4996baa61f024f40516f74fc052a9b57c4 Reviewed-by: Alexey Edelev <semlanik@gmail.com> Reviewed-by: MÃ¥rten Nordheim <marten.nordheim@qt.io> (cherry picked from commit 32375be137ba6bba6f4c5d9741d3dd449ed451e4) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
-rw-r--r--src/network/access/qhttp2connection.cpp69
-rw-r--r--src/network/access/qhttp2connection_p.h24
-rw-r--r--tests/auto/network/access/qhttp2connection/tst_qhttp2connection.cpp55
3 files changed, 134 insertions, 14 deletions
diff --git a/src/network/access/qhttp2connection.cpp b/src/network/access/qhttp2connection.cpp
index 3f88318bb17..c0b07ddd652 100644
--- a/src/network/access/qhttp2connection.cpp
+++ b/src/network/access/qhttp2connection.cpp
@@ -34,8 +34,38 @@ using namespace Http2;
\sa QHttp2Connection
*/
-QHttp2Stream::QHttp2Stream(QHttp2Connection *connection, quint32 streamID) noexcept
- : QObject(connection), m_streamID(streamID)
+/*!
+ \struct QHttp2Stream::Configuration
+ \inmodule QtNetwork
+ \internal
+
+ \brief Configuration options for a QHttp2Stream.
+
+ The Configuration struct holds options that control stream behavior.
+
+ \sa QHttp2Connection::createStream()
+*/
+
+/*!
+ \variable QHttp2Stream::Configuration::useDownloadBuffer
+
+ Controls whether incoming DATA frames, from QHttp2Stream::dataReceived(),
+ are buffered. The default is \c true.
+
+ You may disable buffering for client-initiated streams when the
+ application processes DATA immediately.
+
+ Buffering must remain enabled for pushed streams. A pushed stream can
+ receive DATA before the application becomes aware of them and the buffered
+ DATA is required to deliver the pushed response.
+
+ \sa QHttp2Stream::downloadBuffer(), QHttp2Stream::takeDownloadBuffer(),
+ QHttp2Configuration::serverPushEnabled(), QHttp2Stream::dataReceived()
+*/
+
+QHttp2Stream::QHttp2Stream(QHttp2Connection *connection, quint32 streamID,
+ Configuration configuration) noexcept
+ : QObject(connection), m_streamID(streamID), m_configuration(configuration)
{
Q_ASSERT(connection);
Q_ASSERT(streamID); // stream id 0 is reserved for connection control messages
@@ -213,6 +243,12 @@ QHttp2Stream::~QHttp2Stream() noexcept {
Returns the buffer containing the data received from the remote peer.
*/
+/*!
+ \fn QHttp2Stream::Configuration QHttp2Stream::configuration() const
+
+ Returns the configuration of this stream.
+*/
+
void QHttp2Stream::finishWithError(Http2::Http2Error errorCode, const QString &message)
{
qCDebug(qHttp2ConnectionLog, "[%p] stream %u finished with error: %ls (error code: %u)",
@@ -697,7 +733,7 @@ void QHttp2Stream::handleDATA(const Frame &inboundFrame)
inboundFrame.dataSize());
if (endStream)
transitionState(StateTransition::CloseRemote);
- const auto shouldBuffer = !fragment.isEmpty();
+ const auto shouldBuffer = m_configuration.useDownloadBuffer && !fragment.isEmpty();
if (shouldBuffer) {
// Only non-empty fragments get appended!
m_downloadBuffer.append(std::move(fragment));
@@ -891,23 +927,35 @@ QHttp2Connection *QHttp2Connection::createDirectServerConnection(QIODevice *sock
}
/*!
- Creates a stream on this connection.
+ \fn QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError> QHttp2Connection::createStream()
+ Creates a stream on this connection, using the default QHttp2Stream::Configuration.
+
+//! [createStream]
Automatically picks the next available stream ID and returns a pointer to
the new stream, if possible. Otherwise returns an error.
\sa QHttp2Connection::CreateStreamError, QHttp2Stream
+//! [createStream]
+ \sa createStream(QHttp2Stream::Configuration)
+*/
+
+/*!
+ Creates a stream with \a configuration on this connection.
+
+ \include qhttp2connection.cpp createStream
*/
-QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError> QHttp2Connection::createStream()
+QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError>
+QHttp2Connection::createStream(QHttp2Stream::Configuration configuration)
{
Q_ASSERT(m_connectionType == Type::Client); // This overload is just for clients
if (m_nextStreamID > lastValidStreamID)
return { QHttp2Connection::CreateStreamError::StreamIdsExhausted };
- return createLocalStreamInternal();
+ return createLocalStreamInternal(configuration);
}
QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError>
-QHttp2Connection::createLocalStreamInternal()
+QHttp2Connection::createLocalStreamInternal(QHttp2Stream::Configuration conf)
{
if (m_goingAway)
return { QHttp2Connection::CreateStreamError::ReceivedGOAWAY };
@@ -915,7 +963,7 @@ QHttp2Connection::createLocalStreamInternal()
if (size_t(m_peerMaxConcurrentStreams) <= size_t(numActiveLocalStreams()))
return { QHttp2Connection::CreateStreamError::MaxConcurrentStreamsReached };
- if (QHttp2Stream *ptr = createStreamInternal_impl(streamID)) {
+ if (QHttp2Stream *ptr = createStreamInternal_impl(streamID, conf)) {
m_nextStreamID += 2;
return {ptr};
}
@@ -923,7 +971,8 @@ QHttp2Connection::createLocalStreamInternal()
return { QHttp2Connection::CreateStreamError::UnknownError };
}
-QHttp2Stream *QHttp2Connection::createStreamInternal_impl(quint32 streamID)
+QHttp2Stream *QHttp2Connection::createStreamInternal_impl(quint32 streamID,
+ QHttp2Stream::Configuration conf)
{
Q_ASSERT(streamID > m_lastIncomingStreamID || streamID >= m_nextStreamID);
@@ -936,7 +985,7 @@ QHttp2Stream *QHttp2Connection::createStreamInternal_impl(quint32 streamID)
if (!result.inserted)
return nullptr;
QPointer<QHttp2Stream> &stream = result.iterator.value();
- stream = new QHttp2Stream(this, streamID);
+ stream = new QHttp2Stream(this, streamID, conf);
stream->m_recvWindow = streamInitialReceiveWindowSize;
stream->m_sendWindow = streamInitialSendWindowSize;
diff --git a/src/network/access/qhttp2connection_p.h b/src/network/access/qhttp2connection_p.h
index f3f14145278..e2af7d7ab33 100644
--- a/src/network/access/qhttp2connection_p.h
+++ b/src/network/access/qhttp2connection_p.h
@@ -101,6 +101,11 @@ public:
Q_ENUM(State)
constexpr static quint8 DefaultPriority = 127;
+ struct Configuration
+ {
+ bool useDownloadBuffer = true;
+ };
+
~QHttp2Stream() noexcept;
// HTTP2 things
@@ -124,6 +129,8 @@ public:
QByteDataBuffer takeDownloadBuffer() noexcept { return std::exchange(m_downloadBuffer, {}); }
void clearDownloadBuffer() { m_downloadBuffer.clear(); }
+ Configuration configuration() const { return m_configuration; }
+
Q_SIGNALS:
void headersReceived(const HPack::HttpHeader &headers, bool endStream);
void headersUpdated();
@@ -154,7 +161,8 @@ private Q_SLOTS:
private:
friend class QHttp2Connection;
- QHttp2Stream(QHttp2Connection *connection, quint32 streamID) noexcept;
+ QHttp2Stream(QHttp2Connection *connection, quint32 streamID,
+ Configuration configuration) noexcept;
[[nodiscard]] QHttp2Connection *getConnection() const
{
@@ -201,6 +209,8 @@ private:
bool m_isReserved = false;
bool m_owningByteDevice = false;
+ const Configuration m_configuration;
+
friend tst_QHttp2Connection;
};
@@ -235,7 +245,12 @@ public:
createDirectServerConnection(QIODevice *socket, const QHttp2Configuration &config);
~QHttp2Connection();
- [[nodiscard]] QH2Expected<QHttp2Stream *, CreateStreamError> createStream();
+ [[nodiscard]] QH2Expected<QHttp2Stream *, CreateStreamError> createStream()
+ {
+ return createStream(QHttp2Stream::Configuration{});
+ }
+ [[nodiscard]] QH2Expected<QHttp2Stream *, CreateStreamError>
+ createStream(QHttp2Stream::Configuration config);
QHttp2Stream *getStream(quint32 streamId) const;
QHttp2Stream *promisedStream(const QUrl &streamKey) const
@@ -278,8 +293,9 @@ private:
friend class QHttp2Stream;
[[nodiscard]] QIODevice *getSocket() const { return qobject_cast<QIODevice *>(parent()); }
- QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError> createLocalStreamInternal();
- QHttp2Stream *createStreamInternal_impl(quint32 streamID);
+ QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError>
+ createLocalStreamInternal(QHttp2Stream::Configuration = {});
+ QHttp2Stream *createStreamInternal_impl(quint32 streamID, QHttp2Stream::Configuration = {});
bool isInvalidStream(quint32 streamID) noexcept;
bool streamWasResetLocally(quint32 streamID) noexcept;
diff --git a/tests/auto/network/access/qhttp2connection/tst_qhttp2connection.cpp b/tests/auto/network/access/qhttp2connection/tst_qhttp2connection.cpp
index 417655c31d9..22aa9d44262 100644
--- a/tests/auto/network/access/qhttp2connection/tst_qhttp2connection.cpp
+++ b/tests/auto/network/access/qhttp2connection/tst_qhttp2connection.cpp
@@ -21,6 +21,8 @@ class tst_QHttp2Connection : public QObject
private slots:
void construct();
void constructStream();
+ void streamConfiguration_data();
+ void streamConfiguration();
void testSETTINGSFrame();
void maxHeaderTableSize();
void testPING();
@@ -204,6 +206,59 @@ void tst_QHttp2Connection::constructStream()
QCOMPARE(stream->isUploadingDATA(), false);
}
+void tst_QHttp2Connection::streamConfiguration_data()
+{
+ QTest::addColumn<bool>("useDownloadBuffer");
+
+ QTest::addRow("useDownloadBuffer=true") << true;
+ QTest::addRow("useDownloadBuffer=false") << false;
+}
+
+void tst_QHttp2Connection::streamConfiguration()
+{
+ QFETCH(const bool, useDownloadBuffer);
+
+ auto [client, server] = makeFakeConnectedSockets();
+ auto *clientConnection = makeHttp2Connection(client.get(), {}, Client);
+ auto *serverConnection = makeHttp2Connection(server.get(), {}, Server);
+
+ QHttp2Stream::Configuration config;
+ config.useDownloadBuffer = useDownloadBuffer;
+
+ QHttp2Stream *clientStream = clientConnection->createStream(config).unwrap();
+ QVERIFY(clientStream);
+ QCOMPARE(clientStream->configuration().useDownloadBuffer, useDownloadBuffer);
+ QVERIFY(waitForSettingsExchange(clientConnection, serverConnection));
+
+ QSignalSpy newIncomingStreamSpy{ serverConnection, &QHttp2Connection::newIncomingStream };
+ QSignalSpy clientDataReceivedSpy{ clientStream, &QHttp2Stream::dataReceived };
+
+ HPack::HttpHeader headers = getRequiredHeaders();
+ clientStream->sendHEADERS(headers, false);
+
+ QVERIFY(newIncomingStreamSpy.wait());
+ auto *serverStream = newIncomingStreamSpy.front().front().value<QHttp2Stream *>();
+ QVERIFY(serverStream);
+
+ const HPack::HttpHeader responseHeaders{ { ":status", "200" } };
+ serverStream->sendHEADERS(responseHeaders, false);
+
+ const QByteArray testData = "Hello World"_ba.repeated(100);
+ serverStream->sendDATA(testData, true);
+
+ QVERIFY(clientDataReceivedSpy.wait());
+ QCOMPARE(clientDataReceivedSpy.count(), 1);
+
+ const QByteArray receivedData = clientDataReceivedSpy.front().front().value<QByteArray>();
+ QCOMPARE(receivedData, testData);
+
+ if (useDownloadBuffer) {
+ QCOMPARE(clientStream->downloadBuffer().byteAmount(), testData.size());
+ } else {
+ QVERIFY(clientStream->downloadBuffer().isEmpty());
+ }
+}
+
void tst_QHttp2Connection::testSETTINGSFrame()
{
constexpr qint32 PrefaceLength = 24;