Signatur
Beschreibung
Ein WebTransportBidirectionalStream besteht aus zwei Hälften: readable für eingehende und writable für ausgehende Daten. Die Eigenschaft readable gibt eine Instanz von WebTransportReceiveStream zurück. Diese Klasse erbt von ReadableStream und liefert Daten als Uint8Array-Blöcke, die über HTTP/3 (QUIC) zuverlässig und in der richtigen Reihenfolge ankommen.
Einen bidirektionalen Stream bekommst du auf zwei Wegen. Entweder öffnest du ihn selbst mit WebTransport.prototype.createBidirectionalStream(), oder der Server öffnet ihn und du nimmst ihn über WebTransport.incomingBidirectionalStreams entgegen. In beiden Fällen liest du die Antworten der Gegenseite über readable. Das passt zum Beispiel für Anfrage-Antwort-Muster, RPC-Aufrufe oder Chat-Kanäle, bei denen keine Nachricht verloren gehen darf.
Weil es ein gewöhnlicher ReadableStream ist, stehen alle Werkzeuge der Streams-API bereit: getReader(), for await...of, pipeTo(), pipeThrough() (etwa mit TextDecoderStream) und cancel(). Zusätzlich gibt es getStats() für Statistiken auf Transportebene.
Rückgabewert
ReadableStream, genauer ein WebTransportReceiveStream, der die empfangenen Bytes als Uint8Array-Blöcke liefert. Die Eigenschaft gibt bei jedem Zugriff dasselbe Objekt zurück.Beispiele
Anfrage senden und Antwort über readable lesen
const transport = new WebTransport('https://example.com:4433/echo');
await transport.ready;
const stream = await transport.createBidirectionalStream();
// Anfrage schreiben
const writer = stream.writable.getWriter();
await writer.write(new TextEncoder().encode('Hallo Server'));
await writer.close();
// Antwort lesen
const reader = stream.readable.getReader();
const decoder = new TextDecoder();
let antwort = '';
while (true) {
const { value, done } = await reader.read();
if (done) break;
antwort += decoder.decode(value, { stream: true });
}
antwort += decoder.decode();
console.log('Antwort:', antwort);
Vom Server geöffnete Streams mit for await...of verarbeiten
const transport = new WebTransport('https://example.com:4433/chat');
await transport.ready;
const handleStream = async (bidi) => {
const textStream = bidi.readable.pipeThrough(new TextDecoderStream());
for await (const chunk of textStream) {
console.log('Empfangen:', chunk);
}
console.log('Stream beendet');
};
for await (const bidi of transport.incomingBidirectionalStreams) {
handleStream(bidi); // bewusst nicht awaiten, damit mehrere Streams parallel laufen
}
Lesen per Timeout abbrechen und Fehler behandeln
const transport = new WebTransport('https://example.com:4433/data');
await transport.ready;
const { readable, writable } = await transport.createBidirectionalStream();
const writer = writable.getWriter();
await writer.write(new Uint8Array([1, 2, 3]));
await writer.close();
const timer = setTimeout(() => readable.cancel('Timeout'), 5000);
try {
let bytes = 0;
for await (const chunk of readable) {
bytes += chunk.byteLength;
}
console.log(`Empfangen: ${bytes} Bytes`);
} catch (err) {
// WebTransportError, z. B. wenn der Server den Stream zurücksetzt
console.error('Lesefehler:', err.name, err.streamErrorCode);
} finally {
clearTimeout(timer);
}
// Wichtig · Fallstricke
- Kompatibilität: WebTransport gibt es in Chromium-Browsern (ab 97), Firefox (ab 114) und Safari (ab 26.4). Auf älteren Safari-Versionen fehlt es. Prüfe die Unterstützung vorher mit
'WebTransport' in globalThis. Die API funktioniert nur in sicheren Kontexten (HTTPS) und braucht einen Server, der HTTP/3 mit WebTransport unterstützt. - Lock:
getReader()sperrt den Stream. Ein zweiter Aufruf,pipeTo()oderfor awaitwirft dann einenTypeError, bis dureader.releaseLock()aufrufst. - Nachrichtengrenzen: Die Blöcke entsprechen nicht zwingend den einzelnen
write()-Aufrufen der Gegenseite. Ein Datenstrom kann zerteilt oder zusammengefasst ankommen. Lege deshalb eigene Nachrichtengrenzen fest, zum Beispiel mit einem Längenpräfix oder Trennzeichen. - Fehler: Setzt die Gegenseite den Stream zurück (RESET_STREAM) oder bricht die Verbindung ab, wird
read()mit einemWebTransportErrorabgelehnt. Den Anwendungs-Fehlercode findest du instreamErrorCode. - Abbrechen:
readable.cancel()schickt der Gegenseite STOP_SENDING. Diewritable-Hälfte bleibt davon unberührt. Du musst sie selbst mitclose()oderabort()beenden. - Parallele Streams: Arbeite eingehende Streams nicht nacheinander mit
awaitin der äußeren Schleife ab. Sonst blockiert ein lange laufender Stream alle folgenden.