Expand description
Node http2 module: a REAL, minimal HTTP/2 server over TLS+ALPN.
This is a genuine HTTP/2 server (RFC 7540 framing + RFC 7541 HPACK via the
hpack crate) layered on the same blocking-rustls model as tls/https.
A real h2 client (curl --http2, nghttp, Node’s http2 client) can perform
a GET and receive a response body from a server built with
http2.createSecureServer.
── Threading model (identical discipline to net/tls) ────────────────────
Background threads NEVER touch the JS heap (a main-thread thread_local). One
thread per TCP connection owns the rustls::StreamOwned, performs the TLS
handshake (negotiating ALPN h2), then runs the full HTTP/2 framing loop:
reading/parsing frames and writing response frames. HPACK Encoder/Decoder
(with their connection-scoped dynamic tables) live on that thread. Every
JS-visible effect — building the Http2Stream/Http2Session objects, emitting
stream/session/request, running listeners — happens on the main thread
via posted IoTask closures. The stream objects talk back to their connection
thread through an mpsc channel of H2Cmds (respond/write/end enqueue a
command; the owner thread encodes+writes the frame), so reads and writes stay
on the single thread that owns the stateful TLS + HPACK cipher/table state.
── What IS implemented (server) ─────────────────────────────────────────────
- TLS handshake with ALPN offering only
h2; non-h2clients are closed. - Client connection preface check (
PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n). - SETTINGS: we send our (empty, valid) SETTINGS; we ACK the client’s SETTINGS and ignore incoming SETTINGS ACKs.
- HEADERS (type 1) inbound: PADDED + PRIORITY flags stripped, header block
HPACK-decoded into pseudo-headers (
:method/:path/:scheme/:authority)- regular headers. Fires
stream(and compatrequest).
- regular headers. Fires
- DATA (type 0) inbound: emitted on the stream as
data/end(request body). Http2Stream.respond(headers)→ HPACK-encoded HEADERS with:status.Http2Stream.write(data)/.end([data])→ DATA frames (END_STREAM on end), chunked to <= 16384 (the default SETTINGS_MAX_FRAME_SIZE) per frame.- PING (type 6): answered with a PING ACK echoing the 8-byte payload.
- WINDOW_UPDATE (type 8) / PRIORITY (type 2) / RST_STREAM (type 3): accepted and ignored (large-enough default windows; see caveats).
- GOAWAY (type 7): sent on connection close; inbound GOAWAY ends the loop.
── Honest limitations (NOT implemented — deliberately, not faked) ────────────
http2.connect(CLIENT) is NOT implemented — it throws a clear error.http2.createServer(cleartext h2c / prior-knowledge) is NOT implemented.- CONTINUATION frames (type 9): a HEADERS frame WITHOUT END_HEADERS is not reassembled — that stream is skipped. Real clients pack a single small GET header block into one HEADERS frame, so this rarely triggers, but a very large request header block would be dropped rather than mis-parsed.
- Flow control is minimal: we advertise/assume the default windows and IGNORE inbound WINDOW_UPDATE for our own send side. A response body larger than the peer’s stream/connection window (65535 bytes by default) can STALL. Small responses (the common case, and the test target) are unaffected.
- No server push, no trailers, no PRIORITY tree, no per-stream RST bookkeeping, no ALTSVC/ORIGIN. These are absent, never stubbed to look present.
── Handler-fault isolation (why the server used to close right after HEADERS) ─
The event loop treats an Err returned from a posted IoTask as FATAL
(host::drive_event_loop runs task()?), which terminates the whole process
and closes every live socket. events::emit propagates a listener’s error
(invoke(&f, …)?). So an exception thrown by the user’s stream/request
handler used to bubble out of the connection’s on_headers IoTask and kill the
server the instant the first request arrived — the client sees the TCP close as
a “broken pipe” with no response. To prevent one faulty handler from taking
down the server, the connection IoTasks (on_headers/on_data/on_session)
CATCH handler errors, print them to stderr (like Node’s uncaught-exception
output, so the cause is visible), and return Ok — the loop and other
connections survive. Set HTTP2_DEBUG=1 to trace every frame in/out on stderr.
── Verification status ──────────────────────────────────────────────────────
The framing (9-octet header layout, big-endian 24-bit length, frame type/flag
values, HPACK usage) is written to RFC 7540/7541. It has NOT been compiled or
run in this session (the parent owns the shared build), so the byte-level
correctness is verified-by-construction against the RFCs, not by a live curl.
See the final report for the exact curl --http2 command to confirm it.
Constants§
- METHODS
http2module functions routed throughstdlib::call.- SERVER_
METHODS - Instance method names for the
@@nativetags this module owns (exposed tostdlib::instance_has_methodso a method read yields a bound method). - SESSION_
METHODS - STREAM_
METHODS
Functions§
- call
stdlib::callentry forhttp2.<method>.- constant
http2.constantsand other non-function properties, reachable vianamespace_property→stdlib::constant.- instance_
call - new_
emitter_ object - Build a native emitter object (
@@nativetag +@@on/@@oncemaps + extras), sharing the EventEmitter shape withevents/net/tls.