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.
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.
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.
Ein Peer muss zwei HTTP/3 SETTINGS ankündigen, bevor WebTransport verwendet werden darf:
| Einstellung | Bedeutung |
|---|---|
SETTINGS_ENABLE_CONNECT_PROTOCOL | RFC 9220. Der Peer akzeptiert den Pseudo-Header :protocol, daher ist Extended CONNECT auf dieser Verbindung zulässig. |
SETTINGS_WEBTRANSPORT_MAX_SESSIONS | draft-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.
Zwei Klassen modellieren eine Sitzung, und beide befinden sich in der Unit sgcWebTransport:
WebTransportConnect zurück, der Server übergibt sie an OnWebTransportSession.Session.OpenStream oder empfangen Sie einen vom Peer geöffneten über Session.OnStream.
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.
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;
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;
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:
SendDatagram eine TsgcQUICException aus. Es prüft DatagramsSupported, das Flag, mit dem ein Transport erklärt, dass sein Datagramm-Writer-Hook (OnSendDatagramData) tatsächlich die Leitung erreicht. Kein ausgelieferter Transport setzt es: Der HTTP/3-Client bindet diesen Hook an einen bewusst leeren Handler, und der HTTP/3-Server bindet ihn an TsgcHTTP3Server.SendDatagram, dessen Basisimplementierung ein bewusster No-Op ist. Die Methode verweigert daher den Dienst, bevor sie überhaupt etwas codiert, anstatt Ihre Payload stillschweigend zu verlieren. Unter .NET löst das verwaltete SendDatagram keine Ausnahme aus, es verwirft sein Argument direkt.OnDatagram wird ausschließlich von TsgcWebTransportSession.DoDatagram ausgelöst. Dessen einziger Aufrufer ist TsgcHTTP3ServerConnection.ProcessWebTransportDatagram, ein Integrationspunkt für einen datagrammfähigen Transport, den der ausgelieferte HTTP/3-Stack nie aufruft. OnDatagram wird daher nie ausgelöst.
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.
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. Das definiert wiederum SGC_QUIC, SGC_HTTP3 und SGC_WEBTRANSPORT. Der gesamte WebTransport-Code ist durch {$IFDEF SGC_WEBTRANSPORT} geschützt.sgcWebTransport für die Sitzungs- und Stream-Klassen, sgcHTTP für TsgcHTTP3Client und TsgcHTTP3Server, sgcQUIC_Server für TsgcQUICServerConnection.qmCustom oder qmBuiltin) wird beim Laden von OpenSSL automatisch erkannt und ist nicht vom Benutzer wählbar. WebTransport läuft auf qmBuiltin, dem nativen QUIC von OpenSSL, das vom mitgelieferten OpenSSL 3.5 bereitgestellt wird und die einzige Engine mit der Server- und Multi-Stream-Unterstützung ist, die eine Sitzung benötigt. Diese Engine hat keinen DATAGRAM-Pfad nach RFC 9221, weshalb Datagramme wirkungslos bleiben und SendDatagram eine Ausnahme auslöst.WebTransportConnect löst PlatformNotSupportedException aus, wenn netstandard2.0 verwendet wird oder wenn QuicConnection.IsSupported False ist.