HTTP/1.1 and HTTP/2 in Zig: field-value parsers, sans-I/O connection machines, and the engines you build a server or client on.
Zig's std.http has an HTTP/1.1 client and server. It has no HTTP/2 and no
HPACK, and its request and response types come with a connection attached, so a
proxy, a test, or a server with its own event loop has nothing to call. This
package is that layer. h1 and h2 each hold parsers, a sans-I/O connection
machine, and engines; the parsers take bytes, return borrowed slices, fill
storage you own, and never allocate.
It is mid-level on purpose. There is no router, no middleware, and no request
type you cannot see through: h1.ServerConn and h2.Session hand you events,
and an engine is a listener and a handler loop around them.
const http = @import("http");
fn hostOf(bytes: []const u8) !?[]const u8 {
var storage: [32]http.Header = undefined;
var search: http.h1.SearchState = .init;
const parsed = try http.h1.parseRequest(bytes, &search, &storage);
return http.h1.host(parsed.head.headers); // borrows `bytes`
}The library is pre-1.0 and the API is not frozen; a consumer pins a version.
- Field values:
content_length.parse,encoding.negotiate,token_list.contains, andtransfer_encoding.summarizeread one field family each, andCursoris the byte position all of them walk. - Headers:
Headers.get,values, andcontainsTokenlook up a field section in wire order and fold ASCII case, and duplicate lines stay separate. - Method and status:
Method.parseis case-sensitive,Statusis an open enum so an unnamed three-digit code is still a value, andWireVersionkeeps the exact digits. - The h1 message:
h1.parseRequestreturns borrowed slices orerror.Incomplete,h1.framingpicks the body rule,h1.chunked.Decoderresumes mid-body, andh1.Writerwrites the message back out. - The h1 connection machine:
h1.ServerConnandh1.ClientConntake the bytes you read and the time you have, and own no memory and no socket. - The h1 engines:
h1.engine.blocking.servetakes one connection at a time, andh1.engine.poll,.epoll, and.kqueueserve fixed slots with deadlines. - The h2 session and frames:
h2.frame.parseis the frame codec, andh2.Sessionowns SETTINGS, streams, flow control, HPACK, and CONTINUATION assembly. - HPACK:
hpack.Decoderandhpack.Encoderkeep the static and dynamic tables in step, andprefix_intandhuffmanare the primitives QPACK reuses. - The client contract:
client.Requestandclient.Bodyare protocol-neutral, andclient.blocking.Clientdispatches to an h1 or an h2 adapter. - Running a server:
engine.Handler(Exchange)is the one vtable every engine calls, andhttp_tlsadds TLS without putting crypto in the core. - Performance: 81 ns against 79 ns on a typical request head, 255 ns against 334 ns on a wide one, and 3.8x on a 1 KiB header value.
- Limits: no streaming request bodies, no connection reuse, and the rest of what is not here.
Not implemented:
- Streaming request bodies:
client.Request.bodyis a[]const u8, and an engine'sExchange.request.bodyis the whole body up toConfig.body_bytes. - Chunked request bodies on the server:
h1.ServerConnanswers 501 withRejectReason.chunked_unsupported. - Response trailers:
h1.ClientConnparses them intoConfig.trailers, andclient.ResponseHeadhas no trailer list, so the fields are dropped. - Connection reuse and pooling:
h1.client.blocking.fetchdoes not reuse the connection, and nothing inhttp.clientpools one. - Redirects: a 3xx comes back as a
client.Responsewith itsStatus; nothing follows it. - Cookies:
Headersnever combinesSet-Cookie, h2cookielines stay separate, and there is no jar. - Routing:
h1.targetFormclassifies the target and stops. There is no router and no method dispatch. - Middleware:
engine.Handleris one vtable with four entries, and nothing chains around it. - Server push: a received
PUSH_PROMISEis a connection errorPROTOCOL_ERROR. - HTTP/3: there is no
h3namespace, no QPACK, and no QUIC varint.
zig fetch --save https://baskingspot.dev/http/v0.2.0.tar.gzAdd the module in build.zig:
const http = b.dependency("http", .{ .target = target, .optimize = optimize });
exe.root_module.addImport("http", http.module("http"));std.http keeps its name, so http sits beside it in the same file. The
package also exports http_tls, a second module for the TLS readiness engines;
import it only when the server terminates TLS. http builds with Zig 0.16 only.
- The guide says which piece to use for a task, with the symbols and the code.
- The API reference documents every public symbol in
src/root.zig. examples/runs every engine, the two clients, and a small curl.just check-examplebuilds all of them.CHANGELOG.mdrecords what moved in the API.AGENTS.mdhas the working notes for the repository.
just cinix develop provides the toolchain. just lists the recipes, and just ci
runs what CI runs: the unit tests, lint, the Go interop server, the example
builds, and h2spec in strict mode against the readiness and TLS examples.
just bench runs the benchmarks at ReleaseFast, just fuzz 50M drives the
fuzz targets, and just example starts the HTTP/1.1 server on 127.0.0.1:8080.
just conformance-run starts the h2c example and runs h2spec against it.
just doc builds the site into zig-out/site/http/, and just guide serves it
with live reload.
MIT. See LICENSE. The logo files in docs/static/ are baskingspot
marks and are not covered by it. See docs/static/LICENSE.
Built with LLM assistance. How, in detail.