Decision records
ADR-0019: HTTP/3 (QUIC) — experimental, behind a feature
- Status: Accepted (2026-10-06) — evaluation
- Benchmark:
docs/benchmarks/2026-10-06-http3.md
Context
M10: evaluate HTTP/3 behind a feature flag. The Rust HTTP/3 stack is quinn (QUIC,
0.11, mature) with h3/h3-quinn (0.0.x, explicitly pre-1.0).
Decision
- Cargo feature
http3(scalws-http → scalws-server → scalwsd), off by default; default builds and packages contain no QUIC code. A build without the feature refuseshttp3: trueat start with a clear error. listener.http3: true(TLS listeners only) binds a QUIC endpoint on the same port number over UDP. TLS: TLS 1.3 only, ALPNh3, no 0-RTT, the listener’s reloadable SNI resolver — configured and ACME certificates rotate for QUIC too.- Requests enter the same
RequestServiceas HTTP/1.1/2 viacall_boxed(the pipeline is generic over the request body type), so routing, limits, security rules, quotas, cache, telemetry and access logs are identical. Connection-specific headers are stripped from responses (RFC 9114 §4.2). - Connections take a permit from the global connection limit and pass per-source
admission; on shutdown the endpoint sends GOAWAY and in-flight requests drain.
Transport: bidirectional streams per connection =
h2_max_concurrent_streams, idle timeout =idle_timeout. - TCP responses of such listeners advertise
Alt-Svc: h3=":<port>"; ma=86400.
Evaluation result
- Functionally complete for request/response traffic (integration test with a real QUIC + HTTP/3 client: GET, POST bodies, deny rules, SNI selection, Alt-Svc).
- Throughput ≈ 57 % of scalws’s HTTP/2 on the same host (userspace QUIC + per-packet crypto) and ≈ 6× Caddy’s HTTP/3; memory flat in a soak run including HTTP/3.
- A per-connection task set that kept completed requests (1.1 GiB after 10 s of load) was found by the benchmark and fixed before acceptance.
Not implemented
WebSocket over HTTP/3 (extended CONNECT), 0-RTT, connection migration tuning, QUIC
metrics beyond handshakes, GSO/GRO tuning. Staying behind the feature until h3
reaches 1.0 or a production evaluation justifies default-on.