TsgcWebTransportSession › Events › OnStream
Fires when the peer opens a new stream inside this session. Assign OnData on the stream here, or the first bytes are lost.
property OnStream: TsgcWebTransportStreamEvent read FOnStream write FOnStream;
// event type
TsgcWebTransportStreamEvent = procedure(Sender: TObject;
const aStream: TsgcWebTransportStream) of object;
// handler signature
procedure MyStream(Sender: TObject; const aStream: TsgcWebTransportStream);
| Name | Type | Description |
|---|---|---|
Sender | TObject | The TsgcWebTransportSession the stream belongs to. Same object as aStream.Session. |
aStream | const TsgcWebTransportStream | The new stream, fully registered before the event fires. StreamId, Unidirectional and Session are already set, and HeaderSent is True because the peer sent the header. It is owned by the session, so do not free it. |
Unassigned. When no handler is assigned, peer-opened streams are still registered and counted, but you never learn about them and any data they carry is discarded.
When it fires. The transport reads a brand-new QUIC stream, finds a WebTransport header on it, varint(0x54) for unidirectional or varint(0x41) for bidirectional followed by varint(SessionId), and matches that session id to this session. It then calls AddIncomingStream, which registers the stream and raises this event.
Assign OnData inside the handler. This is the important part. Bytes that arrived in the same read as the header are delivered to the stream's OnData immediately after your handler returns. If you have not assigned OnData by then, those bytes are gone, with no error. Since a peer typically writes its header and its first message together, that is the common case, not an edge case. Assign the handler first, do everything else afterwards.
Answering on the stream. For a bidirectional stream, the HTTP/3 client and server bind a per-stream writer just after registration, so aStream.WriteData replies on the same QUIC stream. Peer-opened unidirectional streams are receive-only; writing to one is meaningless, so check Unidirectional before you answer.
Assign OnStream early enough. On the server, assign it inside OnWebTransportSession, before that handler returns, since the peer may open a stream as soon as it sees the :status = 200. On the client, assign it immediately after WebTransportConnect returns and before you start pumping.
Threading. In Delphi and C++Builder the event runs on the thread that pumps the connection: the thread that calls WebTransportProcessEvents on the client, a server connection thread on the server. It is not synchronised to the main thread, so marshal UI work yourself. On .NET it runs on a thread-pool thread from the inbound accept loop.
The stream is already in the session's list when the event fires, so StreamCount includes it and FindStreamById will find it from inside your handler.
// Server: assign OnStream while accepting, before the handler returns
procedure TfrmServer.OnWTSession(Sender: TObject;
const aConnection: TsgcQUICServerConnection;
const aSession: TsgcWebTransportSession; var aAccept: Boolean);
begin
aAccept := True;
aSession.OnStream := OnWTStream; // peer may open a stream at once
end;
procedure TfrmServer.OnWTStream(Sender: TObject;
const aStream: TsgcWebTransportStream);
begin
// FIRST: hook OnData. Bytes that came with the header are delivered
// as soon as this handler returns, and would otherwise be lost.
aStream.OnData := OnWTStreamData;
aStream.OnClose := OnWTStreamClose;
memoLog.Lines.Add(Format('stream %d opened by peer (%s)',
[aStream.StreamId,
BoolToStr(aStream.Unidirectional, True)]));
end;
procedure TfrmServer.OnWTStreamData(Sender: TObject; const aData: TBytes);
var
oStream: TsgcWebTransportStream;
begin
if not (Sender is TsgcWebTransportStream) then
Exit;
oStream := TsgcWebTransportStream(Sender);
memoLog.Lines.Add('received: ' + TEncoding.UTF8.GetString(aData));
// Echo back, but only on a bidirectional stream
if not oStream.Unidirectional then
oStream.WriteData(aData);
end;