TsgcWebTransportSession › Methods › AddIncomingStream
Registers a stream the peer opened, raises OnStream, and delivers any payload that arrived with it.
function AddIncomingStream(aStreamId: Int64; aUnidirectional: Boolean;
const aData: TBytes): TsgcWebTransportStream;
| Name | Type | Description |
|---|---|---|
aStreamId | Int64 | The QUIC stream id of the peer-opened stream. Stored in the new stream's StreamId so the demultiplexer can route later data to it. |
aUnidirectional | Boolean | True when the peer opened a unidirectional stream (its header was 0x54), False for a bidirectional one (header 0x41). |
aData | const TBytes | Application bytes that arrived in the same read as the stream header, with the header already stripped. Delivered to the new stream's OnData after OnStream returns. Pass an empty array when there was no trailing payload. |
The newly created and registered TsgcWebTransportStream. The session owns it. The method never returns nil.
This is a transport integration point, not an application method. It is the inbound counterpart of OpenStream: where OpenStream creates a stream and writes its header, AddIncomingStream adopts a stream whose header has already been read and validated. The HTTP/3 client and server call it while demultiplexing the QUIC connection. Application code should handle OnStream instead, which this method raises.
The order of operations matters and is worth knowing, because it determines what is true inside your OnStream handler:
HeaderSent as True, because the peer already sent the header; the library must not write another one.OnStream, your chance to assign OnData.aData is not empty, delivers it to the stream's OnData.
Because step 5 follows step 4, a payload that arrived alongside the header is not lost: assign OnData inside OnStream and you will receive it. Fail to assign it there and those bytes are dropped silently.
The stream is created with the session-level fallback writer. For a peer-opened bidirectional stream, the HTTP/3 client and server replace it, right after this call returns, with a per-stream writer bound to the peer's QUIC stream, so an echo from OnData goes back on the same stream. Peer-opened unidirectional streams are receive-only and keep the fallback writer; writing to one is meaningless.
The method does not validate aStreamId against the session, nor check for a duplicate id. Callers are expected to have parsed and matched the varint(0x54|0x41) + varint(SessionId) header first.
This member is currently available in the Delphi/C++Builder implementation only. The .NET class has an internal AddIncomingStream(long, bool), which application code cannot call and which takes no payload argument: on the managed stack, trailing bytes are delivered by the transport's read loop instead.
// What a custom QUIC transport does after parsing the WebTransport header
// off a brand-new peer-opened QUIC stream. The shipping HTTP/3 client and
// server do exactly this; applications handle OnStream instead.
var
oSession: TsgcWebTransportSession;
oStream: TsgcWebTransportStream;
vEmpty: TBytes;
begin
// vSessionId and vPayload came from the 0x54/0x41 + SessionId header
oSession := FWebTransportSessions.FindBySessionId(vSessionId);
if not Assigned(oSession) then
Exit; // header names a session we do not have
// Register with an empty payload, so we can bind the writer BEFORE the
// data is delivered and an echo from OnData can target this stream.
SetLength(vEmpty, 0);
oStream := oSession.AddIncomingStream(vStreamId, vUnidirectional, vEmpty);
if Assigned(oStream) then
begin
if (vStreamId >= 0) and not vUnidirectional then
oStream.OnWriteBytes := DoWebTransportStreamWriteBytes;
if Length(vPayload) > 0 then
oStream.ReceiveData(vPayload);
end;
end;