WebTransport

WebTransport über HTTP/3 ermöglicht es beiden Peers, viele unabhängige Streams innerhalb einer einzigen QUIC-Verbindung zu öffnen, ausgehandelt mit einer einzigen Extended-CONNECT-Anfrage.

Einführung

WebTransport ist ein Transportprotokoll, das auf HTTP/3 (RFC 9114) aufsetzt und durch draft-ietf-webtrans-http3 spezifiziert wird. sgcWebSockets implementiert draft-09. Ein Client öffnet eine Sitzung mit einer einzigen Extended CONNECT-Anfrage, die den Pseudo-Header :protocol = webtransport trägt. Sobald der Server mit :status = 200 antwortet, dürfen beide Seiten beliebig viele bidirektionale und unidirektionale Streams innerhalb dieser Sitzung ohne weitere Roundtrips öffnen. Da HTTP/3 über QUIC läuft, sind diese Streams unabhängig und frei von Head-of-Line-Blocking.

Betrachten Sie es als das Low-Level-Geschwister von WebSockets. Während ein WebSocket Ihnen einen geordneten Nachrichtenkanal bietet, gibt Ihnen eine WebTransport-Sitzung eine ganze Verbindung voller Kanäle, die Sie nach Belieben öffnen und schließen.

Extended CONNECT

WebTransport setzt auf Extended CONNECT auf, das für HTTP/2 in RFC 8441 definiert und durch RFC 9220 auf HTTP/3 übertragen wurde. Eine normale CONNECT-Anfrage hat keinen Pseudo-Header :protocol und kein :path. Ein Extended CONNECT fügt beide hinzu, sodass eine einzelne Anfrage ein beliebiges Protokoll auf einem einzigen Anfrage-Stream in Gang setzen kann. Für WebTransport sieht die Anfrage so aus:

:method    = CONNECT
:protocol  = webtransport
:scheme    = https
:authority = example.com
:path      = /echo

Der Anfrage-Stream bleibt für die Lebensdauer der Sitzung geöffnet und wird nie für HTTP-Body-Daten verwendet. Seine QUIC-Stream-ID wird zur Session-ID.

Die SETTINGS, die es aktivieren

Ein Peer muss zwei HTTP/3 SETTINGS ankündigen, bevor WebTransport verwendet werden darf:

EinstellungBedeutung
SETTINGS_ENABLE_CONNECT_PROTOCOLRFC 9220. Der Peer akzeptiert den Pseudo-Header :protocol, daher ist Extended CONNECT auf dieser Verbindung zulässig.
SETTINGS_WEBTRANSPORT_MAX_SESSIONSdraft-ietf-webtrans-http3 Abschnitt 3.1. Die Anzahl gleichzeitiger WebTransport-Sitzungen, die der Peer akzeptiert. Ein Wert von 0 bedeutet, dass WebTransport nicht angeboten wird.

Beim TsgcHTTP3Server bewirkt das Setzen von H3Settings.Settings.WebTransportMaxSessions auf einen Wert größer als null, dass beide Einstellungen automatisch angekündigt werden und die Verbindung in den Multi-Stream-Modus wechselt. Beim TsgcHTTP3Client gibt ServerSupportsWebTransport True zurück, sobald der SETTINGS-Frame des Peers gelesen wurde und beide Werte enthielt.

Das Objektmodell

Zwei Klassen modellieren eine Sitzung, und beide befinden sich in der Unit sgcWebTransport:

Jeder WebTransport-Stream ist ein echter QUIC-Stream mit einem kleinen vorangestellten Header, der ihn an eine Sitzung bindet. Ein unidirektionaler Stream beginnt mit varint(0x54) + varint(SessionId), ein bidirektionaler mit varint(0x41) + varint(SessionId). Die Bibliothek schreibt und parst diesen Header für Sie: WriteData überträgt nur Anwendungsbytes, und OnData liefert nur Anwendungsbytes.

Server-Ablauf

Aktivieren Sie WebTransport, indem Sie H3Settings.Settings.WebTransportMaxSessions auf einen Wert über null setzen, und behandeln Sie dann OnWebTransportSession. Der Handler erhält eine aktive Sitzung und ein Annahme-Flag, var aAccept in Delphi und C++Builder, e.Accept in .NET. Es kommt auf jedem Stack als True an, nichts zu tun nimmt die Sitzung also an; setzen Sie es auf False, um sie abzulehnen, der Server antwortet dann mit :status = 404 und verwirft die Sitzung. Bei Annahme antwortet der Server mit :status = 200, hält den CONNECT-Stream offen und registriert die Sitzung.

Weisen Sie OnStream innerhalb des Handlers zu, bevor Sie zurückkehren, da vom Peer geöffnete Streams eintreffen können, sobald die Sitzung registriert ist.


// WebTransport is enabled only when WebTransportMaxSessions > 0
FServer := TsgcHTTP3Server.Create(nil);
FServer.H3Settings.Settings.WebTransportMaxSessions := 4;
FServer.OnWebTransportSession := OnWTSession;

procedure TfrmServer.OnWTSession(Sender: TObject;
  const aConnection: TsgcQUICServerConnection;
  const aSession: TsgcWebTransportSession; var aAccept: Boolean);
begin
  aAccept := True;   // already True on entry; set False to reject with 404
  aSession.OnStream := OnWTStream;
  aSession.OnClose := OnWTSessionClose;
end;

procedure TfrmServer.OnWTStream(Sender: TObject;
  const aStream: TsgcWebTransportStream);
begin
  aStream.OnData := OnWTStreamData;
end;

procedure TfrmServer.OnWTStreamData(Sender: TObject; const aData: TBytes);
begin
  // echo the bytes back on the same stream
  TsgcWebTransportStream(Sender).WriteData(aData);
end;

Client-Ablauf

Rufen Sie WebTransportConnect mit der URL der Sitzung auf. Es öffnet eine dedizierte QUIC-Verbindung, sendet das Extended CONNECT, wartet auf die durch ConnectTimeout begrenzte Antwort und gibt bei :status = 200 die Sitzung zurück, andernfalls nil. Ein vorheriger Aufruf von Connect ist nicht erforderlich: Wenn der Client bereits verbunden ist, trennt er zuerst die Verbindung, denn eine WebTransport-Sitzung erfordert, dass die CONNECT-Anfrage auf dem ersten vom Client initiierten bidirektionalen Stream übertragen wird.

Die WebTransport-Verbindung ist nicht-blockierend, daher wird nichts vom Socket gelesen, solange Sie sie nicht pumpen. Rufen Sie WebTransportProcessEvents regelmäßig aus einem Timer oder einem Hintergrund-Thread auf. Ohne das werden OnData und OnStream nie ausgelöst.


FClient := TsgcHTTP3Client.Create(nil);
FClient.TLSOptions.VerifyCertificate := False;
FClient.ConnectTimeout := 10000;

// Opens the QUIC connection and performs the Extended CONNECT. Returns nil if
// the server rejects the session or does not support WebTransport.
FSession := FClient.WebTransportConnect('https://127.0.0.1:4433/echo');
if Assigned(FSession) then
begin
  FStream := FSession.OpenStream(False);   // False = bidirectional
  FStream.OnData := OnStreamData;
  FStream.WriteData('Hello WebTransport');
  tmrPump.Enabled := True;
end;

procedure TfrmClient.tmrPumpTimer(Sender: TObject);
begin
  // Pump the QUIC connection so incoming stream data is harvested
  FClient.WebTransportProcessEvents;
end;

Datagramme: aktueller Status

WebTransport bietet gemäß Spezifikation auch unzuverlässige Datagramme, und diese Bibliothek stellt die API dafür bereit: SendDatagram an der Sitzung und das Ereignis OnDatagram.

Im Auslieferungszustand unterstützt WebTransport in sgcWebSockets nur Streams. Datagramme gelangen nicht auf die Leitung und treffen auch nicht von dort ein. Das gilt auf dem Client, auf dem Server, in Delphi, in C++Builder und unter .NET. Streams funktionieren vollständig; Datagramme funktionieren überhaupt nicht. Der ausgehende Aufruf schlägt laut statt still fehl, das ist die eine Sache, die sich geändert hat. Konkret:

Warum, und auf welcher Engine. sgcWebSockets besitzt zwei QUIC-Engines, TsgcQUICMode = (qmCustom, qmBuiltin), die automatisch gewählt werden, wenn OpenSSL geladen wird, und die von der Anwendung nicht auswählbar sind. qmCustom ist der Pascal-QUIC-Stack, der verwendet wird, wenn die geladene Bibliothek die quictls-QUIC-TLS-API bereitstellt. qmBuiltin ist OpenSSLs eigenes natives QUIC, das andernfalls verwendet wird.

WebTransport läuft in jeder ausgelieferten Konfiguration auf qmBuiltin. Das mitgelieferte OpenSSL 3.5 exportiert OSSL_QUIC_client_method, aber nicht die QUIC-TLS-Einstiegspunkte von quictls, daher wird qmBuiltin ausgewählt. Es ist auch die einzige praktikable Wahl: qmCustom hat keine Serverunterstützung und kann die zusätzlichen QUIC-Streams, die eine WebTransport-Sitzung benötigt, nicht öffnen.

Und qmBuiltin ist genau die Engine ohne Datagramm-Pfad. WebTransport-Datagramme benötigen die QUIC-DATAGRAM-Erweiterung (RFC 9221) unterhalb der HTTP-Datagramm-Schicht (RFC 9297). Das native QUIC von OpenSSL implementiert RFC 9221 nicht, und seine Serverseite stellt keinen DATAGRAM-Schreibpfad bereit, es gibt also keine Stelle, an die die Bytes geschrieben werden könnten. Sie stattdessen auf einen zuverlässigen Stream zu schreiben, würde das HTTP/3-Framing beschädigen, daher verweigert die Bibliothek den Aufruf, anstatt die Verbindung zu zerstören. Unter .NET ist die Ursache dieselbe in anderer Gestalt: System.Net.Quic stellt keine rohen QUIC-Datagramme bereit.

Dennoch kündigt der Server weiterhin SETTINGS_H3_DATAGRAM an. Das Aktivieren von WebTransport veranlasst den Server, es zusammen mit SETTINGS_ENABLE_CONNECT_PROTOCOL und SETTINGS_WEBTRANSPORT_MAX_SESSIONS anzukündigen, obwohl kein Datagramm transportiert werden kann. Das ist beabsichtigt und keine veraltete Einstellung. Der Entwurf WebTransport over HTTP/3 verlangt die Einstellung, und Browser weigern sich, ohne sie eine Sitzung aufzubauen, daher wird sie aus Gründen der Interoperabilität angekündigt: Ohne sie wäre die Stream-Hälfte von WebTransport, die Hälfte, die funktioniert, aus einem Browser nicht erreichbar.

Was vorhanden ist. Der vollständige Kodier- und Demultiplex-Pfad in sgcWebTransport ist implementiert und korrekt: Kodierung und Dekodierung der Quarter-Stream-ID, die Suche der Sitzung über die Quarter-Stream-ID und die Zustellung der Payload. Ein datagrammfähiges QUIC-Backend kann Datagramme aktivieren, indem es einen echten Writer bereitstellt (durch Zuweisen von OnSendDatagramData oder durch Überschreiben des virtuellen TsgcHTTP3Server.SendDatagram), für jede Sitzung DatagramsSupported := True setzt, damit der Encoder läuft, statt eine Ausnahme auszulösen, und beim Empfang ProcessWebTransportDatagram aufruft. Solange ein solches Backend nicht existiert, betrachten Sie die Datagramm-Unterstützung als nicht vorhanden und entwerfen Sie Ihr Protokoll auf Basis von Streams.

Anforderungen

Referenz