TsgcWebTransportStream › Events › OnData
Fires when application bytes arrive on this stream. This is how you receive everything in WebTransport.
property OnData: TsgcWebTransportStreamDataEvent read FOnData write FOnData;
// event type
TsgcWebTransportStreamDataEvent = procedure(Sender: TObject;
const aData: TBytes) of object;
// handler signature
procedure MyStreamData(Sender: TObject; const aData: TBytes);
| Name | Type | Description |
|---|---|---|
Sender | TObject | The TsgcWebTransportStream the bytes arrived on. Cast it to reach StreamId, Unidirectional or Session, which is how one handler can serve many streams. |
aData | const TBytes | Application bytes only. The WebTransport stream header has already been parsed off, so this is exactly what the peer passed to its own WriteData, subject to the chunking caveat below. |
Unassigned. When no handler is assigned, inbound bytes on this stream are discarded silently.
Assign it before anything can arrive. On a peer-opened stream that means inside the session's OnStream handler, before it returns: bytes that came in the same read as the stream header are delivered the instant that handler returns, and are dropped if no handler is set. On a stream you opened, assign it before the first WriteData, since a bidirectional peer may answer immediately.
On the client, pump or you get nothing. The QUIC connection is non-blocking, so OnData only fires while TsgcHTTP3Client.WebTransportProcessEvents is being called from a timer or thread. A client that assigns OnData and never pumps sees no data at all, with no error. This does not apply to the server, or to .NET.
Message boundaries do not survive. The most important caveat here. Each call gives you whatever bytes the transport had available: one peer WriteData can surface as several OnData calls, and several can arrive coalesced into one. Do not treat one OnData as one message. If your protocol has messages, accumulate the bytes and frame them yourself. Short test payloads usually arrive whole, so this rarely shows up until the messages get bigger.
Answering. On a bidirectional stream, calling Sender's WriteData from inside the handler replies on the same stream, and that is exactly the echo pattern the demos use. On a peer-opened unidirectional stream you cannot reply: check Unidirectional first.
Threading. In Delphi and C++Builder the handler runs on the thread that pumped the data in: your timer or worker thread on the client, a connection thread on the server. Nothing is synchronised to the main thread, so marshal UI updates with TThread.Synchronize or TThread.Queue. On .NET it runs on a thread-pool thread from the stream's read loop, so use Invoke for WinForms or WPF. Keep the handler quick either way: it runs on the read path and blocking it stalls the connection.
aData is valid for the duration of the call. Copy it if you need to keep it. An empty array is possible if a transport pushes one, though the library's own callers check the length first.
// Assign as soon as you have the stream, and before you write
FStream := FSession.OpenStream(False);
FStream.OnData := OnStreamData;
FStream.WriteData('Hello WebTransport');
tmrPump.Enabled := True; // without this, OnData never fires
procedure TfrmClient.tmrPumpTimer(Sender: TObject);
begin
FClient.WebTransportProcessEvents;
end;
procedure TfrmClient.OnStreamData(Sender: TObject; const aData: TBytes);
var
oStream: TsgcWebTransportStream;
begin
if not (Sender is TsgcWebTransportStream) then
Exit;
oStream := TsgcWebTransportStream(Sender);
// Sender identifies the stream, so one handler serves them all
memoLog.Lines.Add(Format('stream %d: %d bytes: %s',
[oStream.StreamId, Length(aData), TEncoding.UTF8.GetString(aData)]));
// Echo, but only where a reply is possible
if not oStream.Unidirectional then
oStream.WriteData(aData);
end;
// Framing: OnData does NOT preserve message boundaries, so a real protocol
// accumulates bytes and cuts messages out of the buffer itself.
procedure TfrmClient.OnFramedData(Sender: TObject; const aData: TBytes);
var
vLen: Integer;
vMsg: TBytes;
begin
FBuffer := FBuffer + aData; // TBytes concatenation, D12+
while Length(FBuffer) >= 4 do
begin
vLen := (FBuffer[0] shl 24) or (FBuffer[1] shl 16) or
(FBuffer[2] shl 8) or FBuffer[3];
if Length(FBuffer) < 4 + vLen then
Break; // message not complete yet, wait for more
vMsg := Copy(FBuffer, 4, vLen);
FBuffer := Copy(FBuffer, 4 + vLen, Length(FBuffer) - 4 - vLen);
memoLog.Lines.Add('message: ' + TEncoding.UTF8.GetString(vMsg));
end;
end;