HTTP/2

The library client (h2_connect, h2_request, Client::h2_get / h2_post / h2_send) completes a request and returns the peer status, header fields, body, and any trailing fields. Cleartext sends the connection preface. HTTPS negotiates ALPN and speaks HTTP/2 only when the selected protocol is h2; otherwise the same request is HTTP/1.1 on that connection.

h2_connect / h2_request are one-shot: they dial, finish one stream, and close. Client::h2_get / h2_post / h2_send keep an HTTP/2 session on that client (separate from the HTTP/1.1 ConnPool). Later requests to the same origin reuse the socket and use the next odd stream id. Client::close ends the session.

h2_serve_once / h2_serve / h2_serve_all dispatch each stream to the HttpHandler and write that response. One connection can finish multiple streams. h2_serve_all accepts more than one connection. On a TLS listener, ALPN other than h2 is served as HTTP/1.1 through the same handler.

Flow control tracks connection and stream send windows. WINDOW_UPDATE of zero and a window that would pass 2^31-1 are connection errors signaled with GOAWAY. Peer SETTINGS_HEADER_TABLE_SIZE, SETTINGS_INITIAL_WINDOW_SIZE, SETTINGS_MAX_FRAME_SIZE, and SETTINGS_MAX_CONCURRENT_STREAMS are applied. PING is answered with an ACK of the same 8 bytes. RST_STREAM ends only that stream. GOAWAY refuses newer streams while streams at or below the last stream id can still finish. PRIORITY frames are ignored. Server push is not sent (SETTINGS_ENABLE_PUSH 0).

TLS ALPN is available on coil-tls (alpn: "h2" on ClientOpts / ServerOpts passed to tls::{client,server}::enable).

Frame header encode/decode lives in http::h2 (encode_frame / decode_frame, connection preface). Payload codecs cover SETTINGS (6-byte identifier/value pairs; ACK is empty), WINDOW_UPDATE (4-byte increment, zero rejected), GOAWAY (last-stream-id + error code), PUSH_PROMISE (promised stream id + header block), and CONTINUATION. headers_frame / headers_from_frame wrap HPACK from http::hpack. headers_frames / encode_headers_block_frames split a header block across HEADERS + CONTINUATION when it exceeds the max frame payload (default 16384). The static table is RFC 7541 Appendix A (61 entries). Decode accepts Huffman strings (H=1), literal with incremental indexing, never-indexed literals, and dynamic-table size updates. Encode sets H=1 when Huffman is strictly shorter, otherwise raw H=0. Header fields use indexed static or dynamic entries, or literal with incremental indexing (encode_header_block_with mutates the same HpackTable so a later block can emit a dynamic index). hpack_table_resize still evicts when SETTINGS_HEADER_TABLE_SIZE shrinks. Default dynamic table size is 4096 octets. Peer SETTINGS_HEADER_TABLE_SIZE (id 1) resizes the local encoder table. The decoder table changes when this endpoint sends that setting.

H2Session in http::h2_session is an in-memory server view of a client byte buffer. feed consumes the 24-byte connection preface, then frames via decode_frame. The first frame must be SETTINGS on stream 0; a non-ACK SETTINGS is answered with SETTINGS ACK (flag 0x1, empty payload) on drain. HEADERS on odd stream ids store decoded names/values; DATA on those streams accumulates until END_STREAM. HEADERS or PUSH_PROMISE without END_HEADERS is assembled with following CONTINUATION frames on the same stream before HPACK. Two streams can be interleaved in one buffer. DATA/HEADERS on stream 0 or even client stream ids are BadResponse. WINDOW_UPDATE is accepted (increment 0 still fails in the codec). One HpackTable (default 4096) lives on the session and on the live h2_connect / h2_get_over_h2 decoder; later HEADERS can use dynamic indices from earlier blocks. headers_from_frame still builds a fresh table for one-off decode. queue_push emits PUSH_PROMISE; push_count / push_headers expose received pushes.

Cleartext prior knowledge (RFC 7540 §3.4) is on the wire. h2_prior_knowledge_get builds the client preface + empty SETTINGS + stream-1 HEADERS. h2_request / h2_connect / h2_connect_two are the one-shot socket clients. Client holds H2ClientSlot so h2_get / h2_send can issue N streams on one connection. h2_serve_once(handler) accepts one connection and dispatches each ended stream to the handler. h2_serve requires TLS and accepts until the listener fails. h2_serve_all is the cleartext or TLS accept loop. Socket loopbacks are examples/h2_loopback.hy, examples/h2_mux_loopback.hy, examples/h2_tls_loopback.hy, and examples/h2_reuse_loopback.hy. coil test cannot spawn threads.

Roadmap#

  1. Client/server negotiate h2 via ALPN (h2_connect / h2_serve, tls::{client,server}::enable with ClientOpts / ServerOpts)
  2. Shared framing layer in src/http/h2.hy (frame header, preface, SETTINGS / WINDOW_UPDATE / GOAWAY / HEADERS / CONTINUATION / PUSH_PROMISE)
  3. HPACK (src/http/hpack.hy: static table, Huffman encode/decode, dynamic table; session table; SETTINGS_HEADER_TABLE_SIZE)
  4. Multiplexed requests per connection (H2Session; cleartext prior-knowledge GET and two-GET; TLS ALPN GET)
  5. Fallback to HTTP/1.1 when ALPN does not select h2 (h2_connect: done)

Synced from coil-http/docs/h2.md . Edit it there; changes reach this site on the next package sync.

↑ ↓ navigate ↵ open