h2

tape
import h2 from "h2";
import hpack from "hpack";

var c: h2.Conn = h2.client();
defer c.free();
let none: [0]hpack.Header = [];
let id: i64 = c.open("GET", "https", "example.com", "/", none[..], never, "", 65536, 1 << 20);
send(c.output());                     // your transport: a TLS stream that negotiated "h2"
c.advance_output(c.output().len);
while (true) {
    let n: i64 = receive(buf[..]);    // what the server sent
    if (n == 0) { c.end_of_input(); break; }
    c.feed(buf[0..n]) or { break; };
    send(c.output());                 // answers, window updates
    c.advance_output(c.output().len);
    let r: ?h2.Reply = c.next_reply();
    if (r != null) { io.println("{r!.status}: {r!.body.len} bytes"); break; }
}

h2 is HTTP/2 (RFC 9113) for a client, without the I/O: you feed it what the server sent, send what it queues, and take finished streams. To make requests, use http’s Client, which runs h2 over every https connection whose server picks HTTP/2. h2 is for code that brings its own transport.

FunctionDoes
client() -> ConnA connection with its preface, SETTINGS and window update queued: send them first
c.open(method, scheme, authority, path, fields, sensitive, body, max_head, max_body) -> i64A request as a new stream; its id
c.feed(data)What the server sent: every whole frame is processed
c.output(), c.advance_output(n)What to send, and how much was sent
c.next_reply() -> ?ReplyThe next finished stream
c.end_of_input()The transport ended: open streams fail
c.can_open(), c.active(), c.finished(), c.error()Whether a stream can open now; streams open; whether none ever can again; the connection’s error
c.goodbye()GOAWAY with no error, before you close the transport
c.free()Releases it
tape
pub struct Reply {
    id: i64;
    status: i64;
    headers: hpack.Vec_Header;     // without the pseudo-headers, in order
    body: string;
    err: ?H2Error;                 // when the stream failed
    retry: bool;                   // the server did not process it: send it again elsewhere
}
  • fields are the request’s headers after the pseudo-headers, with lowercase names and none of HTTP/1.1’s connection headers (Connection, Keep-Alive, Transfer-Encoding, Upgrade, …). sensitive(name) says which are never indexed by compression (credentials).
  • The body is copied and sent as the server’s windows allow; send output() after every feed, since a WINDOW_UPDATE may let more go.
  • The server may have up to its MAX_CONCURRENT_STREAMS streams open; can_open() says whether another fits now. open traps when it is false.
  • max_head limits a response’s headers as HTTP/2 counts them (name, value and 32 per field) and max_body its body: past either, the stream is reset and fails (HeadTooLarge, BodyTooLarge); the connection goes on.
  • Errors: H2Error holds RFC 9113’s codes (Protocol, FlowControl, Refused, Cancel, Compression, Calm, Http11Required, …) and GoAway, Closed, HeadTooLarge, BodyTooLarge, Malformed. A connection error is returned by feed once, with every open stream failed; a stream error fails only that stream’s reply.

Not covered: server push (refused), priorities, HTTP/2 without TLS (h2c), the server side.

Last modified: