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-quicfeature 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
- Client sends an extended CONNECT request over HTTP/3.
- bext validates the
:protocolpseudo-header and routes to the handler. - The handler receives a
WebTransportSession. - When either side closes, or the idle timeout expires, the session ends.
- bext emits
webtransport_sessions_activeandwebtransport_datagrams_sent_totalmetrics.
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.
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.
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
- W3C WebTransport specification — the living W3C standard
- MDN: WebTransport API — browser API reference and browser compatibility tables