WebTransport

WebTransport runs over HTTP/3 (QUIC) and provides both reliable bidirectional streams and unreliable datagrams — something neither WebSocket nor SSE can offer. bext supports WebTransport natively when compiled with the h3-quic feature.

Requirements

WebTransport requires HTTP/3, which means:

  • The h3-quic feature flag must be enabled at compile time.
  • A TLS certificate must be configured (QUIC is always encrypted).
  • The UDP port (typically 443) must be reachable.
bext build --features h3-quic

Configuration

[http3]
enabled = true

[webtransport]
enabled = true
max_sessions = 10000
session_idle_timeout_secs = 60

bext advertises WebTransport support via the Alt-Svc header on HTTP/2 responses so browsers can discover and upgrade automatically.

API

The WebTransport API mirrors WebSocket but adds datagram methods:

use bext::webtransport::{WebTransportSession, Datagram};

async fn handle_session(session: WebTransportSession) {
    // Open a reliable bidirectional stream
    let (mut send, mut recv) = session.open_bi().await?;
    send.write_all(b"hello from server").await?;

    // Accept streams opened by the client
    while let Some((mut send, mut recv)) = session.accept_bi().await? {
        tokio::spawn(async move {
            let data = recv.read_to_end(1024 * 64).await?;
            send.write_all(&process(data)).await?;
            Ok::<_, anyhow::Error>(())
        });
    }
}

Reliable Streams

Streams are ordered and reliable (like TCP). Open as many as you need — QUIC multiplexes them without head-of-line blocking:

Method Description
open_bi() Open a bidirectional stream.
open_uni() Open a unidirectional send-only stream.
accept_bi() Accept a client-opened bidi stream.
accept_uni() Accept a client-opened uni stream.

Unreliable Datagrams

Datagrams are unordered and unreliable (like UDP). They are ideal for data where freshness matters more than delivery:

// Send a datagram
session.send_datagram(b"player_pos:x=10,y=20").await?;

// Receive datagrams
while let Some(dgram) = session.recv_datagram().await? {
    handle_position_update(&dgram);
}

Per RFC 9297, the maximum datagram payload size defaults to 1200 bytes. Datagrams larger than this are dropped. You can query the negotiated maximum at runtime:

let max_size = session.max_datagram_size(); // typically 1200

Use Cases

WebTransport shines where WebSocket falls short:

  • Realtime games — unreliable datagrams for position updates, reliable streams for chat and game state.
  • Collaborative editing — CRDT operations on reliable streams, cursor positions on datagrams.
  • Telemetry fan-out — high-frequency sensor data where dropping a sample is better than buffering.
  • Live media — low-latency audio/video segments where retransmission would add unacceptable delay.

Client Example

const transport = new WebTransport("https://example.com/game");
await transport.ready;

// Unreliable datagrams
const writer = transport.datagrams.writable.getWriter();
await writer.write(new Uint8Array([1, 2, 3]));

// Reliable bidirectional stream
const stream = await transport.createBidirectionalStream();
const streamWriter = stream.writable.getWriter();
await streamWriter.write(new TextEncoder().encode("hello"));

Session Lifecycle

  1. Client sends an extended CONNECT request over HTTP/3.
  2. bext validates the :protocol pseudo-header and routes to the handler.
  3. The handler receives a WebTransportSession.
  4. When either side closes, or the idle timeout expires, the session ends.
  5. bext emits webtransport_sessions_active and webtransport_datagrams_sent_total metrics.

Comparison with WebSocket

Feature WebSocket WebTransport
Transport TCP (HTTP/1 or 2) QUIC (HTTP/3)
Reliability Always reliable Streams + datagrams
Head-of-line block Yes No (per-stream)
Multiplexing Single stream Many streams
0-RTT connect No Yes (with QUIC)

Feature Flag

WebTransport requires the h3-quic feature:

bext build --features h3-quic

Without this flag, WebTransport configuration is ignored.

Note

WebTransport requires a TLS certificate and an open UDP port 443. In environments where UDP is firewalled or QUIC is blocked, clients will fail to connect entirely — there is no TCP fallback. For broader compatibility in mixed-network environments, provide a WebSocket fallback path for clients that cannot reach QUIC.

Tip

Datagrams larger than the negotiated maximum (typically 1200 bytes) are silently dropped — no error is returned to the sender. Call session.max_datagram_size() at runtime and fragment payloads that may exceed it.

Related

  • HTTP/3 and QUIC — enabling QUIC transport, which WebTransport depends on
  • WebSockets — TCP-based alternative with broader network compatibility
  • Server-Sent Events — lightweight unidirectional alternative for push-only use cases
  • TLS and HTTPS — certificate configuration required for QUIC/WebTransport
  • Build flags — full list of compile-time feature flags

Links