TsgcWebTransportStream repräsentiert einen bidirektionalen oder unidirektionalen Stream innerhalb einer WebTransport-Sitzung. Über ihn laufen sämtliche Anwendungsdaten.
Eine WebTransport-Sitzung trägt keine eigene Payload. Alles, was Sie senden und empfangen, läuft über einen Stream, und TsgcWebTransportStream ist dieser Stream. Sie umschließt einen echten QUIC-Stream, sodass Streams innerhalb einer Sitzung unabhängig sind: Ein Stocken oder ein Verlust auf einem hält die anderen nicht auf, was genau die Freiheit vom Head-of-Line-Blocking ist, für die es QUIC gibt. Die Klasse ist in der Unit sgcWebTransport deklariert und ist, wie TsgcWebTransportSession, eine einfache Klasse und keine Komponente. Sie erstellen selbst nie eine. Sie erreicht Sie auf eine von zwei Arten:
Session.OpenStream gibt einen schreibbereiten Stream zurück.aStream des OnStream-Ereignisses der Sitzung ein.
In beiden Fällen besitzt die Session den Stream und gibt ihn frei. Geben Sie ihn nicht selbst frei.
Unidirectional wird beim Öffnen des Streams festgelegt und gibt an, welche Art Sie vor sich haben:
| Art | Header auf der Leitung | Behaviour |
|---|---|---|
Bidirektional (Unidirectional = False) | varint(0x41) + varint(SessionId) | Beide Seiten können schreiben. Die natürliche Wahl für Anfrage und Antwort oder für ein Echo. |
Unidirectional (Unidirectional = True) | varint(0x54) + varint(SessionId) | Nur die öffnende Seite kann schreiben. Die Kopie des Empfängers ist ausschließlich zum Empfangen bestimmt, ein Schreibvorgang darauf bewirkt daher nichts Sinnvolles. |
Dieser Header wird einmal von OpenStream geschrieben, bevor Sie den Stream erhalten. Alles, was Sie an WriteData übergeben, ist reine Anwendungs-Payload, und alles, was OnData Ihnen liefert, ist reine Anwendungs-Payload. Sie sehen das Framing nie.
Dies ist die mit Abstand häufigste Fehlerquelle. WriteData und OnData bewahren keine Nachrichtengrenzen. QUIC liefert einen Byte-Strom, und ein WriteData kann als mehrere OnData-Aufrufe erscheinen, oder mehrere Schreibvorgänge treffen zu einem zusammengefasst ein. Wenn Ihr Protokoll Nachrichten kennt, rahmen Sie diese selbst ein, mit einem Längenpräfix, einem Trennzeichen oder Datensätzen fester Größe. Die string-Überladung von WriteData kodiert nach UTF-8, und kurze Test-Payloads treffen üblicherweise in einem Stück ein, wodurch sich diese Falle leicht übersehen lässt, bis der Datenverkehr real wird.
Close markiert den Stream als geschlossen, meldet ihn bei seiner Sitzung ab und löst OnClose aus, sodass StreamCount sinkt. Es sendet kein QUIC-FIN und teilt dem Peer nichts mit. Wie das Close der Sitzung ist es abgesichert: Ein zweifacher Aufruf löst OnClose nur einmal aus.
| Name | Beschreibung |
|---|---|
| StreamId | Die QUIC-Stream-ID, die diesem Stream zugrunde liegt. |
| Unidirectional | True für einen unidirektionalen Stream, False für einen bidirektionalen. Bei der Konstruktion festgelegt. |
| Session | Die Sitzung, der dieser Stream gehört. |
| Name | Beschreibung |
|---|---|
| WriteData | Schreibt Anwendungsbytes in den Stream. Überladen für TBytes und für string (UTF-8-kodiert). |
| Close | Meldet den Stream bei seiner Sitzung ab und löst OnClose aus. Nur ein lokales Signal. |
| Name | Beschreibung |
|---|---|
| OnData | Wird ausgelöst, wenn Anwendungsbytes auf diesem Stream eintreffen. |
| OnClose | Wird ausgelöst, wenn Close auf diesem Stream aufgerufen wird. |
Ein WebTransport-Echo-Server: die Sitzung annehmen, die vom Client geöffneten Streams entgegennehmen und jede Payload auf demselben Stream zurücksenden. Dies ist die Serverseite des Roundtrips, der auf der Seite TsgcWebTransportSession gezeigt wird, sodass beide gegeneinander ausgeführt werden können.
unit FWebTransportServer;
interface
uses
Classes, SysUtils, Forms, StdCtrls, ExtCtrls,
// sgc
sgcHTTP, sgcHTTP3_Classes, sgcHTTP3_Types, sgcQUIC_Server, sgcWebTransport;
type
TfrmServer = class(TForm)
memoLog: TMemo;
btnStart: TButton;
btnStop: TButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure btnStartClick(Sender: TObject);
procedure btnStopClick(Sender: TObject);
private
FServer: TsgcHTTP3Server;
procedure OnWTSession(Sender: TObject;
const aConnection: TsgcQUICServerConnection;
const aSession: TsgcWebTransportSession; var aAccept: Boolean);
procedure OnWTStream(Sender: TObject;
const aStream: TsgcWebTransportStream);
procedure OnWTStreamData(Sender: TObject; const aData: TBytes);
procedure OnWTStreamClose(Sender: TObject);
end;
implementation
{$R *.dfm}
procedure TfrmServer.FormCreate(Sender: TObject);
begin
FServer := TsgcHTTP3Server.Create(nil);
// WebTransport is enabled ONLY when this is greater than zero. Setting it
// advertises SETTINGS_ENABLE_CONNECT_PROTOCOL and
// SETTINGS_WEBTRANSPORT_MAX_SESSIONS, and switches the connection into
// multi-stream mode.
FServer.H3Settings.Settings.WebTransportMaxSessions := 4;
FServer.OnWebTransportSession := OnWTSession;
end;
procedure TfrmServer.FormDestroy(Sender: TObject);
begin
FServer.Free; // frees every session and every stream underneath
end;
procedure TfrmServer.btnStartClick(Sender: TObject);
begin
FServer.Host := '0.0.0.0';
FServer.Port := 4433;
FServer.QUICOptions.SSLOptions.CertFile := 'server.crt';
FServer.QUICOptions.SSLOptions.KeyFile := 'server.key';
FServer.Start;
end;
procedure TfrmServer.btnStopClick(Sender: TObject);
begin
FServer.Stop;
end;
procedure TfrmServer.OnWTSession(Sender: TObject;
const aConnection: TsgcQUICServerConnection;
const aSession: TsgcWebTransportSession; var aAccept: Boolean);
begin
// aAccept arrives True, so doing nothing accepts. Set False to reject,
// and the server answers :status=404 and discards the session.
aAccept := True;
memoLog.Lines.Add(Format('session %d accepted from %s:%d',
[aSession.SessionId, aConnection.PeerIP, aConnection.PeerPort]));
// Assign OnStream BEFORE returning: the client may open a stream the
// moment it sees our :status=200.
aSession.OnStream := OnWTStream;
end;
procedure TfrmServer.OnWTStream(Sender: TObject;
const aStream: TsgcWebTransportStream);
begin
// Assign OnData FIRST. Bytes that arrived with the stream header are
// delivered as soon as this handler returns, and are lost otherwise.
aStream.OnData := OnWTStreamData;
aStream.OnClose := OnWTStreamClose;
memoLog.Lines.Add(Format('stream %d opened by peer (unidirectional=%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(Format('stream %d received %d bytes: %s',
[oStream.StreamId, Length(aData), TEncoding.UTF8.GetString(aData)]));
// ECHO. Only a bidirectional stream can be written back on: a peer-opened
// unidirectional stream is receive-only at this end.
if not oStream.Unidirectional then
begin
oStream.WriteData(aData);
memoLog.Lines.Add('echoed ' + IntToStr(Length(aData)) + ' bytes');
end;
end;
procedure TfrmServer.OnWTStreamClose(Sender: TObject);
begin
memoLog.Lines.Add('stream closed');
end;
end.
sgcVer.inc ist das HTTP/3-Feature-Pack SGC_PACK_HTTP3 innerhalb des Editionsblocks All-Access (SGC_EDT_ALL) deklariert und erfordert SGC_INDY_LIB.{$DEFINE SGC_PACK_HTTP3} in sgcVer.inc, was SGC_QUIC, SGC_HTTP3 und SGC_WEBTRANSPORT definiert. Die gesamte Klasse ist durch {$IFDEF SGC_WEBTRANSPORT} geschützt.sgcWebTransport deklariert die Klasse; fügen Sie sgcHTTP für TsgcHTTP3Client und TsgcHTTP3Server hinzu sowie sgcQUIC_Server für TsgcQUICServerConnection.qmBuiltin (dem nativen QUIC von OpenSSL), die beim Laden von OpenSSL automatisch erkannt wird und nicht vom Benutzer auswählbar ist.esegece.sgcWebSockets.