Skip to content

v0.0.12 - HTTP/3, and HTTP/2 completed

Latest

Choose a tag to compare

@Kludex Kludex released this 12 Jun 12:51
· 4 commits to main since this release
32cec31

🌐 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_STREAM send 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.md covers 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_SENDING for per-stream cancellation (#97), a peer STOP_SENDING surfaced 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 and stream.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 an Upgrade: h2c request continue as stream 1 (#84, #86).
  • HEAD responses carrying content-length no 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.md was 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 - Data is 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_ITEM form (#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 vs h2 comparison 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