Skip to content

Commit

Permalink
Network: Add maxConcurrentStreams setting to QHttp2Configuration
Browse files Browse the repository at this point in the history
Task-number: QTBUG-75087
Change-Id: I0e42aad6e4e1f534a1070835d1b3cd37486663de
Reviewed-by: Mårten Nordheim <[email protected]>
  • Loading branch information
Lena Biliaieva committed Oct 31, 2024
1 parent 9804741 commit 1b393ca
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 8 deletions.
30 changes: 29 additions & 1 deletion src/network/access/qhttp2configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ class QHttp2ConfigurationPrivate : public QSharedData

unsigned maxFrameSize = Http2::minPayloadLimit; // Initial (default) value of 16Kb.

unsigned maxConcurrentStreams = Http2::maxConcurrentStreams;

bool pushEnabled = false;
// TODO: for now those two below are noop.
bool huffmanCompressionEnabled = true;
Expand Down Expand Up @@ -253,6 +255,31 @@ unsigned QHttp2Configuration::maxFrameSize() const
return d->maxFrameSize;
}

/*!
\since 6.9
Sets \a value as the maximum number of concurrent streams that
will be advertised to the peer when sending SETTINGS frame.
\sa maxConcurrentStreams()
*/
void QHttp2Configuration::setMaxConcurrentStreams(unsigned value)
{
d->maxConcurrentStreams = value;
}

/*!
\since 6.9
Returns the maximum number of concurrent streams.
\sa setMaxConcurrentStreams()
*/
unsigned QHttp2Configuration::maxConcurrentStreams() const
{
return d->maxConcurrentStreams;
}

/*!
Swaps this configuration with the \a other configuration.
*/
Expand Down Expand Up @@ -284,7 +311,8 @@ bool QHttp2Configuration::isEqual(const QHttp2Configuration &other) const noexce
return d->pushEnabled == other.d->pushEnabled
&& d->huffmanCompressionEnabled == other.d->huffmanCompressionEnabled
&& d->sessionWindowSize == other.d->sessionWindowSize
&& d->streamWindowSize == other.d->streamWindowSize;
&& d->streamWindowSize == other.d->streamWindowSize
&& d->maxConcurrentStreams == other.d->maxConcurrentStreams;
}

QT_END_NAMESPACE
3 changes: 3 additions & 0 deletions src/network/access/qhttp2configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ class Q_NETWORK_EXPORT QHttp2Configuration
bool setMaxFrameSize(unsigned size);
unsigned maxFrameSize() const;

void setMaxConcurrentStreams(unsigned value);
unsigned maxConcurrentStreams() const;

void swap(QHttp2Configuration &other) noexcept;

private:
Expand Down
28 changes: 23 additions & 5 deletions src/network/access/qhttp2connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -819,7 +819,7 @@ QHttp2Connection *QHttp2Connection::createUpgradedConnection(QIODevice *socket,
connection->m_connectionType = QHttp2Connection::Type::Client;
// HTTP2 connection is already established and request was sent, so stream 1
// is already 'active' and is closed for any further outgoing data.
QHttp2Stream *stream = connection->createStreamInternal().unwrap();
QHttp2Stream *stream = connection->createLocalStreamInternal().unwrap();
Q_ASSERT(stream->streamID() == 1);
stream->setState(QHttp2Stream::State::HalfClosedLocal);
connection->m_upgradedConnection = true;
Expand Down Expand Up @@ -885,16 +885,16 @@ QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError> QHttp2Connectio
Q_ASSERT(m_connectionType == Type::Client); // This overload is just for clients
if (m_nextStreamID > lastValidStreamID)
return { QHttp2Connection::CreateStreamError::StreamIdsExhausted };
return createStreamInternal();
return createLocalStreamInternal();
}

QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError>
QHttp2Connection::createStreamInternal()
QHttp2Connection::createLocalStreamInternal()
{
if (m_goingAway)
return { QHttp2Connection::CreateStreamError::ReceivedGOAWAY };
const quint32 streamID = m_nextStreamID;
if (size_t(m_maxConcurrentStreams) <= size_t(numActiveLocalStreams()))
if (size_t(m_peerMaxConcurrentStreams) <= size_t(numActiveLocalStreams()))
return { QHttp2Connection::CreateStreamError::MaxConcurrentStreamsReached };

if (QHttp2Stream *ptr = createStreamInternal_impl(streamID)) {
Expand Down Expand Up @@ -1217,6 +1217,7 @@ void QHttp2Connection::setH2Configuration(QHttp2Configuration config)
maxSessionReceiveWindowSize = qint32(m_config.sessionReceiveWindowSize());
pushPromiseEnabled = m_config.serverPushEnabled();
streamInitialReceiveWindowSize = qint32(m_config.streamReceiveWindowSize());
m_maxConcurrentStreams = m_config.maxConcurrentStreams();
encoder.setCompressStrings(m_config.huffmanCompressionEnabled());
}

Expand Down Expand Up @@ -1413,9 +1414,19 @@ void QHttp2Connection::handleHEADERS()
const bool isRemotelyInitiatedStream = isClient ^ isClientInitiatedStream;

if (isRemotelyInitiatedStream && streamID > m_lastIncomingStreamID) {
bool streamCountIsOk = size_t(m_maxConcurrentStreams) > size_t(numActiveRemoteStreams());
QHttp2Stream *newStream = createStreamInternal_impl(streamID);
Q_ASSERT(newStream);
m_lastIncomingStreamID = streamID;

if (!streamCountIsOk) {
newStream->setState(QHttp2Stream::State::Open);
newStream->streamError(PROTOCOL_ERROR, QLatin1String("Max concurrent streams reached"));

emit incomingStreamErrorOccured(CreateStreamError::MaxConcurrentStreamsReached);
return;
}

qCDebug(qHttp2ConnectionLog, "[%p] Created new incoming stream %d", this, streamID);
emit newIncomingStream(newStream);
} else if (auto it = m_streams.constFind(streamID); it == m_streams.cend()) {
Expand Down Expand Up @@ -1627,6 +1638,7 @@ void QHttp2Connection::handlePUSH_PROMISE()
if ((reservedID & 1) || reservedID <= m_lastIncomingStreamID || reservedID > lastValidStreamID)
return connectionError(PROTOCOL_ERROR, "PUSH_PROMISE with invalid promised stream ID");

bool streamCountIsOk = size_t(m_maxConcurrentStreams) > size_t(numActiveRemoteStreams());
// RFC 9113, 6.6: A receiver MUST treat the receipt of a PUSH_PROMISE that promises an
// illegal stream identifier (Section 5.1.1) as a connection error
auto *stream = createStreamInternal_impl(reservedID);
Expand All @@ -1635,6 +1647,12 @@ void QHttp2Connection::handlePUSH_PROMISE()
m_lastIncomingStreamID = reservedID;
stream->setState(QHttp2Stream::State::ReservedRemote);

if (!streamCountIsOk) {
stream->streamError(PROTOCOL_ERROR, QLatin1String("Max concurrent streams reached"));
emit incomingStreamErrorOccured(CreateStreamError::MaxConcurrentStreamsReached);
return;
}

// "ignoring a PUSH_PROMISE frame causes the stream state to become
// indeterminate" - let's send RST_STREAM frame with REFUSE_STREAM code.
if (!pushPromiseEnabled) {
Expand Down Expand Up @@ -1955,7 +1973,7 @@ bool QHttp2Connection::acceptSetting(Http2::Settings identifier, quint32 newValu
case Settings::MAX_CONCURRENT_STREAMS_ID: {
qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS MAX_CONCURRENT_STREAMS %d", this,
newValue);
m_maxConcurrentStreams = newValue;
m_peerMaxConcurrentStreams = newValue;
break;
}
case Settings::MAX_FRAME_SIZE_ID: {
Expand Down
12 changes: 10 additions & 2 deletions src/network/access/qhttp2connection_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,8 @@ class Q_NETWORK_EXPORT QHttp2Connection : public QObject
bool isGoingAway() const noexcept { return m_goingAway; }

quint32 maxConcurrentStreams() const noexcept { return m_maxConcurrentStreams; }
quint32 peerMaxConcurrentStreams() const noexcept { return m_peerMaxConcurrentStreams; }

quint32 maxHeaderListSize() const noexcept { return m_maxHeaderListSize; }

bool isUpgradedConnection() const noexcept { return m_upgradedConnection; }
Expand All @@ -263,6 +265,8 @@ class Q_NETWORK_EXPORT QHttp2Connection : public QObject
void errorOccurred(Http2::Http2Error errorCode, const QString &errorString);
void receivedGOAWAY(Http2::Http2Error errorCode, quint32 lastStreamID);
void receivedEND_STREAM(quint32 streamID);
void incomingStreamErrorOccured(CreateStreamError error);

public Q_SLOTS:
bool sendPing();
bool sendPing(QByteArrayView data);
Expand All @@ -273,7 +277,7 @@ public Q_SLOTS:
friend class QHttp2Stream;
[[nodiscard]] QIODevice *getSocket() const { return qobject_cast<QIODevice *>(parent()); }

QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError> createStreamInternal();
QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError> createLocalStreamInternal();
QHttp2Stream *createStreamInternal_impl(quint32 streamID);

bool isInvalidStream(quint32 streamID) noexcept;
Expand Down Expand Up @@ -351,11 +355,15 @@ public Q_SLOTS:

// This is how many concurrent streams our peer allows us, 100 is the
// initial value, can be updated by the server's SETTINGS frame(s):
quint32 m_maxConcurrentStreams = Http2::maxConcurrentStreams;
quint32 m_peerMaxConcurrentStreams = Http2::maxConcurrentStreams;
// While we allow sending SETTTINGS_MAX_CONCURRENT_STREAMS to limit our peer,
// it's just a hint and we do not actually enforce it (and we can continue
// sending requests and creating streams while maxConcurrentStreams allows).

// This is how many concurrent streams we allow our peer to create
// This value is specified in QHttp2Configuration when creating the connection
quint32 m_maxConcurrentStreams = Http2::maxConcurrentStreams;

// This is our (client-side) maximum possible receive window size, we set
// it in a ctor from QHttp2Configuration, it does not change after that.
// The default is 64Kb:
Expand Down

0 comments on commit 1b393ca

Please sign in to comment.