TsgcWebTransportSession › Events › OnOpenStream
Transport hook. Lets the transport allocate the real QUIC stream backing an OpenStream call. Already assigned by the HTTP/3 client and server.
property OnOpenStream: TsgcWebTransportOpenStreamEvent read FOnOpenStream write FOnOpenStream;
// event type
TsgcWebTransportOpenStreamEvent = procedure(Sender: TObject;
const aStream: TsgcWebTransportStream) of object;
// handler signature
procedure MyOpenStream(Sender: TObject; const aStream: TsgcWebTransportStream);
| Name | Type | Description |
|---|---|---|
Sender | TObject | The TsgcWebTransportSession that is opening the stream. |
aStream | const TsgcWebTransportStream | The freshly created stream, not yet written to and not yet in the session's list. Unidirectional is already set and tells the handler which kind of QUIC stream to allocate. StreamId is -1, waiting to be filled in. The handler is expected to set StreamId and OnWriteBytes. |
Unassigned on a bare session. Both the HTTP/3 client and the HTTP/3 server assign it before you ever see the session.
Do not assign this from application code. It is a transport hook, declared as an event only because that is how a plain Pascal class accepts a callback. TsgcHTTP3Client.WebTransportConnect and the server's session-accept path both assign it, and overwriting theirs breaks the session: streams would stop getting real QUIC streams and every write would collapse onto the session-level fallback writer. It is documented because a custom QUIC transport must supply it.
When it fires. From inside OpenStream, after the stream object exists but before its WebTransport header is written. That timing is the whole point: the handler creates the underlying QUIC stream and binds a per-stream writer, so the header, and then everything else, lands on the right stream.
What a handler must do. Two things:
aStream.Unidirectional, and assign its id to aStream.StreamId. Without this the stream keeps -1 and FindStreamById can never match it, so inbound data cannot be routed to it.aStream.OnWriteBytes to a writer that targets that specific QUIC stream.
The handler must not write to the stream, and must not add it to the session; OpenStream does both once the handler returns.
If the handler does nothing, or the event is unassigned, OpenStream still works, after a fashion. The stream keeps its default writer, which routes bytes through the session-level OnWriteData, and keeps StreamId = -1. That models a single multiplexed transport stream: usable for a trivial transport, useless for real WebTransport, where every stream must be independent. The library's own handlers always do the full job, and tolerate a QUIC stream that fails to open by leaving the stream on the fallback writer rather than raising.
This member is currently available in the Delphi/C++Builder implementation only. The .NET class has an equivalent internal OpenStreamHook property, which application code cannot assign, and which differs in one respect: the managed session assigns the stream id itself before invoking the hook, so a .NET hook only binds the writer.
// What a custom QUIC transport assigns. This mirrors the library's own
// TsgcHTTP3Client_Base.DoWebTransportOpenStream. Applications never do this.
procedure TMyTransport.WireSession(const aSession: TsgcWebTransportSession);
begin
aSession.OnOpenStream := DoOpenStream;
aSession.OnWriteData := DoWriteData;
end;
procedure TMyTransport.DoOpenStream(Sender: TObject;
const aStream: TsgcWebTransportStream);
var
oQStream: TsgcSSLQUICStream;
begin
if not Assigned(aStream) then
Exit;
// Create the real QUIC stream of the requested direction, then bind a
// per-stream writer so the 0x54/0x41 + SessionId header that OpenStream
// is about to write, and every later byte, go to THIS QUIC stream.
oQStream := GetQUICComponent.OpenStream(FHost, FPort, aStream.Unidirectional);
if Assigned(oQStream) then
begin
aStream.StreamId := oQStream.StreamId;
aStream.OnWriteBytes := DoStreamWriteBytes;
end;
// If the QUIC stream could not be opened, leave the stream on the
// session-level fallback writer rather than raising.
end;