v0.0.11 - HTTP/2, hardened
⚡ HTTP/2 has landed - and it came in hardened
This is the big one: zttp speaks HTTP/2 now, end to end, on the same sans-IO pull API you already know. A pure-Zig core does the framing, HPACK, flow control, and stream state; your code just calls receive_data() and next_event(). And before the ink was dry, it went through two security passes - so the new surface ships locked down, not "we'll harden it later."
import zttp
conn = zttp.Connection(zttp.SERVER, protocol=zttp.HTTP2)
conn.receive_data(data) # same pull API, now multiplexed
event = conn.next_event() # every event carries its stream_id🚀 Highlights
- Full HTTP/2 core, sans-IO and zero-copy. Frame codec, HPACK (Huffman + dynamic table), per-stream state machine, connection + stream flow control, and a bounded event ring - all in pure Zig, no I/O, no syscalls (#5, #18, #19, #32).
- Read and write. Server reads requests, client reads responses, and the write side serializes responses/requests with outbound flow control and a
Streamhandle that routes multiplexed sends correctly (#19, #32). - Hardened from day one. HTTP/2 Rapid Reset (CVE-2023-44487), CONTINUATION floods (CVE-2024-27316), and H2→H1 header smuggling are all defended in the core - see Security below.
- Now on Python 3.10 and 3.11 too (#31).
🌐 HTTP/2
- Server read path with a sans-IO Zig core: preface + SETTINGS handshake, HEADERS/CONTINUATION reassembly, DATA, trailers, and the control frames (RST_STREAM, GOAWAY, SETTINGS, PING, WINDOW_UPDATE). Every event carries a
stream_id(0 for HTTP/1.1, so existing code is untouched) (#5). - Client responses: read HEADERS/DATA back on a stream you opened, including interim
1xx(#18). - Write side, driven from Python:
send_response/send_data/end_messagethrough a per-streamStreamhandle, with connection- and stream-level send windows parking and draining bytes asWINDOW_UPDATEcredit arrives (#19, #32).
🔒 Security
The HTTP/2 surface got a code-level audit and a CVE-driven audit. What shipped:
- Rapid Reset is bounded (CVE-2023-44487). Streams are evicted the moment they fully close, the concurrency count is derived (no drift), and
max_streams/max_stream_resetschurn caps tripENHANCE_YOUR_CALMbefore a peer can grow the connection without limit (#42). - No header-value smuggling. The H2 read path now rejects
CR/LF/NULand edge whitespace in every header and pseudo-header value - the same rule the H1 path and the H2 writer enforce - so a re-serialized request can't be split (#38). - Empty
:pathrejected per RFC 9113 8.3.1, matching the H1 non-empty-target rule (#38). - Allocation failures surface honestly. Out-of-memory on the upgrade and H2 flush paths now raise
MemoryErrorinstead of being swallowed or returning a half-built event (#43). - Fuzzing reaches the H2 engine. The libFuzzer/OSS-Fuzz target builds again and now drives the connection/HPACK/frame/stream machine, with a matching Python oracle and an always-on property test (#44).
🧰 Tooling & platforms
- Python 3.10 and 3.11 are supported (#31).
- Smaller, cleaner wheels: debug info is stripped from the extension and stale artifacts are no longer bundled (#35).
- HTTP/1.1 core regrouped under an
h1/package, making room forh2/next to it (#29). - A libFuzzer + OSS-Fuzz target for the parser (#25), and docs deploy moved to Cloudflare Workers Builds (#27), with a longer wheel-build timeout for the bigger matrix (#33).
📚 Docs
- HTTP/2 is documented across the site and marked done (#34).
- README response example updated to the current
send_responsesignature (#28).
Full changelog: v0.0.10...v0.0.11