🌐 HTTP/3 has landed - and HTTP/2 is now complete
zttp speaks all three HTTP versions. This release adds HTTP/3 over a from-scratch, pure-Zig QUIC stack - transport, TLS 1.3 handshake, loss recovery, QPACK, and the H3 framing - all behind the same sans-IO pull API. And it closes out HTTP/2: every gap surfaced while integrating with uvicorn is now filled, so H2 is feature-complete for a real server.
import zttp
# HTTP/3, fed UDP datagrams instead of a byte stream - same events out
conn = zttp.Connection(zttp.SERVER, protocol=zttp.HTTP3, **credentials)
conn.receive_datagram(datagram, now)
event = conn.next_event() # Request / Data / EndOfMessage, each with its stream_id🚀 Highlights
- HTTP/3, from the wire up. A complete QUIC transport written from scratch in Zig - long/short-header packets, the TLS 1.3 handshake over CRYPTO frames, packet-number spaces, ACKs, loss detection with PTO, and STREAM (re)transmission - plus H3 framing and QPACK. No
aioquic, no OpenSSL: it's all in the core (#20, #68, #70, #73, #85). - HTTP/2 is feature-complete. Inbound flow-control replenishment, SETTINGS/PING auto-ACK,
GOAWAY/RST_STREAMsend APIs,send_informational(100-continue), eager server preface, and h2c upgrade seeding - the full list of gaps from the uvicorn integration (#36) is closed (#59, #60, #84, #86, #88). - Faster across the board. Single-copy Content-Length bodies, interned request/status lines, one-allocation connection construction, and direct refcounting on 3.14+ free-threaded builds (#69, #71, #72, #74, #76).
- Hardened, and documented as such. The Content-Length smuggling guard runs on every H2 end path, H3 pseudo-header values are validated like regular fields, and
THREAT_MODEL.mdcovers all three protocols (#92, #94, #93).
🌐 HTTP/3 (new)
A read-path-first HTTP/3 server on a from-scratch QUIC stack - the most ambitious piece of this release.
- QUIC transport built in pure Zig: the TLS 1.3 key schedule and AEAD ops (#68), the handshake message codec (#70), the handshake driven over QUIC CRYPTO frames (#73), packet/header serializers (#63), and a handshake interoperable with conformant clients (#101).
- Reliability: the STREAM send path on a shared packet builder (#78), retransmission of lost STREAM data (#79), and tail-loss recovery with a PTO timer (#80).
- HTTP/3 layer: the control stream and SETTINGS exchange (#89), advertised limits, GOAWAY for graceful shutdown (#91),
RESET_STREAM/STOP_SENDINGfor per-stream cancellation (#97), a peerSTOP_SENDINGsurfaced as a reset event (#99), the bidirectional stream limit enforced and replenished (MAX_STREAMS, #100), trailers and 1xx interim responses (#104), and the server's QPACK encoder/decoder streams (#102). - Correctness & safety: pseudo-header values are validated like regular fields (#94), and an early round of crash/validation/resource-exhaustion bugs was fixed (#87).
The QUIC + HTTP/3 path is the newest code and the least battle-tested of the three - see THREAT_MODEL.md.
⚡ HTTP/2 (completed)
Every gap found while wiring H2 into uvicorn (#36) is now resolved:
- Inbound flow control is replenished - the connection and stream receive windows refill and advertise
WINDOW_UPDATE, so request bodies and large responses no longer stall past 64 KiB (#60). - SETTINGS and PING are auto-ACKed (#60), and the server advertises its enforced limits in the handshake SETTINGS (#88).
connection.close()sends GOAWAY andstream.reset()sends RST_STREAM for graceful shutdown and per-stream cancellation (#59), with a connection-fatal error emitting exactly one GOAWAY (#88, prior).stream.send_informational()sends 100-continue,send_response(end_stream=...)avoids the empty trailing DATA frame for bodyless responses, and h2c upgrade seeding lets anUpgrade: h2crequest continue as stream 1 (#84, #86).- HEAD responses carrying
content-lengthno longer trip a stream error (#84).
🔒 Security
- The Content-Length-vs-body guard runs on every H2 end-of-stream path - DATA, a bodyless HEADERS frame, and trailers - so a request that lies about its body length can't be downgraded into a smuggled HTTP/1.1 message (#92). A mismatch now resets the stream, not the connection (#107).
- HTTP/3 pseudo-header values are validated with the same rules as regular fields (#94).
- The H2 roundtrip and the HPACK/QPACK encode-decode differential are fuzzed (#96), and
THREAT_MODEL.mdwas refreshed to cover HTTP/2 and HTTP/3 (#93), including a residual-risk note on the h2→h1 downgrade contract for proxy integrators (#106).
🏎️ Performance
- Single-copy Content-Length bodies -
Datais materialized straight from the fed buffer instead of being copied twice (#71). - Interned request-line and status-line strings, and header tuples/lists stored with the direct
SET_ITEMform (#74, #77). - One-allocation connection construction - the engine is built in place (#72).
- Inline refcounting on CPython 3.14+ GIL/free-threaded builds, plus a general hot-path pass (#76, #75, #69).
📊 Benchmarks
- The suite is now statistically robust (round-robin batches, GC disabled, median + spread) and verifies extraction before timing so the comparison is apples-to-apples (#67).
- Benchmarks are grouped under a
benchmarks/directory, one file per protocol, with an HTTP/2 vsh2comparison added alongside the HTTP/1 suite (#103, #105).
🧹 Code health & docs
- Shared ASCII/H2-field/decimal helpers hoisted into common leaves (#82), change-narration comments stripped (#81), and the H2 code-health cleanups from #58 applied (#106).
- The docs were reorganized around protocol-based usage with a single Architecture page (#95), HTTP/2 and HTTP/3 are explained in the usage docs (#98), and the README covers all three protocols (#65, #66).
Full changelog: v0.0.11...v0.0.12