API reference
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.
Each parser module reads one field family or message head and returns
borrowed slices or fills caller-owned storage. h1.ServerConn and
h2.Session are the connection machines; client is the protocol-neutral
client contract they both implement. The engines own the transport and the
handler loop over std.Io, and the parsers import no socket and no
allocator.
module
content_length
src
Content-Length parsing and combination (RFC 9110 §8.6).
more
parse(values) takes every field line named Content-Length, in the order
they appeared, and returns the length. null means the field was absent.
The field value is 1*DIGIT, so leading zeros are valid: "00" is 0 and
"0042" is 42. Repetition is allowed only when every value is the same
decimal number: ["42", "42"] and ["42, 42"] both parse to 42, while
differing values are a framing conflict and return error.Conflicting.
Empty list elements are ignored, as RFC 9110 §5.6.1 requires, but a field
line with no digits is not a list and is invalid.
This module stops at the field value. The rule that Transfer-Encoding
and Content-Length must not both frame a request (RFC 9112 §6.1) belongs
to the caller that sees both fields.
Constants
Functions
The one length named by the field, or null when the field is absent.
Types
struct
Parser
src
A streaming Content-Length accumulator. feed one field line at a time
when the caller already has a field view; parse is the slice form.
Fields
length: ?u64 = null
Functions
Add one field line. Repetition is allowed only when every value is the same decimal number.
The combined length, or null when no field line was fed.
module
encoding
src
Accept-Encoding parsing and content-coding negotiation.
more
negotiate(header, supported) takes one field value and the server's
supported codings in preference order. negotiateFields takes every field
line with that name, because multiple lines combine as one ordered list
(RFC 9110 §5.3). Both return a token borrowed from supported, the static
identity, or null when the client accepts nothing else. They allocate
nothing.
Rules (RFC 9110 §12.5.3):
- A coding's weight is its
qparameter, 0..1 with at most three fractional digits; no parameter means 1.q=0is not acceptable. *gives a weight to every coding not named explicitly.identityis acceptable by default. It is excluded byidentity;q=0, or by*;q=0when there is no explicit identity entry.- An empty field lists no codings and negotiates to
identity. An absent field is the caller's policy: pass""for the conservative default, or"*"to treat every supported coding as acceptable. - A malformed weight is 0, and the first entry for a coding wins.
- Ties go to the server's order in
supported; an explicit identity weight only wins when it is higher than the best coding's.
Constants
The token for "no content coding". It is not looked up in supported.
Functions
The chosen content coding, or null when nothing is acceptable.
supported lists the content codings the server can produce, most
preferred first. It must not contain identity; that token is handled
here. The returned slice is either one of supported's elements or
identity.
pub fn negotiateFields(fields: []const []const u8, supported: []const []const u8) ?[]const u8
src
Like negotiate, over every field line named Accept-Encoding.
The lines combine as if they were one comma-separated list, in order (RFC 9110 §5.3). A coding named on two lines therefore takes the weight of its first appearance.
module
engine
src
API shapes shared by the HTTP/1 and HTTP/2 server engines.
The protocol engines keep their own exchange types. This module shares the handler vtable shape, the response options, and the body/arena config knobs. It does not try to hide streams, framing, or connection ownership.
Functions
pub fn copyResponseOptions(
allocator: Allocator,
options: ResponseOptions,
) Allocator.Error!ResponseOptions
src
Copy response options and every borrowed field into caller storage. A
streaming handler may build options on its stack; the engine reads them
after handle returns.
pub fn resetArena(arena: *std.heap.ArenaAllocator, retain_bytes: usize) void
src
Types
module
stream
src
Pull-based response bodies for the server engines.
A handler returns a ReadStream; the engine calls next only when it can
send another chunk. The returned slice stays valid until the next next or
close, so the engine can hold it across a partial write.
Constants
value
error{
/// The body reached a clean end.
EndOfStream,
/// No bytes are ready yet. The stream is still open.
Pending,
/// The read was canceled by the caller or the transport.
Canceled,
/// The underlying source failed to read.
ReadFailed,
/// Allocation failed inside the source.
OutOfMemory,
}Types
struct
ResponseStream
src
Fields
stream: ReadStream
content_length: ?u64 = null
null means an unknown length. HTTP/1 streams the body until close
and sends Connection: close; HTTP/2 sends DATA without a declared
length.
struct
QueueStream
src
A response source over an Io.Queue. Closing either side of the queue wakes
the other side with error.Closed, which gives the stream pipe-style
cancellation without a coroutine runtime.
Fields
Functions
struct
ReaderStream
src
A response source over a caller-owned std.Io.Reader and buffer. The source
copies each chunk into buffer; the reader and buffer must outlive the
stream.
Fields
Functions
enum
RejectReason
src
Why the connection machine selected a rejection. Statuses stay the same; this is the additional information an application needs for its message.
Fields
malformed_head malformed_head
conflicting_length conflicting_length
invalid_length invalid_length
conflicting_framing conflicting_framing
invalid_framing invalid_framing
chunked_unsupported chunked_unsupported
missing_host missing_host
invalid_host invalid_host
too_many_fields too_many_fields
head_too_large head_too_large
body_too_large body_too_large
unsupported_version unsupported_version
unsupported_expectation unsupported_expectation
request_timeout request_timeout
internal_error internal_error
struct
ResponseOptions
src
The response fields the application controls.
Fields
status: Status = .ok
content_type: ?[]const u8 = null
close: bool = false
For h1, close after this response and emit Connection: close.
h2 Exchange.respond rejects this field; it has no stream-level
Connection-close meaning.
headers: []const Header = &.{}
Extra regular fields. The engine adds framing and protocol fields.
struct
DeferOptions
src
Options for a deferred response. A zero timeout leaves the machine's own
request deadline as the only bound. on_timeout is copied into the
exchange arena at defer time.
Fields
timeout: Io.Duration = .zero
on_timeout: PolicyResponse = .{
.options = .{ .status = .service_unavailable },
.body = "",
}
type fn
Handler
src
pub fn Handler(comptime Exchange: type) type
One type-erased handler over an engine's exchange type.
Fields
Functions
pub fn noopComplete(_: *anyopaque, _: *Exchange) void
src
pub fn noopReportError(_: *anyopaque, _: anyerror) void
src
pub fn defaultPolicyResponse(
_: *anyopaque,
context: PolicyContext,
) anyerror!PolicyResponse
src
Keep the machine's status and an empty body.
Types
struct
VTable
src
Fields
handle: *const fn (ptr: *anyopaque, exchange: *Exchange) anyerror!void
Build the response. The engine supplies a 500 if the handler returns without one.
complete: *const fn (ptr: *anyopaque, exchange: *Exchange) void
The exchange is finished: the response was written, the stream was reset, or the connection ended. The arena is readable; the engine resets it afterward.
report_error: *const fn (ptr: *anyopaque, err: anyerror) void
handle or the engine failed. The engine has already arranged
its policy response or reset.
policy_response: *const fn (
ptr: *anyopaque,
context: PolicyContext,
) anyerror!PolicyResponse
Review a parser or handler rejection. The body is allocated
from context.arena or borrowed until the response completes.
struct
Config
src
The two limits whose names mean the same thing in every engine.
Fields
body_bytes: usize = 64 * 1024
Largest buffered request body.
arena_retain_bytes: usize = 16 * 1024
Retained per-exchange arena capacity after completion.
enum
ResumeResult
src
Fields
module
stream
src
Pull-based response bodies for the server engines.
A handler returns a ReadStream; the engine calls next only when it can
send another chunk. The returned slice stays valid until the next next or
close, so the engine can hold it across a partial write.
Constants
value
error{
/// The body reached a clean end.
EndOfStream,
/// No bytes are ready yet. The stream is still open.
Pending,
/// The read was canceled by the caller or the transport.
Canceled,
/// The underlying source failed to read.
ReadFailed,
/// Allocation failed inside the source.
OutOfMemory,
}Types
struct
ResponseStream
src
Fields
stream: ReadStream
content_length: ?u64 = null
null means an unknown length. HTTP/1 streams the body until close
and sends Connection: close; HTTP/2 sends DATA without a declared
length.
struct
QueueStream
src
A response source over an Io.Queue. Closing either side of the queue wakes
the other side with error.Closed, which gives the stream pipe-style
cancellation without a coroutine runtime.
Fields
Functions
struct
ReaderStream
src
A response source over a caller-owned std.Io.Reader and buffer. The source
copies each chunk into buffer; the reader and buffer must outlive the
stream.
Fields
Functions
module
client
src
Shared HTTP client types and adapters.
Constants
Body failures carry no payload. A reset code or local cause is available from the Body that returned the error.
pub const BodyStepError = common.BodyStepError
src
Types
module
common
src
The shared HTTP client contract.
more
Protocol adapters provide a negotiated transport and implement the same request, response-head, and streaming-body shapes. Response heads are snapshots; body bytes are borrowed from the connection until the next advancing operation.
The next shape, after v0.1, is a writable request body rather than a reader
the adapter drains. DATA may follow a committed header block. The producer
appends only after the previous frame is fully accepted; the queue reports
capacity, not frame state, and AppendWhilePartial is a bug signal rather
than a control path. Retry belongs to the transport write. Nothing may
interleave a header block between HEADERS and its final CONTINUATION.
Trailers share the connection buffer with a borrowed body slice, so a
trailers() accessor must snapshot into the response arena when eof is
reported, not lazily on access.
Connection failure and reuse are deliberately protocol-specific. h1's
Body.close drains an unfinished response into scratch and may leave the
connection ConnectionDirty; that state is recoverable only by reconnecting.
h2 resets the stream and keeps the connection unless it is
ConnectionPoisoned, which is terminal. A dual-protocol caller must branch
on those names and must not alias them.
Constants
value
error{
EndOfStream,
Truncated,
Protocol,
Reset,
LocalCanceled,
LocalCapacity,
LocalOutOfMemory,
LocalUnusable,
}Body failures carry no payload. A reset code or local cause is available from the Body that returned the error.
pub const BodyStepError = BodyError || error{WouldBlock}
src
Types
struct
Request
src
Fields
method_token: []const u8
The exact wire method token. Methods are case-sensitive and the registry is open.
scheme: Scheme
authority: []const u8
h1 sends this as Host; h2 sends it as :authority.
target: []const u8
The origin-form path and query. h2 sends it as :path.
headers: []const Header = &.{}
Regular fields only, in wire order. Never include Host or an h2
pseudo-field. A caller targeting both protocols must send lowercase
names: h1 forwards them as-is, while h2 rejects uppercase as
InvalidHeader.
body: []const u8 = ""
Request body bytes. h1 sends them with Content-Length; h2 sends DATA
inside the request stream. Empty is the common case. When h2 returns an
early final response before the body is sent, this slice stays valid
until Body.close.
struct
ResponseHead
src
Fields
enum
Local
src
A local failure, not a peer outcome. The enum is closed: a new local
failure adds a variant rather than reusing protocol.
Fields
canceled canceled
Our cancellation. The stream is unfinished; close or reset the stream and keep the connection.
capacity capacity
A local buffer or policy limit ended this response. Reset the stream; the connection survives and this request is done.
out_of_memory out_of_memory
Allocation failed. The connection is not usable; close it.
unusable unusable
A local failure left a partial frame on the wire. The connection is not usable; close it. A blocking adapter must not produce this.
struct
BodyResult
src
A successful body read. The slice is borrowed for the connection operation and is never empty.
Fields
data: []const u8
struct
BodyStep
src
A non-blocking body step. WouldBlock is an error, not a result variant.
Fields
Functions
pub fn localCause(self: BodyStep) ?Local
src
Types
struct
Body
src
The blocking-client body contract.
read may block, never returns error.WouldBlock, never returns empty
data, and reports completion with eof. Holding four 16 KiB slices
exhausts the default 65,535-byte h2 connection window.
Fields
Functions
pub fn localCause(self: Body) ?Local
src
Types
struct
VTable
src
Fields
read: *const fn (ptr: *anyopaque) BodyError!BodyResult
Return the next body bytes or eof. The borrowed slice is valid
until any operation that advances the connection; a caller that
holds it across another read must copy. Holding an h2 slice pins
the connection window for every stream.
resetCode: *const fn (ptr: *anyopaque) ?u32
The RST_STREAM code when read returned error.Reset.
localCause: *const fn (ptr: *anyopaque) ?Local
The local cause when read returned a Local* error.
close: *const fn (ptr: *anyopaque) void
Finish the response. Idempotent. Releases unread flow control and resets an incomplete stream where the protocol supports it.
struct
GoAway
src
A connection-level GOAWAY. Streams at or below last_stream_id remain
valid; streams above it were not processed.
Fields
last_stream_id: u31
code: u32
struct
Config
src
Fields
allocator: Allocator
max_header_bytes: usize = 16 * 1024
Maximum snapshot bytes for response field names and values.
max_headers: usize = 128
Maximum response field records.
module
frame_queue
src
Resumable outbound frame queue for a step-shaped HTTP client.
more
A step client cannot block on a partial write: the transport may accept a prefix of one frame and return. This queue keeps the frame being written and a byte offset into it, refuses to append behind a partially written frame, and holds the CONTIGUATION contiguity rule for header blocks (RFC 9113 §4.3).
The queue owns no transport, no session, and no allocator. The caller lends
storage for the bytes and metadata for the frames. Bytes are copied in once;
after that the caller asks for unsent(), hands it to the transport, and
reports what was accepted with accepted.
The queue describes one serialized request. A multiplexing client needs one queue per stream plus an arbiter above them that never starts a second stream's frame while any frame is partially written.
Constants
value
error{
/// A frame is partially written. Nothing may be appended until it completes.
AppendWhilePartial,
/// No room for the frame's bytes or metadata.
QueueFull,
/// A header block is open and this frame is not a CONTINUATION on that
/// stream, or a CONTINUATION arrived with no block open.
InterleavedFrame,
/// A frame with no bytes.
EmptyFrame,
/// The previous request committed and the queue is empty. Call `reset`
/// before starting another request; control frames are still allowed.
NeedsReset,
}Types
enum
Class
src
What a frame does to the connection's framing rules.
Fields
headers headers
HEADERS. Opens a header block unless end_headers is set.
continuation continuation
CONTINUATION. Only legal while a header block is open.
data data
DATA or any other frame that takes no part in a header block.
control control
A connection-level frame: WINDOW_UPDATE, PING, SETTINGS, GOAWAY.
struct
Frame
src
Fields
bytes: []const u8
One complete encoded frame. Copied into the queue's storage.
stream_id: u31
class: Class
end_headers: bool
The frame carries the END_HEADERS flag.
end_stream: bool = false
The frame carries the END_STREAM flag.
struct
Queue
src
Fields
storage: []u8
meta: []Meta
used: usize
Bytes used in storage. Reset when the queue empties.
head: usize
tail: usize
offset: usize
Bytes of the head frame the transport already accepted.
state: State
complete: bool
The frame carrying END_STREAM was fully accepted. Retry safety flips at END_HEADERS; the request ends at END_STREAM.
run_stream: ?u31
Stream whose header block is open, if any.
Functions
True when a frame is partially written. Nothing may be appended.
pub fn currentState(self: *const Queue) State
src
Bytes of the head frame still to be written, or null when empty.
Report bytes the transport accepted. Never more than unsent().?.len.
Start a new request. Requires an empty queue.
Types
module
blocking
src
Protocol dispatch for a blocking client.
Both adapters share the types in client/common.zig and speak over the
caller-owned reader and writer. The caller chooses the protocol above this
layer; TLS ALPN or a cleartext prior-knowledge decision belongs there.
Types
struct
Request
src
pub const Request = common.Request
Fields
method_token: []const u8
The exact wire method token. Methods are case-sensitive and the registry is open.
scheme: Scheme
authority: []const u8
h1 sends this as Host; h2 sends it as :authority.
target: []const u8
The origin-form path and query. h2 sends it as :path.
headers: []const Header = &.{}
Regular fields only, in wire order. Never include Host or an h2
pseudo-field. A caller targeting both protocols must send lowercase
names: h1 forwards them as-is, while h2 rejects uppercase as
InvalidHeader.
body: []const u8 = ""
Request body bytes. h1 sends them with Content-Length; h2 sends DATA
inside the request stream. Empty is the common case. When h2 returns an
early final response before the body is sent, this slice stays valid
until Body.close.
struct
Request
src
pub const Request = common.Request
Fields
method_token: []const u8
The exact wire method token. Methods are case-sensitive and the registry is open.
scheme: Scheme
authority: []const u8
h1 sends this as Host; h2 sends it as :authority.
target: []const u8
The origin-form path and query. h2 sends it as :path.
headers: []const Header = &.{}
Regular fields only, in wire order. Never include Host or an h2
pseudo-field. A caller targeting both protocols must send lowercase
names: h1 forwards them as-is, while h2 rejects uppercase as
InvalidHeader.
body: []const u8 = ""
Request body bytes. h1 sends them with Content-Length; h2 sends DATA
inside the request stream. Empty is the common case. When h2 returns an
early final response before the body is sent, this slice stays valid
until Body.close.
struct
Body
src
pub const Body = common.Body
The blocking-client body contract.
read may block, never returns error.WouldBlock, never returns empty
data, and reports completion with eof. Holding four 16 KiB slices
exhausts the default 65,535-byte h2 connection window.
Fields
Functions
pub fn localCause(self: Body) ?Local
src
Types
struct
VTable
src
Fields
read: *const fn (ptr: *anyopaque) BodyError!BodyResult
Return the next body bytes or eof. The borrowed slice is valid
until any operation that advances the connection; a caller that
holds it across another read must copy. Holding an h2 slice pins
the connection window for every stream.
resetCode: *const fn (ptr: *anyopaque) ?u32
The RST_STREAM code when read returned error.Reset.
localCause: *const fn (ptr: *anyopaque) ?Local
The local cause when read returned a Local* error.
close: *const fn (ptr: *anyopaque) void
Finish the response. Idempotent. Releases unread flow control and resets an incomplete stream where the protocol supports it.
struct
GoAway
src
pub const GoAway = common.GoAway
A connection-level GOAWAY. Streams at or below last_stream_id remain
valid; streams above it were not processed.
Fields
last_stream_id: u31
code: u32
struct
BodyResult
src
pub const BodyResult = common.BodyResult
A successful body read. The slice is borrowed for the connection operation and is never empty.
Fields
data: []const u8
enum
Local
src
pub const Local = common.Local
A local failure, not a peer outcome. The enum is closed: a new local
failure adds a variant rather than reusing protocol.
Fields
canceled canceled
Our cancellation. The stream is unfinished; close or reset the stream and keep the connection.
capacity capacity
A local buffer or policy limit ended this response. Reset the stream; the connection survives and this request is done.
out_of_memory out_of_memory
Allocation failed. The connection is not usable; close it.
unusable unusable
A local failure left a partial frame on the wire. The connection is not usable; close it. A blocking adapter must not produce this.
struct
BodyStep
src
pub const BodyStep = common.BodyStep
A non-blocking body step. WouldBlock is an error, not a result variant.
Fields
Functions
pub fn localCause(self: BodyStep) ?Local
src
Types
struct
Config
src
pub const Config = common.Config
Fields
allocator: Allocator
max_header_bytes: usize = 16 * 1024
Maximum snapshot bytes for response field names and values.
max_headers: usize = 128
Maximum response field records.
module
h1
src
HTTP/1.1 parsing, the connection machine, and the HTTP/1 server engines.
more
The parser APIs are sans-I/O: the parser takes the bytes read so far and
reports either a head, error.Incomplete, or error.Invalid. It allocates
nothing and borrows every returned slice from the input. The caller owns the
buffer and the storage for the field lines. engine is the optional server
layer; the parser files do not import a socket.
Lifetimes: the returned head borrows the input buffer and the caller's header storage. It is valid only while both are unchanged. A buffered reader may move its bytes when it refills, so copy the fields that must outlive the head before the next read or rebase.
Chooses strict behavior where RFC 9112 permits leniency:
- Bare LF is rejected. RFC 9112 §2.2 says a recipient MAY recognize it.
- Start lines require exactly one SP between components. RFC 9112 §3 and §4 permit whitespace-delimited parsing but warn about smuggling.
- obs-fold is rejected. RFC 9112 §5.2 permits reject-or-unfold.
- A line before the first field that starts with SP/HTAB is rejected. RFC 9112 §2.2 permits rejecting or consuming it.
SearchState carries the number of bytes already searched for the end of
the head. Keep one per connection buffer. The parser records the search
length on error.Incomplete and clears it after a complete parse; the
caller never passes a length. This lets the completeness check look only at
the new bytes, so a client trickling one octet at a time cannot force a
rescan of the whole buffer. Parsing itself always starts at the beginning.
Invalid syntax before a terminator is reported on the first call; after
that the gate returns error.Incomplete until the terminator arrives.
Constants
value
error{
/// The buffer ends before the head does. Read more and retry.
Incomplete,
/// The bytes cannot form an HTTP/1.1 message. The caller should send 400.
Invalid,
/// A well-formed HTTP-version this parser does not handle. 505.
UnsupportedVersion,
/// The caller's field storage is full. The caller should send 431.
TooManyHeaders,
}Functions
pub fn persistent(version: Version, headers: Headers) bool
src
Whether the connection persists after the most recently received message.
pub fn targetForm(method: ?Method, target: []const u8) ?TargetForm
src
Classify the request-target by the form RFC 9112 §3.2 allows for method.
Returns null when the bytes do not fit an allowed form. This is syntax only:
it does not resolve the authority or normalize the path.
The single Host value, or null when the field is absent. More than one
field line is error.DuplicateHost. RFC 9112 §3.2 requires exactly one
Host in an HTTP/1.1 request; the caller applies that rule.
pub fn parseRequest(
buf: []const u8,
state: *SearchState,
storage: []Header,
) Error!ParsedRequest
src
Parse a request head from buf.
more
storage receives the field lines; the returned head.headers.items is
the used prefix. The head borrows buf and storage; see RequestHead
for what invalidates it.
state is the incremental search state for this buffer; see
SearchState. buf must be shorter than 4 GiB; a real head limit is far
smaller.
pub fn parseResponse(
buf: []const u8,
state: *SearchState,
storage: []Header,
) Error!ParsedResponse
src
Parse a response head from buf.
more
A 1xx head is a complete message head. parseResponse returns it like any
other; a client that wants the final response calls it again on the
remaining bytes. Status.class() identifies the interim class.
The head borrows buf and storage; see ResponseHead. state works as
in parseRequest.
pub fn parseFieldSection(
buf: []const u8,
state: *SearchState,
storage: []Header,
) Error!ParsedFieldSection
src
Parse a standalone field section: *( field-line CRLF ) CRLF.
This is the trailer parser and the entry point for a caller that already
has a start line. The section is complete at the first empty line; an empty
section is a single CRLF. The returned headers borrow buf and storage;
see RequestHead.
Types
struct
WireVersion
src
pub const WireVersion = version_mod.WireVersion
The two decimal digits from the start-line, exactly as sent.
RFC 9112 §2.3: HTTP-version = HTTP-name "/" DIGIT "." DIGIT.
Fields
enum
Version
src
pub const Version = version_mod.Version
The version a recipient processes a message as. A message with major version 1 and a higher minor version is processed as 1.1 (RFC 9110 §6.2).
Fields
@"HTTP/1.0" @"HTTP/1.0"
@"HTTP/1.1" @"HTTP/1.1"
module
chunked
src
Chunked transfer coding, RFC 9112 §7.1 through §7.1.3.
more
The decoder is resumable and allocates nothing. Feed it the body bytes
available so far; it consumes from the front and returns borrowed chunk
data, a trailer section, or need_more. Call next again with the
unconsumed bytes after a read. A chunk data slice may end before the chunk
does; the decoder remembers the remaining count across calls.
The caller owns the trailer storage and the size limit. The decoder parses and ignores chunk extensions, rejects a chunk-size overflow, and returns trailers separately so the caller can decide which ones are mergeable (RFC 9110 §6.5.1).
Constants
value
error{
/// The bytes so far do not complete the next chunk-size line, data, or
/// trailer section. Read more and retry with the unconsumed bytes.
Incomplete,
/// The chunk grammar is malformed.
Invalid,
/// The chunk size does not fit in `u64`.
Overflow,
/// The trailer section does not fit in the caller's `Header` storage.
TooManyHeaders,
}Types
union
Event
src
pub const Event = union(enum)
Fields
data: []const u8
A borrowed slice of chunk data from the front of the input.
trailers: Headers
The trailer section, after the last chunk. This is the final event;
isDone() is true. The headers borrow the input buffer and the
caller's trailer storage.
need_more need_more
The input ended in the middle of a unit. Read more and call again with the unconsumed bytes.
module
ServerConn
src
A sans-I/O HTTP/1 server connection state machine.
more
The connection owns no memory and does no I/O. The caller supplies the request buffer, the field storage, and the response head buffer, then drives the machine from a socket loop:
var conn = h1.ServerConn.init(.{ ... });
conn.start(now); // before the first read
const dst = conn.recvBuffer(); // read into this
switch (conn.received(n, now)) { ... }
conn.respond(.{ .status = .ok }, body);
const slices = conn.sendSlices(); // writev these
switch (conn.sent(n, now)) { ... }
After .keep_alive, call advance before the next read; it parses a
pipelined request already in the buffer. .sent starts the idle timer on
.keep_alive; the first request byte starts the head and total timers.
After .read, read the request body. When .sent returns
.close_after_drain, recvBuffer is empty: read into your own scratch
buffer and discard the bytes. The machine does not route drained bytes.
Close on EOF, or when tick returns .close at the drain deadline. A zero
drain_timeout skips the drain and returns .close instead.
Call eof when the peer closes during a request and tick when the poll
deadline expires; tick covers every phase.
Policy responses are queued by the machine. A handler can review the status
and body through Handler.VTable.policy_response before they are written.
The parser and engine still own the rejection and drain decision.
Request bodies default to the request buffer. A .request_head event lets
the application respond from the head alone or call acceptBody with its
own storage. Body bytes already received are copied there; later reads land
there directly. If the final short read is below min_read_bytes, the
machine uses request-buffer scratch instead. A body that does not fit the
chosen storage is 413.
switchProtocols sends a 101 and ends HTTP for this connection. Chunked
request bodies are not implemented yet and are 501.
Constants
pub const RespondError = …
src
value
h1.Writer.Error || error{
InvalidState,
InvalidStatus,
ResponseHeadTooLarge,
}Functions
Start the idle deadline. Call once after accept, before the first read. Without it a silent connection has no deadline. The first input byte starts the head and total request timeouts.
pub fn negotiatedVersion(self: *const ServerConn) h1.Version
src
The effective request version. A streaming response uses the same version in its status line as the buffered response path.
pub fn recvBuffer(self: *ServerConn) []u8
src
Where the engine should read. Empty while a response, the application, or a drain owns the connection.
Account for a non-empty read into recvBuffer. A zero-byte read is not
data: call eof when the transport reports a clean close. This keeps an
empty transport completion from becoming an accidental 400.
Parse bytes already in the request buffer. Call after .keep_alive before
the next read, so a pipelined request is handled without a syscall.
The peer closed. This is the only zero-byte input path. A clean close
before any request bytes is .closed; a partial head or body is the
policy 400.
The poll deadline fired. Covers every phase. An idle connection with no request bytes closes silently; a started head, body, or application timeout queues a 408. A response or drain timeout closes.
pub fn respond(
self: *ServerConn,
options: ResponseOptions,
body: []const u8,
) RespondError!void
src
Queue a final response after .request or .request_head. The body must
stay valid until the response completes. An early response leaves request
bytes unread and drains them before closing instead of treating them as
another request. A HEAD request sends Content-Length and the head only.
A 204 or 304 response ignores the body and omits Content-Length.
pub fn switchProtocols(self: *ServerConn, options: ResponseOptions) RespondError!void
src
Queue a 101 response after a request head, then hand the stream to the
application. After sent returns .switched, HTTP is finished. Bytes
already read past the head remain available through remainingInput.
The caller owns Connection. Supply Connection: Upgrade when needed; the
machine emits no Connection field for a switch, even when the request says
close.
pub fn reviewRejection(self: *ServerConn, replacement: PolicyResponse) RespondError!void
src
Replace a queued machine rejection with an application response. The response still owns unread input and closes after sending.
pub fn sendSlices(self: *const ServerConn) Slices
src
The next response bytes, in order. Zero-length head means the head is done.
Account for n response bytes written from sendSlices.
pub fn remainingInput(self: *ServerConn) []const u8
src
Bytes already read past the request that produced the 101. The stream
stays open; these bytes are valid while remainingInput is called.
pub fn acceptBody(self: *ServerConn, storage: []u8) Event
src
Accept this request body into memory chosen from the request head. Bytes already received are copied once; subsequent reads go there directly. When it is too small, return a 413 rather than waiting on the default buffer.
pub fn bufferBody(self: *ServerConn) Event
src
Keep the current default behavior: buffer the body in the request buffer.
The earliest active deadline. Null when every timeout is disabled or the
connection is closed. Call start after accept so a silent connection has
a deadline.
Types
enum
RejectReason
src
pub const RejectReason = @import("../engine/common.zig").RejectReason
Why the connection machine selected a rejection. Statuses stay the same; this is the additional information an application needs for its message.
Fields
malformed_head malformed_head
conflicting_length conflicting_length
invalid_length invalid_length
conflicting_framing conflicting_framing
invalid_framing invalid_framing
chunked_unsupported chunked_unsupported
missing_host missing_host
invalid_host invalid_host
too_many_fields too_many_fields
head_too_large head_too_large
body_too_large body_too_large
unsupported_version unsupported_version
unsupported_expectation unsupported_expectation
request_timeout request_timeout
internal_error internal_error
struct
Limits
src
Fields
body_bytes: usize = 64 * 1024
Largest request body an application may accept.
min_read_bytes: usize = 0
Minimum usable space in a read destination. Reject a head without enough room. Reject a default-buffered body without enough room. For the final short read into application-owned body storage, use request buffer scratch and copy only the body bytes across.
idle_timeout: Io.Duration = .fromSeconds(5)
Zero disables the deadline. Starts at accept and after a keep-alive response, while the connection waits for the next request.
head_timeout: Io.Duration = .fromSeconds(5)
Zero disables the deadline. Starts with the first request byte.
body_timeout: Io.Duration = .fromSeconds(10)
Zero disables the deadline.
write_timeout: Io.Duration = .fromSeconds(10)
total_timeout: Io.Duration = .fromSeconds(30)
Zero disables the deadline. Starts with the first request byte.
drain_timeout: Io.Duration = .fromSeconds(1)
Bounds a lingering close after an unread request body. The engine
drains input until the deadline. Zero skips the drain and returns
.close instead.
struct
Config
src
Fields
request_buffer: []u8
headers: []Header
response_buffer: []u8
limits: Limits = .{}
write_chunk_bytes: usize = 0
Coalesce the head and body prefix when they fit. The buffer supplies the copy room and every send slice respects this cap. Zero preserves the separate head/body slices.
struct
ResponseOptions
src
pub const ResponseOptions = @import("../engine/common.zig").ResponseOptions
Extra fields after the machine's. Do not duplicate Content-Length or Connection.
Fields
status: Status = .ok
content_type: ?[]const u8 = null
close: bool = false
For h1, close after this response and emit Connection: close.
h2 Exchange.respond rejects this field; it has no stream-level
Connection-close meaning.
headers: []const Header = &.{}
Extra regular fields. The engine adds framing and protocol fields.
struct
Head
src
Fields
head: h1.RequestHead
body_bytes: usize
The complete Content-Length body size, before choosing a sink.
struct
Request
src
Fields
head: h1.RequestHead
Borrows the request buffer and the caller's header storage. Valid until
the response completes. A request_head head must survive the call to
acceptBody or bufferBody; then its storage must survive this request
event. If the caller responds from request_head without accepting a
body, it may instead copy what it needs and reuse the header storage
after the response. No field slice survives that reuse.
body: []const u8
A slice of the chosen body storage. Valid until the response completes.
union
Event
src
pub const Event = union(enum)
Fields
need_more need_more
Read more bytes into recvBuffer, after draining sendSlices.
request_head: Head
The request head is complete but the body is not. Respond immediately
to refuse or switch protocols, or call acceptBody or bufferBody.
request: Request
A complete request. Respond, then drain sendSlices.
reject: Rejection
The machine queued this policy response. Send it, then close. sent
reports whether the engine must drain input before the close.
closed closed
The peer closed with no partial request.
union
Done
src
pub const Done = union(enum)
Fields
more more
More response bytes remain.
read read
The interim response is complete; read the request body.
keep_alive keep_alive
The response is complete and the connection can parse the next request.
close close
The response is complete and the connection must close.
switched switched
A 101 response is complete. The stream belongs to the next protocol;
remainingInput returns bytes read past the request, and HTTP
deadlines no longer apply.
close_after_drain close_after_drain
The response is complete. The request body was not fully read; drain
the raw socket into scratch storage until deadline expires or the
peer closes, so a reset does not discard the response. recvBuffer is
empty in this phase. Limits.drain_timeout bounds the drain; zero
skips it and returns .close.
module
ClientConn
src
A sans-I/O HTTP/1 client connection.
more
The caller owns the receive buffer and header storage, writes the request
with h1.Writer, feeds response bytes, and consumes borrowed body slices.
The machine buffers only the current receive window; it never allocates and
never buffers the whole body.
One request is in flight at a time. A final response, or an interim 1xx
head, is returned as Event.response. Body bytes follow as Event.body;
Event.done ends the response. Leftover bytes after done belong to the
next response, and start preserves them.
Constants
value
h1.Error || h1.framing.Error || h1.chunked.Error || error{
HeadTooLarge,
BodyTooLarge,
}Functions
Begin the next response. Call before writing a request and after done.
Leftover bytes from the previous response stay at the front of the receive
buffer.
pub fn recvBuffer(self: *ClientConn) []u8
src
Where the caller reads. Empty when body bytes are still unconsumed or the response is complete.
Account for a non-empty read into recvBuffer. A zero-byte read is not
data: call eof when the transport reports a clean close.
Process bytes already in the receive buffer. Call after Event.response
to get body data, or after Event.body to get the next slice.
The peer closed. A clean close before any bytes is closed; a close in the
middle of a head or length/chunked body is an error.
Types
struct
Config
src
Fields
recv_buffer: []u8
Caller-owned receive window. The caller reads into recvBuffer().
headers: []Header
Caller-owned field storage for the current response head.
trailers: []Header = &.{}
Caller-owned storage for chunked trailers. Empty rejects trailers.
body_bytes: u64 = 16 * 1024 * 1024
Maximum body bytes delivered to the caller.
union
Event
src
pub const Event = union(enum)
Fields
need_more need_more
Read more bytes into recvBuffer and call either received.
response: Response
A response head. Call next for its body.
body: []const u8
A borrowed body slice, valid until the next call to the machine.
done done
The response is complete. Call start for the next request.
closed closed
The peer closed before any response bytes.
module
client
src
HTTP/1 client adapters.
Types
module
blocking
src
A blocking HTTP/1 client over std.Io.net.
The adapter writes one request with h1.Writer, feeds the response into
h1.ClientConn, and copies the body into a caller-owned buffer. It does
not allocate, does not own the stream, and does not reuse the connection.
Functions
Write one request and read one response. The caller owns the stream and
must close it. Payload and field slices in Response borrow config.
pub fn fetchIo(
reader: *Io.Reader,
writer: *Io.Writer,
request: Request,
config: Config,
) !Response
src
Write one request and read one response over caller-owned plaintext reader and writer interfaces. TLS adapters call this after the handshake.
Types
struct
Config
src
Fields
recv_buffer: []u8
Receive window for response heads and body bytes.
headers: []Header
Field storage for the response head.
trailers: []Header = &.{}
Trailer storage for chunked responses. Empty rejects trailers.
body_buffer: []u8
Destination for the complete response body.
write_buffer: []u8
Writer buffer for the request.
module
streaming
src
A blocking HTTP/1 client with a streaming response body.
The client writes one request, parses the response head into a per-response
arena, and exposes body bytes through the shared client Body interface.
It does not own the transport; the caller closes the stream.
Constants
Body failures carry no payload. A reset code or local cause is available from the Body that returned the error.
Types
struct
Request
src
pub const Request = common.Request
Fields
method_token: []const u8
The exact wire method token. Methods are case-sensitive and the registry is open.
scheme: Scheme
authority: []const u8
h1 sends this as Host; h2 sends it as :authority.
target: []const u8
The origin-form path and query. h2 sends it as :path.
headers: []const Header = &.{}
Regular fields only, in wire order. Never include Host or an h2
pseudo-field. A caller targeting both protocols must send lowercase
names: h1 forwards them as-is, while h2 rejects uppercase as
InvalidHeader.
body: []const u8 = ""
Request body bytes. h1 sends them with Content-Length; h2 sends DATA
inside the request stream. Empty is the common case. When h2 returns an
early final response before the body is sent, this slice stays valid
until Body.close.
struct
Body
src
pub const Body = common.Body
The blocking-client body contract.
read may block, never returns error.WouldBlock, never returns empty
data, and reports completion with eof. Holding four 16 KiB slices
exhausts the default 65,535-byte h2 connection window.
Fields
Functions
pub fn localCause(self: Body) ?Local
src
Types
struct
VTable
src
Fields
read: *const fn (ptr: *anyopaque) BodyError!BodyResult
Return the next body bytes or eof. The borrowed slice is valid
until any operation that advances the connection; a caller that
holds it across another read must copy. Holding an h2 slice pins
the connection window for every stream.
resetCode: *const fn (ptr: *anyopaque) ?u32
The RST_STREAM code when read returned error.Reset.
localCause: *const fn (ptr: *anyopaque) ?Local
The local cause when read returned a Local* error.
close: *const fn (ptr: *anyopaque) void
Finish the response. Idempotent. Releases unread flow control and resets an incomplete stream where the protocol supports it.
struct
BodyResult
src
pub const BodyResult = common.BodyResult
A successful body read. The slice is borrowed for the connection operation and is never empty.
Fields
data: []const u8
enum
Local
src
pub const Local = common.Local
A local failure, not a peer outcome. The enum is closed: a new local
failure adds a variant rather than reusing protocol.
Fields
canceled canceled
Our cancellation. The stream is unfinished; close or reset the stream and keep the connection.
capacity capacity
A local buffer or policy limit ended this response. Reset the stream; the connection survives and this request is done.
out_of_memory out_of_memory
Allocation failed. The connection is not usable; close it.
unusable unusable
A local failure left a partial frame on the wire. The connection is not usable; close it. A blocking adapter must not produce this.
struct
Config
src
pub const Config = common.Config
Fields
allocator: Allocator
max_header_bytes: usize = 16 * 1024
Maximum snapshot bytes for response field names and values.
max_headers: usize = 128
Maximum response field records.
struct
Client
src
Fields
allocator: Allocator
reader: *Io.Reader
writer: *Io.Writer
arena: std.heap.ArenaAllocator
conn: h1.ClientConn
recv: [16 * 1024]u8
header_storage: []Header
max_header_bytes: usize
max_headers: usize
active: bool
body_eof: bool
body_local: ?Local
dirty: bool
pending: []const u8
Functions
module
engine
src
Server engines over the sans-I/O HTTP/1 connection machine.
The parser modules stay free of sockets. Each engine owns a transport and
drives h1.ServerConn; the connection machine owns no memory and does no
I/O.
Constants
A type-erased HTTP handler over this engine's exchange.
Types
module
blocking
src
A blocking HTTP/1 server engine over std.Io.
One connection is served at a time. The engine uses the Io runtime's blocking network streams, so it cannot enforce the connection deadlines; all timeouts are zero. Use it for tools, tests, and trusted peers. The evented engines are separate.
Constants
A type-erased HTTP handler over this engine's exchange.
Functions
Accept connections until an unrecoverable accept error.
Serve one connection until it closes.
pub fn serveIo(
io: Io,
reader: *Io.Reader,
writer: *Io.Writer,
handler: Handler,
config: Config,
) !void
src
Serve one connection over a caller-owned plaintext reader and writer.
TLS adapters call this after the handshake; the stream adapters call it
from serve.
Types
struct
Exchange
src
pub const Exchange = @import("common.zig").Exchange
One request/response exchange handed to the application.
Fields
id: usize
The engine slot serving this exchange. Stable for the exchange; the
slot is reused after complete returns.
conn: *h1.ServerConn
request: h1.ServerConn.Request
response_stream: ?stream.ResponseStream = null
response_options: ?h1.ServerConn.ResponseOptions = null
deferred: ?common.Deferred(Exchange) = null
deferred_deadline: ?Io.Timestamp = null
deferred_on_timeout: ?PolicyResponse = null
waker: ?common.Waker = null
defer_supported: bool = false
Set by the readiness engine. The blocking engine has no wake loop.
arena: Allocator
Engine-owned allocator for this exchange. It is valid from handler
entry until complete returns. The engine resets it before the slot
serves another request.
io: Io
The engine's Io. Deferred deadlines start at the deferResponse call.
Functions
pub fn respond(
self: *Exchange,
options: h1.ServerConn.ResponseOptions,
body: []const u8,
) h1.ServerConn.RespondError!void
src
The body may borrow arena or handler-owned memory. Either way, the
bytes must stay valid until the response completes.
pub fn respondStream(
self: *Exchange,
options: h1.ServerConn.ResponseOptions,
source: stream.ResponseStream,
) (h1.ServerConn.RespondError || Allocator.Error)!void
src
Register a pull-based response body. The engine writes the head and
pulls chunks after handle returns. content_length controls framing.
pub fn deferResponse(
self: *Exchange,
context: *anyopaque,
callback: *const fn (
context: *anyopaque,
exchange: *Exchange,
) anyerror!common.ResumeResult,
cancel: *const fn (context: *anyopaque) void,
defer_options: common.DeferOptions,
) (error{ Unsupported, InvalidState } || Allocator.Error)!void
src
Park the exchange until external work completes. The handler returns
without responding; the engine calls callback on its own thread after
wake. The context must live until complete. cancel runs if the
client disconnects or the request deadline expires before the response.
Only readiness engines support this; the blocking engine returns
error.Unsupported.
pub fn wakeHandle(self: *const Exchange) ?common.Waker
src
Copy this handle before returning from handle if a worker may outlive
the exchange. The copy remains valid until the server is deinitialized;
the exchange field is cleared when the slot is reset. Calling wake on
a completed exchange reads that cleared field.
Wake the engine so it resumes a deferred exchange. Safe from another thread while the exchange is alive.
struct
PolicyContext
src
pub const PolicyContext = @import("common.zig").PolicyContext
The context for a machine rejection. The head may not exist yet.
Fields
struct
PolicyResponse
src
pub const PolicyResponse = @import("common.zig").PolicyResponse
A replacement for the default status-and-empty-body rejection.
Fields
struct
Config
src
Buffers and policy for one connection. The request buffer holds the head and body.
Fields
allocator: mem.Allocator
request_buffer: []u8
headers: []Header
response_head_buffer: []u8
common: common.Config = .{}
write_chunk_bytes: usize = 0
Coalesce and cap response chunks. Zero preserves separate head/body.
module
poll
src
A poll(2)-based HTTP/1 server engine.
The readiness loop is shared with epoll; this module supplies the poll(2) registration and wait backend.
Constants
A type-erased HTTP handler over this engine's exchange.
Types
struct
Config
src
pub const Config = readiness.Config
Fields
allocator: Allocator
max_connections: usize
common: common.Config = .{}
request_buffer_bytes: usize = 16 * 1024
headers: usize = 64
response_head_bytes: usize = 1024
write_chunk_bytes: usize = 0
Coalesce and cap response chunks. Zero preserves separate head/body.
drain_bytes: usize = 4 * 1024
Scratch for the close-after-drain phase. Zero skips the allocation;
a zero Limits.drain_timeout also skips the phase and this buffer.
limits: h1.ServerConn.Limits = .{}
accept_backoff: Io.Duration = .fromMilliseconds(50)
tcp_defer_accept: bool = false
When every slot is busy, new connections stay in the accept backlog until one frees; they are not accepted and closed.
Linux TCP_DEFER_ACCEPT. The listener wakes only when the peer sends
data, not when the handshake completes. Other platforms ignore it.
struct
Exchange
src
pub const Exchange = readiness.Exchange
One request/response exchange handed to the application.
Fields
id: usize
The engine slot serving this exchange. Stable for the exchange; the
slot is reused after complete returns.
conn: *h1.ServerConn
request: h1.ServerConn.Request
response_stream: ?stream.ResponseStream = null
response_options: ?h1.ServerConn.ResponseOptions = null
deferred: ?common.Deferred(Exchange) = null
deferred_deadline: ?Io.Timestamp = null
deferred_on_timeout: ?PolicyResponse = null
waker: ?common.Waker = null
defer_supported: bool = false
Set by the readiness engine. The blocking engine has no wake loop.
arena: Allocator
Engine-owned allocator for this exchange. It is valid from handler
entry until complete returns. The engine resets it before the slot
serves another request.
io: Io
The engine's Io. Deferred deadlines start at the deferResponse call.
Functions
pub fn respond(
self: *Exchange,
options: h1.ServerConn.ResponseOptions,
body: []const u8,
) h1.ServerConn.RespondError!void
src
The body may borrow arena or handler-owned memory. Either way, the
bytes must stay valid until the response completes.
pub fn respondStream(
self: *Exchange,
options: h1.ServerConn.ResponseOptions,
source: stream.ResponseStream,
) (h1.ServerConn.RespondError || Allocator.Error)!void
src
Register a pull-based response body. The engine writes the head and
pulls chunks after handle returns. content_length controls framing.
pub fn deferResponse(
self: *Exchange,
context: *anyopaque,
callback: *const fn (
context: *anyopaque,
exchange: *Exchange,
) anyerror!common.ResumeResult,
cancel: *const fn (context: *anyopaque) void,
defer_options: common.DeferOptions,
) (error{ Unsupported, InvalidState } || Allocator.Error)!void
src
Park the exchange until external work completes. The handler returns
without responding; the engine calls callback on its own thread after
wake. The context must live until complete. cancel runs if the
client disconnects or the request deadline expires before the response.
Only readiness engines support this; the blocking engine returns
error.Unsupported.
pub fn wakeHandle(self: *const Exchange) ?common.Waker
src
Copy this handle before returning from handle if a worker may outlive
the exchange. The copy remains valid until the server is deinitialized;
the exchange field is cleared when the slot is reset. Calling wake on
a completed exchange reads that cleared field.
Wake the engine so it resumes a deferred exchange. Safe from another thread while the exchange is alive.
struct
PolicyContext
src
pub const PolicyContext = readiness.PolicyContext
The context for a machine rejection. The head may not exist yet.
Fields
struct
PolicyResponse
src
pub const PolicyResponse = readiness.PolicyResponse
A replacement for the default status-and-empty-body rejection.
Fields
module
epoll
src
An epoll(7)-based HTTP/1 server engine.
The readiness loop is shared with poll; this module supplies the epoll registration and wait backend.
Constants
A type-erased HTTP handler over this engine's exchange.
Types
struct
Config
src
pub const Config = readiness.Config
Fields
allocator: Allocator
max_connections: usize
common: common.Config = .{}
request_buffer_bytes: usize = 16 * 1024
headers: usize = 64
response_head_bytes: usize = 1024
write_chunk_bytes: usize = 0
Coalesce and cap response chunks. Zero preserves separate head/body.
drain_bytes: usize = 4 * 1024
Scratch for the close-after-drain phase. Zero skips the allocation;
a zero Limits.drain_timeout also skips the phase and this buffer.
limits: h1.ServerConn.Limits = .{}
accept_backoff: Io.Duration = .fromMilliseconds(50)
tcp_defer_accept: bool = false
When every slot is busy, new connections stay in the accept backlog until one frees; they are not accepted and closed.
Linux TCP_DEFER_ACCEPT. The listener wakes only when the peer sends
data, not when the handshake completes. Other platforms ignore it.
struct
Exchange
src
pub const Exchange = readiness.Exchange
One request/response exchange handed to the application.
Fields
id: usize
The engine slot serving this exchange. Stable for the exchange; the
slot is reused after complete returns.
conn: *h1.ServerConn
request: h1.ServerConn.Request
response_stream: ?stream.ResponseStream = null
response_options: ?h1.ServerConn.ResponseOptions = null
deferred: ?common.Deferred(Exchange) = null
deferred_deadline: ?Io.Timestamp = null
deferred_on_timeout: ?PolicyResponse = null
waker: ?common.Waker = null
defer_supported: bool = false
Set by the readiness engine. The blocking engine has no wake loop.
arena: Allocator
Engine-owned allocator for this exchange. It is valid from handler
entry until complete returns. The engine resets it before the slot
serves another request.
io: Io
The engine's Io. Deferred deadlines start at the deferResponse call.
Functions
pub fn respond(
self: *Exchange,
options: h1.ServerConn.ResponseOptions,
body: []const u8,
) h1.ServerConn.RespondError!void
src
The body may borrow arena or handler-owned memory. Either way, the
bytes must stay valid until the response completes.
pub fn respondStream(
self: *Exchange,
options: h1.ServerConn.ResponseOptions,
source: stream.ResponseStream,
) (h1.ServerConn.RespondError || Allocator.Error)!void
src
Register a pull-based response body. The engine writes the head and
pulls chunks after handle returns. content_length controls framing.
pub fn deferResponse(
self: *Exchange,
context: *anyopaque,
callback: *const fn (
context: *anyopaque,
exchange: *Exchange,
) anyerror!common.ResumeResult,
cancel: *const fn (context: *anyopaque) void,
defer_options: common.DeferOptions,
) (error{ Unsupported, InvalidState } || Allocator.Error)!void
src
Park the exchange until external work completes. The handler returns
without responding; the engine calls callback on its own thread after
wake. The context must live until complete. cancel runs if the
client disconnects or the request deadline expires before the response.
Only readiness engines support this; the blocking engine returns
error.Unsupported.
pub fn wakeHandle(self: *const Exchange) ?common.Waker
src
Copy this handle before returning from handle if a worker may outlive
the exchange. The copy remains valid until the server is deinitialized;
the exchange field is cleared when the slot is reset. Calling wake on
a completed exchange reads that cleared field.
Wake the engine so it resumes a deferred exchange. Safe from another thread while the exchange is alive.
struct
PolicyContext
src
pub const PolicyContext = readiness.PolicyContext
The context for a machine rejection. The head may not exist yet.
Fields
struct
PolicyResponse
src
pub const PolicyResponse = readiness.PolicyResponse
A replacement for the default status-and-empty-body rejection.
Fields
module
kqueue
src
A kqueue(2)-based HTTP/1 server engine.
The readiness loop is shared with poll and epoll; this module supplies the Darwin transport edge and the kqueue registration and wait backend.
Constants
A type-erased HTTP handler over this engine's exchange.
Types
struct
Config
src
pub const Config = readiness.Config
Fields
allocator: Allocator
max_connections: usize
common: common.Config = .{}
request_buffer_bytes: usize = 16 * 1024
headers: usize = 64
response_head_bytes: usize = 1024
write_chunk_bytes: usize = 0
Coalesce and cap response chunks. Zero preserves separate head/body.
drain_bytes: usize = 4 * 1024
Scratch for the close-after-drain phase. Zero skips the allocation;
a zero Limits.drain_timeout also skips the phase and this buffer.
limits: h1.ServerConn.Limits = .{}
accept_backoff: Io.Duration = .fromMilliseconds(50)
tcp_defer_accept: bool = false
When every slot is busy, new connections stay in the accept backlog until one frees; they are not accepted and closed.
Linux TCP_DEFER_ACCEPT. The listener wakes only when the peer sends
data, not when the handshake completes. Other platforms ignore it.
struct
Exchange
src
pub const Exchange = readiness.Exchange
One request/response exchange handed to the application.
Fields
id: usize
The engine slot serving this exchange. Stable for the exchange; the
slot is reused after complete returns.
conn: *h1.ServerConn
request: h1.ServerConn.Request
response_stream: ?stream.ResponseStream = null
response_options: ?h1.ServerConn.ResponseOptions = null
deferred: ?common.Deferred(Exchange) = null
deferred_deadline: ?Io.Timestamp = null
deferred_on_timeout: ?PolicyResponse = null
waker: ?common.Waker = null
defer_supported: bool = false
Set by the readiness engine. The blocking engine has no wake loop.
arena: Allocator
Engine-owned allocator for this exchange. It is valid from handler
entry until complete returns. The engine resets it before the slot
serves another request.
io: Io
The engine's Io. Deferred deadlines start at the deferResponse call.
Functions
pub fn respond(
self: *Exchange,
options: h1.ServerConn.ResponseOptions,
body: []const u8,
) h1.ServerConn.RespondError!void
src
The body may borrow arena or handler-owned memory. Either way, the
bytes must stay valid until the response completes.
pub fn respondStream(
self: *Exchange,
options: h1.ServerConn.ResponseOptions,
source: stream.ResponseStream,
) (h1.ServerConn.RespondError || Allocator.Error)!void
src
Register a pull-based response body. The engine writes the head and
pulls chunks after handle returns. content_length controls framing.
pub fn deferResponse(
self: *Exchange,
context: *anyopaque,
callback: *const fn (
context: *anyopaque,
exchange: *Exchange,
) anyerror!common.ResumeResult,
cancel: *const fn (context: *anyopaque) void,
defer_options: common.DeferOptions,
) (error{ Unsupported, InvalidState } || Allocator.Error)!void
src
Park the exchange until external work completes. The handler returns
without responding; the engine calls callback on its own thread after
wake. The context must live until complete. cancel runs if the
client disconnects or the request deadline expires before the response.
Only readiness engines support this; the blocking engine returns
error.Unsupported.
pub fn wakeHandle(self: *const Exchange) ?common.Waker
src
Copy this handle before returning from handle if a worker may outlive
the exchange. The copy remains valid until the server is deinitialized;
the exchange field is cleared when the slot is reset. Calling wake on
a completed exchange reads that cleared field.
Wake the engine so it resumes a deferred exchange. Safe from another thread while the exchange is alive.
struct
PolicyContext
src
pub const PolicyContext = readiness.PolicyContext
The context for a machine rejection. The head may not exist yet.
Fields
struct
PolicyResponse
src
pub const PolicyResponse = readiness.PolicyResponse
A replacement for the default status-and-empty-body rejection.
Fields
struct
Exchange
src
pub const Exchange = @import("common.zig").Exchange
One request/response exchange handed to the application.
Fields
id: usize
The engine slot serving this exchange. Stable for the exchange; the
slot is reused after complete returns.
conn: *h1.ServerConn
request: h1.ServerConn.Request
response_stream: ?stream.ResponseStream = null
response_options: ?h1.ServerConn.ResponseOptions = null
deferred: ?common.Deferred(Exchange) = null
deferred_deadline: ?Io.Timestamp = null
deferred_on_timeout: ?PolicyResponse = null
waker: ?common.Waker = null
defer_supported: bool = false
Set by the readiness engine. The blocking engine has no wake loop.
arena: Allocator
Engine-owned allocator for this exchange. It is valid from handler
entry until complete returns. The engine resets it before the slot
serves another request.
io: Io
The engine's Io. Deferred deadlines start at the deferResponse call.
Functions
pub fn respond(
self: *Exchange,
options: h1.ServerConn.ResponseOptions,
body: []const u8,
) h1.ServerConn.RespondError!void
src
The body may borrow arena or handler-owned memory. Either way, the
bytes must stay valid until the response completes.
pub fn respondStream(
self: *Exchange,
options: h1.ServerConn.ResponseOptions,
source: stream.ResponseStream,
) (h1.ServerConn.RespondError || Allocator.Error)!void
src
Register a pull-based response body. The engine writes the head and
pulls chunks after handle returns. content_length controls framing.
pub fn deferResponse(
self: *Exchange,
context: *anyopaque,
callback: *const fn (
context: *anyopaque,
exchange: *Exchange,
) anyerror!common.ResumeResult,
cancel: *const fn (context: *anyopaque) void,
defer_options: common.DeferOptions,
) (error{ Unsupported, InvalidState } || Allocator.Error)!void
src
Park the exchange until external work completes. The handler returns
without responding; the engine calls callback on its own thread after
wake. The context must live until complete. cancel runs if the
client disconnects or the request deadline expires before the response.
Only readiness engines support this; the blocking engine returns
error.Unsupported.
pub fn wakeHandle(self: *const Exchange) ?common.Waker
src
Copy this handle before returning from handle if a worker may outlive
the exchange. The copy remains valid until the server is deinitialized;
the exchange field is cleared when the slot is reset. Calling wake on
a completed exchange reads that cleared field.
Wake the engine so it resumes a deferred exchange. Safe from another thread while the exchange is alive.
module
framing
src
HTTP/1.1 message body framing (RFC 9112 §6.3).
more
The head is parsed; this decides how many octets follow it. The decision is
pure: it reads the field section and the request context, and returns a
RequestBody or a ResponseBody. It does not read the body.
Order matters. The no-body rules for HEAD, 1xx, 204, and 304 come first, then the CONNECT tunnel rule, then the Transfer-Encoding and Content-Length rules.
Constants
value
error{
/// Transfer-Encoding is present on an HTTP/1.0 message (RFC 9112 §6.1).
FaultyFraming,
/// Transfer-Encoding and Content-Length both frame the message.
ConflictingFraming,
/// Transfer-Encoding is malformed, repeats chunked, or gives it
/// parameters. A request whose final coding is not chunked also lands
/// here.
InvalidTransferEncoding,
/// Content-Length is malformed, conflicting, or overflows `u64`.
InvalidContentLength,
}Functions
The body framing for a request head.
pub fn response(
version: Version,
status: Status,
request_method: ?Method,
headers: Headers,
) Error!ResponseBody
src
The body framing for a response head. request_method is the method of the
request this response answers, or null when it is not known.
Types
union
RequestBody
src
pub const RequestBody = union(enum)
The body framing of a request. A request is never close-delimited or a tunnel.
Fields
module
Writer
src
HTTP/1 writer methods over a caller-owned std.Io.Writer.
more
The writer carries the sink. The caller owns it: a fixed buffer, a file, a
socket, or anything else that implements std.Io.Writer. These methods only
format; they never create, flush, or close the sink, and they never
allocate.
The checked methods validate the parser's grammar before writing. The
assume* variants skip that validation for data the caller already trusts;
they write the bytes as given. Use the checked form unless the input is
static or validated elsewhere.
Constants
value
Io.Writer.Error || error{
InvalidMethod,
InvalidTarget,
InvalidFieldName,
InvalidFieldValue,
InvalidReason,
EmptyChunk,
}Functions
pub fn requestLine(
self: Writer,
method: Method,
target: []const u8,
version: Version,
) Error!void
src
Write a request line for one of the common methods.
pub fn requestLineToken(
self: Writer,
method_token: []const u8,
target: []const u8,
version: Version,
) Error!void
src
Write a request line for an arbitrary method token.
pub fn assumeRequestLine(
self: Writer,
method: Method,
target: []const u8,
version: Version,
) Error!void
src
requestLine without validation.
pub fn assumeRequestLineToken(
self: Writer,
method_token: []const u8,
target: []const u8,
version: Version,
) Error!void
src
requestLineToken without validation.
pub fn statusLine(self: Writer, version: Version, status: Status) Error!void
src
Write a status line with the canonical reason phrase. An unnamed status writes an empty reason; the separating SP is still present.
pub fn statusLineReason(
self: Writer,
version: Version,
status: Status,
reason: []const u8,
) Error!void
src
Write a status line with a caller-supplied reason phrase.
pub fn assumeStatusLine(self: Writer, version: Version, status: Status) Error!void
src
statusLine without validation.
pub fn assumeStatusLineReason(
self: Writer,
version: Version,
status: Status,
reason: []const u8,
) Error!void
src
statusLineReason without validation.
Write one field line.
pub fn assumeField(self: Writer, name: []const u8, value: []const u8) Error!void
src
field without validation.
Write the empty line that ends the field section.
pub fn contentLength(self: Writer, length: u64) Error!void
src
Write a Content-Length field line.
Write body bytes. The body is opaque; no validation applies.
Write the Transfer-Encoding: chunked field line.
Write one non-final chunk.
Write the last chunk and the trailer section. An empty trailer list
writes 0\r\n\r\n.
pub fn assumeLastChunk(self: Writer, trailers: []const Header) Error!void
src
lastChunk without validation.
struct
SearchState
src
The incremental search state for a head or field section.
more
Keep one per connection buffer. Pass &state to parseRequest,
parseResponse, or parseFieldSection. The parser records how much it
searched on error.Incomplete and clears the state after a complete parse,
so the caller never passes a length and never resets it for the next
message.
Fields
searched: u32
Constants
struct
RequestHead
src
A parsed request head.
more
The head borrows. method_token, target, and every headers.items name
and value point into the buffer passed to parseRequest; headers.items
itself points into the caller's Header storage. The value is valid only
while both are unchanged: reading more bytes into the buffer, rebasing it,
or reusing the storage invalidates it. Copy anything that must outlive the
parse.
method, version, and wire_version are plain values.
Fields
method: ?Method
The common method, or null when the wire token is not one of them.
method_token: []const u8
The exact wire token, always. Methods are case-sensitive (RFC 9110 §9.1), and the registry is open.
target: []const u8
version: Version
The effective version, after RFC 9110 §6.2.
wire_version: WireVersion
The exact digits from the wire. HTTP/1.9 is .{ .major = 1, .minor = 9 }
here, while version is HTTP/1.1.
headers: Headers
struct
ResponseHead
src
A parsed response head.
The same lifetime rule as RequestHead: reason and the field lines
borrow the input buffer, and headers.items borrows the caller's storage.
Copy anything that must outlive the parse.
Fields
version: Version
The effective version, after RFC 9110 §6.2.
wire_version: WireVersion
The exact digits from the wire.
status: Status
The three digits as an open Status. RFC 9110 §15 makes 100..599 the
meaningful range; this parser returns what the wire said and leaves the
range check to the caller.
reason: []const u8
headers: Headers
struct
ParsedRequest
src
A request head and the number of bytes it occupied.
The head borrows the input buffer and the caller's header storage; see
RequestHead.
Fields
struct
ParsedResponse
src
A response head and the number of bytes it occupied.
The head borrows the input buffer and the caller's header storage; see
ResponseHead.
Fields
struct
ParsedFieldSection
src
A parsed field section and the number of bytes it occupied.
The headers borrow the input buffer and the caller's storage, exactly as a
RequestHead does; see that type for the lifetime rule.
Fields
module
h2
src
HTTP/2 (RFC 9113), h2c and h2 over TLS alike. Sans-I/O.
The frame codec is the first layer: frame headers, typed payloads, and an
outbound writer over a caller buffer. Session composes it with settings,
stream state, flow control, HPACK, and CONTINUATION assembly.
Types
module
client
src
HTTP/2 client adapters.
Types
module
blocking
src
A blocking HTTP/2 client with a streaming response body.
more
The adapter owns a h2.Session, serializes one request at a time, and
snapshots response header names and values into the per-response arena.
Body reads drive both directions of the session so SETTINGS acknowledgments
and WINDOW_UPDATE frames are flushed while the response body is consumed.
The request, response, body, and config types come from the shared client
contract; h1.client.streaming implements the same contract.
Types
struct
Request
src
pub const Request = shared.Request
Fields
method_token: []const u8
The exact wire method token. Methods are case-sensitive and the registry is open.
scheme: Scheme
authority: []const u8
h1 sends this as Host; h2 sends it as :authority.
target: []const u8
The origin-form path and query. h2 sends it as :path.
headers: []const Header = &.{}
Regular fields only, in wire order. Never include Host or an h2
pseudo-field. A caller targeting both protocols must send lowercase
names: h1 forwards them as-is, while h2 rejects uppercase as
InvalidHeader.
body: []const u8 = ""
Request body bytes. h1 sends them with Content-Length; h2 sends DATA
inside the request stream. Empty is the common case. When h2 returns an
early final response before the body is sent, this slice stays valid
until Body.close.
struct
Body
src
pub const Body = shared.Body
The blocking-client body contract.
read may block, never returns error.WouldBlock, never returns empty
data, and reports completion with eof. Holding four 16 KiB slices
exhausts the default 65,535-byte h2 connection window.
Fields
Functions
pub fn localCause(self: Body) ?Local
src
Types
struct
VTable
src
Fields
read: *const fn (ptr: *anyopaque) BodyError!BodyResult
Return the next body bytes or eof. The borrowed slice is valid
until any operation that advances the connection; a caller that
holds it across another read must copy. Holding an h2 slice pins
the connection window for every stream.
resetCode: *const fn (ptr: *anyopaque) ?u32
The RST_STREAM code when read returned error.Reset.
localCause: *const fn (ptr: *anyopaque) ?Local
The local cause when read returned a Local* error.
close: *const fn (ptr: *anyopaque) void
Finish the response. Idempotent. Releases unread flow control and resets an incomplete stream where the protocol supports it.
struct
Config
src
pub const Config = shared.Config
Fields
allocator: Allocator
max_header_bytes: usize = 16 * 1024
Maximum snapshot bytes for response field names and values.
max_headers: usize = 128
Maximum response field records.
struct
Client
src
Fields
allocator: Allocator
reader: *Io.Reader
writer: *Io.Writer
arena: std.heap.ArenaAllocator
max_header_bytes: usize
max_headers: usize
session: Session
stream_id: u31 = 0
active: bool = false
remote_end: bool = false
The peer sent END_STREAM for the current response.
body_eof: bool = false
All response bytes have been delivered to the caller.
reset_sent: bool = false
A reset was queued for the current stream by close.
request_state: RequestState = .idle
Staged until the request's END_HEADERS reaches the writer.
connection_poisoned: bool = false
A write or connection failure poisoned this client.
output_written: u64 = 0
Bytes successfully handed to the writer.
pending: []const u8 = &.{}
Borrowed from the current session event until it is consumed.
pending_flow: u32 = 0
Flow-control charge for pending, including padding.
net_buffer: [4096]u8 = undefined
Unfed transport bytes, kept when the session input buffer is full.
net_start: usize = 0
net_end: usize = 0
body: Body = undefined
goaway: ?shared.GoAway = null
early_head: ?ResponseHead = null
Response head received while the request body was still being sent.
pending_body: []const u8 = &.{}
Request-body bytes still to send after an early response head.
body_reset_code: ?u32 = null
RST_STREAM code when the last read returned error.Reset.
body_local: ?shared.Local = null
Local cause when the last read returned a Local* error.
Functions
Initialize in its final location. The caller owns the reader, writer, header storage, and allocator.
pub fn commitState(self: *const Client) RequestState
src
pub fn isConnectionPoisoned(self: *const Client) bool
src
The connection-level GOAWAY boundary, if one arrived. Compare with
streamId(): a body result of truncated above this boundary was not
processed by the peer.
The last request's stream id. Preserved after Body.close so a caller
can compare it with goAway().last_stream_id.
Send one request, including its body, and wait for the response head.
module
engine
src
Server engines over the sans-I/O HTTP/2 session.
The session owns protocol state; an engine owns the transport, the request
buffering, and the handler loop. blocking is the reference engine; the
readiness engines share the same exchange core.
Constants
A type-erased HTTP/2 handler over this engine's exchange.
Types
module
blocking
src
A blocking HTTP/2 server engine over std.Io.
The engine owns the transport loop, not the exchange state. It feeds the
shared Connection core, flushes queued output, and blocks on reads. One
connection is served at a time; the h2c example adds a thread per
connection. No deadlines, like h1.engine.blocking.
Constants
A type-erased HTTP/2 handler over this engine's exchange.
Functions
Accept connections until an unrecoverable accept error.
Serve one connection over a transport stream.
pub fn serveIo(
reader: *Io.Reader,
writer: *Io.Writer,
handler: Handler,
config: Config,
) !void
src
Serve one connection over an already-open reader/writer pair. This is the
TLS seam: a tls_std server hands its reader and writer here, and the h2
session runs over decrypted bytes.
Types
struct
Exchange
src
pub const Exchange = common.Exchange
Fields
id: usize
The engine's stream slot. Stable until complete, then reused.
stream_id: u31
request: Request
arena: Allocator
Per-stream storage for the request and any handler allocations.
response: ?Response = null
Constants
pub const RespondError = …
src
value
error{
AlreadyResponded,
InvalidResponse,
}Functions
pub fn respond(
self: *Exchange,
options: ResponseOptions,
body: []const u8,
) RespondError!void
src
Record the response. The engine sends the HEADERS and pumps body under
flow control. The body must stay valid until complete returns. The
extra headers are borrowed for the same lifetime.
Types
struct
ResponseOptions
src
pub const ResponseOptions = common.ResponseOptions
The response fields the application controls.
Fields
status: Status = .ok
content_type: ?[]const u8 = null
close: bool = false
For h1, close after this response and emit Connection: close.
h2 Exchange.respond rejects this field; it has no stream-level
Connection-close meaning.
headers: []const Header = &.{}
Extra regular fields. The engine adds framing and protocol fields.
struct
Config
src
Fields
allocator: Allocator
read_buffer: []u8
Transport read scratch fed to Connection.receive.
write_buffer: []u8
The std.Io writer's buffer.
max_streams: u16 = 128
Engine slot count and the session's MAX_CONCURRENT_STREAMS.
common: shared.Config = .{ .arena_retain_bytes = 4 * 1024 }
session: Options = .{ .role = .server }
module
poll
src
A poll(2)-based HTTP/2 server engine.
The readiness loop is shared with epoll; this module supplies the poll(2) registration and wait backend.
Constants
A type-erased HTTP/2 handler over this engine's exchange.
Types
struct
Exchange
src
pub const Exchange = readiness.Exchange
Fields
id: usize
The engine's stream slot. Stable until complete, then reused.
stream_id: u31
request: Request
arena: Allocator
Per-stream storage for the request and any handler allocations.
response: ?Response = null
Constants
pub const RespondError = …
src
value
error{
AlreadyResponded,
InvalidResponse,
}Functions
pub fn respond(
self: *Exchange,
options: ResponseOptions,
body: []const u8,
) RespondError!void
src
Record the response. The engine sends the HEADERS and pumps body under
flow control. The body must stay valid until complete returns. The
extra headers are borrowed for the same lifetime.
Types
module
epoll
src
An epoll(7)-based HTTP/2 server engine.
The readiness loop is shared with poll and kqueue; this module supplies the Linux epoll registration and wait backend.
Constants
A type-erased HTTP/2 handler over this engine's exchange.
Types
struct
Exchange
src
pub const Exchange = readiness.Exchange
Fields
id: usize
The engine's stream slot. Stable until complete, then reused.
stream_id: u31
request: Request
arena: Allocator
Per-stream storage for the request and any handler allocations.
response: ?Response = null
Constants
pub const RespondError = …
src
value
error{
AlreadyResponded,
InvalidResponse,
}Functions
pub fn respond(
self: *Exchange,
options: ResponseOptions,
body: []const u8,
) RespondError!void
src
Record the response. The engine sends the HEADERS and pumps body under
flow control. The body must stay valid until complete returns. The
extra headers are borrowed for the same lifetime.
Types
module
kqueue
src
A kqueue(2)-based HTTP/2 server engine.
The readiness loop is shared with poll and epoll; this module supplies the Darwin kqueue registration and wait backend.
Constants
A type-erased HTTP/2 handler over this engine's exchange.
Types
struct
Exchange
src
pub const Exchange = readiness.Exchange
Fields
id: usize
The engine's stream slot. Stable until complete, then reused.
stream_id: u31
request: Request
arena: Allocator
Per-stream storage for the request and any handler allocations.
response: ?Response = null
Constants
pub const RespondError = …
src
value
error{
AlreadyResponded,
InvalidResponse,
}Functions
pub fn respond(
self: *Exchange,
options: ResponseOptions,
body: []const u8,
) RespondError!void
src
Record the response. The engine sends the HEADERS and pumps body under
flow control. The body must stay valid until complete returns. The
extra headers are borrowed for the same lifetime.
Types
module
connection
src
The sans-I/O HTTP/2 exchange engine shared by the transports.
more
The connection owns the session, the fixed slot table, the per-stream
arenas, and the handler. A transport feeds bytes with receive, calls
drive to deliver events and queue responses, and drains send. It never
touches a socket. blocking and the readiness engines are thin drivers
around this type.
Constants
A type-erased HTTP/2 handler over this engine's exchange.
Types
struct
Exchange
src
pub const Exchange = common.Exchange
Fields
id: usize
The engine's stream slot. Stable until complete, then reused.
stream_id: u31
request: Request
arena: Allocator
Per-stream storage for the request and any handler allocations.
response: ?Response = null
Constants
pub const RespondError = …
src
value
error{
AlreadyResponded,
InvalidResponse,
}Functions
pub fn respond(
self: *Exchange,
options: ResponseOptions,
body: []const u8,
) RespondError!void
src
Record the response. The engine sends the HEADERS and pumps body under
flow control. The body must stay valid until complete returns. The
extra headers are borrowed for the same lifetime.
Types
struct
ResponseOptions
src
pub const ResponseOptions = common.ResponseOptions
The response fields the application controls.
Fields
status: Status = .ok
content_type: ?[]const u8 = null
close: bool = false
For h1, close after this response and emit Connection: close.
h2 Exchange.respond rejects this field; it has no stream-level
Connection-close meaning.
headers: []const Header = &.{}
Extra regular fields. The engine adds framing and protocol fields.
struct
Options
src
Fields
allocator: Allocator
handler: Handler
max_streams: u16 = 128
body_bytes: usize = 64 * 1024
arena_retain_bytes: usize = 4 * 1024
session: SessionOptions = .{ .role = .server }
head_timeout: Io.Duration = .zero
Time from accept until the first request head; zero disables.
body_timeout: Io.Duration = .zero
Time from a request head to its END_STREAM; zero disables.
struct
Connection
src
Fields
allocator: Allocator
handler: Handler
session: Session
slots: []Slot
body_bytes: usize
arena_retain_bytes: usize
head_timeout: Io.Duration
body_timeout: Io.Duration
head_deadline: ?Io.Timestamp
Functions
pub fn hasPendingOutput(self: *const Connection) bool
src
pub fn beginDrain(self: *Connection) !void
src
Queue a GOAWAY and leave the connection open for its active streams.
Arm the connection-level head deadline. A zero timeout leaves it unset.
The earliest head or body deadline, if any.
Deliver every complete event currently buffered, start the handlers whose requests are complete, and queue as much response output as flow control accepts. Returns whether anything happened.
struct
Connection
src
pub const Connection = connection.Connection
Fields
allocator: Allocator
handler: Handler
session: Session
slots: []Slot
body_bytes: usize
arena_retain_bytes: usize
head_timeout: Io.Duration
body_timeout: Io.Duration
head_deadline: ?Io.Timestamp
Functions
pub fn hasPendingOutput(self: *const Connection) bool
src
pub fn beginDrain(self: *Connection) !void
src
Queue a GOAWAY and leave the connection open for its active streams.
Arm the connection-level head deadline. A zero timeout leaves it unset.
The earliest head or body deadline, if any.
Deliver every complete event currently buffered, start the handlers whose requests are complete, and queue as much response output as flow control accepts. Returns whether anything happened.
struct
ConnectionOptions
src
pub const ConnectionOptions = connection.Options
Fields
allocator: Allocator
handler: Handler
max_streams: u16 = 128
body_bytes: usize = 64 * 1024
arena_retain_bytes: usize = 4 * 1024
session: SessionOptions = .{ .role = .server }
head_timeout: Io.Duration = .zero
Time from accept until the first request head; zero disables.
body_timeout: Io.Duration = .zero
Time from a request head to its END_STREAM; zero disables.
module
readiness
src
The readiness engine shared by the poll(2), epoll(7), and kqueue(2) backends, for h2c and h2 over TLS.
more
Fixed connection slots, nonblocking sockets, one thread. Each slot owns a
byte transport and drives the sans-I/O Connection core above it. The
Waiter.Platform edge supplies the listener, stop descriptor, accept,
read, send, and close for the host. The Transport parameter supplies the
h2c socket transport or the TLS record transport.
Constants
A type-erased HTTP/2 handler over this engine's exchange.
Types
struct
Exchange
src
pub const Exchange = common.Exchange
Fields
id: usize
The engine's stream slot. Stable until complete, then reused.
stream_id: u31
request: Request
arena: Allocator
Per-stream storage for the request and any handler allocations.
response: ?Response = null
Constants
pub const RespondError = …
src
value
error{
AlreadyResponded,
InvalidResponse,
}Functions
pub fn respond(
self: *Exchange,
options: ResponseOptions,
body: []const u8,
) RespondError!void
src
Record the response. The engine sends the HEADERS and pumps body under
flow control. The body must stay valid until complete returns. The
extra headers are borrowed for the same lifetime.
Types
type fn
Config
src
pub fn Config(comptime Transport: type) type
Fields
allocator: Allocator
max_connections: usize
read_buffer_bytes: usize = 16 * 1024
Read storage for the plaintext transport. TLS owns its record storage and ignores this.
write_buffer_bytes: usize = 16 * 1024
Staging for h2 output, and the plaintext transport's write storage. TLS encrypts from this staging buffer.
max_streams: u16 = 128
common: shared.Config = .{ .arena_retain_bytes = 4 * 1024 }
session: SessionOptions = .{ .role = .server }
idle_timeout: Io.Duration = .zero
Zero disables the deadline.
write_timeout: Io.Duration = .zero
total_timeout: Io.Duration = .zero
head_timeout: Io.Duration = .zero
body_timeout: Io.Duration = .zero
accept_backoff: Io.Duration = .fromMilliseconds(50)
drain_timeout: Io.Duration = .fromSeconds(5)
How long a stop waits for active streams to finish before closing.
transport: ?Transport.Options = null
Transport options. The plaintext transport has defaults; TLS requires credentials and a handshake template.
type fn
Server
src
pub fn Server(comptime Wait: type, comptime Transport: type) type
Fields
io: Io
allocator: Allocator
handler: Handler
config: ConfigType
listener: P.Listener
stop: P.Stop
slots: []Slot
wait: Wait
active: usize
accept_paused_until: ?Io.Timestamp
draining: bool
drain_deadline: ?Io.Timestamp
Constants
Functions
pub fn requestStop(self: *Self) void
src
module
transport
src
The byte-transport event shared by the readiness engines.
more
A transport owns the socket-facing state for one connection slot: the raw
fd, its read and write storage, and, for TLS, the handshake and record
layer. It exposes application plaintext to the h2 Connection and accepts
h2 output in return. The readiness loop owns readiness, deadlines, and the
connection lifetime; it never sees a TLS record.
Types
union
Event
src
pub const Event = union(enum)
One step of the transport state machine.
Fields
need_input need_input
No complete input record or plaintext remains.
need_output need_output
The transport has wire output to drain before it can make progress.
progress progress
The transport advanced but has more work ready now.
handshake_complete handshake_complete
TLS completed its handshake and the configured ALPN was selected.
application_data: []const u8
Borrowed plaintext. The transport keeps it pinned until consume.
closed closed
The peer ended its side of the transport. Flush pending output, then close the slot.
module
wait
src
Types shared by the readiness wait backends.
Constants
What the waiter should watch for.
What the waiter saw.
Functions
pub fn ownerIndex(owner: Owner) usize
src
The backend table has one entry for the stop event, one for the listener, then one per connection slot.
pub fn ownerFromIndex(index: usize) Owner
src
Types
module
wait_poll
src
poll(2) registration and wait.
Types
struct
Wait
src
Fields
allocator: Allocator
entries: []Entry
pollfds: []posix.pollfd
owners: []types.Owner
ready: []types.Ready
Functions
Types
module
Platform
src
Linux transport edge for the readiness engines.
Constants
pub const AcceptError = …
src
value
error{
ConnectionAborted,
ListenerFailed,
SystemResources,
}Functions
pub fn openListener(address: Io.net.IpAddress, tcp_defer_accept: bool) !Listener
src
Types
module
wait_epoll
src
epoll(7) registration and wait.
Types
struct
Wait
src
Fields
allocator: Allocator
entries: []Entry
epfd: linux.fd_t
events: []linux.epoll_event
ready: []types.Ready
Functions
Types
module
Platform
src
Linux transport edge for the readiness engines.
Constants
pub const AcceptError = …
src
value
error{
ConnectionAborted,
ListenerFailed,
SystemResources,
}Functions
pub fn openListener(address: Io.net.IpAddress, tcp_defer_accept: bool) !Listener
src
Types
module
wait_kqueue
src
kqueue registration and wait for the Darwin readiness engine.
Types
struct
Wait
src
Fields
Functions
Types
module
Platform
src
Darwin transport edge for the kqueue readiness engine.
Constants
pub const AcceptError = …
src
value
error{
ConnectionAborted,
ListenerFailed,
SystemResources,
}Functions
pub fn openListener(address: Io.net.IpAddress, tcp_defer_accept: bool) !Listener
src
Types
struct
Config
src
pub const Config = blocking.Config
Fields
allocator: Allocator
read_buffer: []u8
Transport read scratch fed to Connection.receive.
write_buffer: []u8
The std.Io writer's buffer.
max_streams: u16 = 128
Engine slot count and the session's MAX_CONCURRENT_STREAMS.
common: shared.Config = .{ .arena_retain_bytes = 4 * 1024 }
session: Options = .{ .role = .server }
struct
Exchange
src
pub const Exchange = blocking.Exchange
Fields
id: usize
The engine's stream slot. Stable until complete, then reused.
stream_id: u31
request: Request
arena: Allocator
Per-stream storage for the request and any handler allocations.
response: ?Response = null
Constants
pub const RespondError = …
src
value
error{
AlreadyResponded,
InvalidResponse,
}Functions
pub fn respond(
self: *Exchange,
options: ResponseOptions,
body: []const u8,
) RespondError!void
src
Record the response. The engine sends the HEADERS and pumps body under
flow control. The body must stay valid until complete returns. The
extra headers are borrowed for the same lifetime.
Types
struct
ResponseOptions
src
pub const ResponseOptions = blocking.ResponseOptions
The response fields the application controls.
Fields
status: Status = .ok
content_type: ?[]const u8 = null
close: bool = false
For h1, close after this response and emit Connection: close.
h2 Exchange.respond rejects this field; it has no stream-level
Connection-close meaning.
headers: []const Header = &.{}
Extra regular fields. The engine adds framing and protocol fields.
module
flow
src
HTTP/2 flow-control windows (RFC 9113 §5.2, §6.9).
more
A window counts the bytes a peer may still send (the receive side) or this endpoint may still send (the send side). It is signed because SETTINGS_INITIAL_WINDOW_SIZE can shrink below the bytes already sent, and a sender must wait for WINDOW_UPDATE to bring the window positive again (§6.9.2).
This module is accounting only. It does not decide whether an error is a connection or a stream error; the session does.
Constants
pub const max_window: i64 = 0x7fff_ffff
src
The largest flow-control window, 2^31-1.
value
error{
/// The frame carries more than the window allows, or an update overflows it.
FlowControlError,
/// A WINDOW_UPDATE increment of zero (RFC 9113 §6.9).
ZeroIncrement,
}Types
struct
Window
src
Fields
available: i64
Functions
Account for count bytes of DATA. A send window must be positive
before the bytes are written; a receive window rejects bytes over its
limit.
Apply a WINDOW_UPDATE increment.
pub fn applyInitialChange(self: *Window, old: u32, new: u32) Error!void
src
Apply the delta from a SETTINGS_INITIAL_WINDOW_SIZE change to one stream's window. The result may be negative.
module
frame
src
HTTP/2 frame codec (RFC 9113 §4, §6, §7).
more
Sans-I/O: parse takes bytes read so far and reports a frame, Incomplete,
or an error; Writer fills a caller buffer. Neither owns memory, and both
borrow the payload from the input.
parse enforces frame syntax only: the 9-byte header, the per-type payload
length, and padding. Stream and connection rules (which frame may appear on
which stream, SETTINGS state, flow control, CONTINUATION ordering) belong to
the layer above. Unknown frame types parse as Body.unknown; the caller
ignores them, as RFC 9113 §4.1 requires.
h2c and h2 over TLS use this same codec. The connection preface is
preface; nothing here touches TLS.
Constants
The client connection preface (RFC 9113 §3.4).
pub const min_max_frame_size: u32 = 16_384
src
The smallest, default, and largest SETTINGS_MAX_FRAME_SIZE values (RFC 9113 §6.5.2).
pub const default_max_frame_size: u32 = min_max_frame_size
src
The smallest, default, and largest SETTINGS_MAX_FRAME_SIZE values (RFC 9113 §6.5.2).
pub const max_frame_size_limit: u32 = 0xff_ffff
src
value
error{
/// Fewer than 9 header bytes, or fewer payload bytes than the header says.
Incomplete,
/// The payload length exceeds the caller's SETTINGS_MAX_FRAME_SIZE.
FrameTooLarge,
/// The payload length is wrong for the frame type.
InvalidLength,
/// The padding length is larger than the payload.
InvalidPadding,
/// The frame cannot be represented.
Invalid,
}Functions
pub fn parseHeader(buf: []const u8) Error!Header
src
pub fn encodeHeader(out: *[9]u8, header: Header) void
src
Parse one frame from the front of buf, rejecting a payload larger than
max_frame_size before waiting for it.
Parse the payload of a frame whose header is already known.
Types
enum
FrameType
src
pub const FrameType = enum(u8)
Frame types defined by RFC 9113 §6, plus the open registry.
Fields
data data = 0x0
headers headers = 0x1
priority priority = 0x2
rst_stream rst_stream = 0x3
settings settings = 0x4
push_promise push_promise = 0x5
ping ping = 0x6
goaway goaway = 0x7
window_update window_update = 0x8
continuation continuation = 0x9
_ _
enum
ErrorCode
src
pub const ErrorCode = enum(u32)
Error codes from RFC 9113 §7, plus the open registry.
Fields
no_error no_error = 0x0
protocol_error protocol_error = 0x1
internal_error internal_error = 0x2
flow_control_error flow_control_error = 0x3
settings_timeout settings_timeout = 0x4
stream_closed stream_closed = 0x5
frame_size_error frame_size_error = 0x6
refused_stream refused_stream = 0x7
cancel cancel = 0x8
compression_error compression_error = 0x9
connect_error connect_error = 0xa
enhance_your_calm enhance_your_calm = 0xb
inadequate_security inadequate_security = 0xc
http_1_1_required http_1_1_required = 0xd
_ _
enum
SettingId
src
pub const SettingId = enum(u16)
SETTINGS identifiers from RFC 9113 §6.5.2, plus the open registry.
Fields
header_table_size header_table_size = 0x1
enable_push enable_push = 0x2
max_concurrent_streams max_concurrent_streams = 0x3
initial_window_size initial_window_size = 0x4
max_frame_size max_frame_size = 0x5
max_header_list_size max_header_list_size = 0x6
_ _
struct
flags
src
Frame flags. The same bit means different things per frame type; unused flags are ignored on receipt (RFC 9113 §4.1).
Constants
pub const end_stream: u8 = 0x1
src
DATA, HEADERS.
SETTINGS, PING.
pub const end_headers: u8 = 0x4
src
HEADERS, PUSH_PROMISE, CONTINUATION.
DATA, HEADERS, PUSH_PROMISE.
HEADERS.
struct
PushPromise
src
Fields
promised_stream_id: u31
block: []const u8
The field block fragment, padding removed.
struct
WindowUpdate
src
Fields
increment: u31
The 31-bit increment. Zero is a protocol error at the stream layer (RFC 9113 §6.9); this codec keeps the wire value.
union
Body
src
pub const Body = union(enum)
Fields
data: Data
headers: Headers
priority: Priority
rst_stream: RstStream
settings: Settings
push_promise: PushPromise
ping: Ping
goaway: GoAway
window_update: WindowUpdate
continuation: Continuation
unknown: Unknown
struct
Writer
src
Build frames into a caller buffer. begin reserves the header, the caller
appends the payload, and end patches the length.
Fields
Constants
pub const WriterError = error{NoSpace}
src
Functions
module
head
src
HTTP/2 field-section validation and the request/response heads (RFC 9113 §8.2, §8.3).
more
The input is the decoded field list HPACK produced, pseudo-headers
included and in wire order. The output extracts the pseudo-headers into a
typed head and exposes the regular fields as a Headers view over the same
storage. Every slice is borrowed; nothing allocates.
Validation follows RFC 9113 §8.2.1's minimal rules, with field names
restricted to lowercase tchar as HTTP/1.1's parser already does. A
malformed field section is a stream error of type PROTOCOL_ERROR
(RFC 9113 §8.1.1); the caller maps Error.
Cookie fields are not combined here. RFC 9113 §8.2.3 says they combine
with "; " before processing; the caller decides where the combined value
lives.
Constants
value
error{
/// A field name is empty, has a colon, or is not lowercase tchar.
InvalidFieldName,
/// A field value has NUL, CR, LF, another control, or edge whitespace.
InvalidFieldValue,
/// A pseudo-header followed a regular field.
PseudoAfterField,
/// A pseudo-header appeared more than once.
DuplicatePseudo,
/// A pseudo-header this direction does not define.
UnknownPseudo,
/// A required pseudo-header is missing.
MissingPseudo,
/// `:method` is not a non-empty token.
InvalidMethod,
/// `:scheme` is not a valid scheme.
InvalidScheme,
/// `:path` is empty, not absolute, or `*` without OPTIONS.
InvalidPath,
/// `:authority` is empty or carries userinfo for http(s).
InvalidAuthority,
/// `:status` is not exactly three digits.
InvalidStatus,
/// `:protocol` is not a token, or is used without CONNECT.
InvalidProtocol,
/// A connection-specific field is present, or `TE` is not `trailers`.
ForbiddenField,
/// An outbound response put `content-length` in the extra fields.
DuplicateContentLength,
}Functions
Validate a trailer section: regular fields only, no pseudo-headers.
pub fn validateResponse(value: response_mod.Response) Error!void
src
Validate the typed outbound response. The status is in range, the content type is a legal field value, and every extra field is a regular field with no connection-specific semantics.
Types
struct
RequestHead
src
Fields
method: ?Method
The common method, or null when the token is not one of them.
method_token: []const u8
The exact :method token. Always present.
scheme: ?[]const u8
authority: ?[]const u8
path: ?[]const u8
protocol: ?[]const u8
RFC 8441 extended CONNECT.
headers: Headers
The regular fields, in wire order. Pseudo-headers are not included.
struct
ResponseHead
src
Fields
module
header_block
src
Assemble one header block from HEADERS or PUSH_PROMISE and the CONTINUATION frames that follow it (RFC 9113 §4.3, §6.2, §6.10).
more
The caller owns the block buffer and feeds fragments in wire order. A
fragment for a single-frame block is returned borrowed; a split block is
copied into the buffer and returned when END_HEADERS arrives. While a block
is open, RFC 9113 §4.3 allows no other frame on the connection, so the
caller must check isAssembling before dispatching anything else.
Constants
value
error{
/// A new HEADERS or PUSH_PROMISE arrived while a block was open.
Interleaved,
/// A CONTINUATION arrived with no block open, or for another stream.
UnexpectedContinuation,
/// The assembled block does not fit the caller's buffer.
TooLarge,
}Types
struct
Assembler
src
Fields
buffer: []u8
len: usize
stream_id: ?u31
The stream whose block is open, or null when no block is open.
Functions
pub fn isAssembling(self: Assembler) bool
src
pub fn start(
self: *Assembler,
stream_id: u31,
fragment: []const u8,
end_headers: bool,
) Error!?[]const u8
src
Start a block from a HEADERS or PUSH_PROMISE fragment. Returns the block when END_HEADERS is set on that frame, or null when CONTINUATION frames follow.
pub fn continuation(
self: *Assembler,
stream_id: u31,
fragment: []const u8,
end_headers: bool,
) Error!?[]const u8
src
Append a CONTINUATION fragment. Returns the block when END_HEADERS is set, or null for another fragment.
module
session
src
An HTTP/2 connection state machine, sans I/O (RFC 9113).
more
The session owns the protocol state: SETTINGS, the stream table, flow
control, HPACK, CONTINUATION assembly, and the outbound queue. It never
touches a socket, a TLS context, or an event loop. The caller feeds bytes
with receive, consumes events with nextEvent, submits frames with the
submit* methods, and drains send into its transport.
Every method that can allocate takes the caller's allocator. The session
stores no allocator. Stream records and output chunks come from
fixed-record pools, so a caller with a fixed ceiling can pass a
std.heap.FixedBufferAllocator; the pools reuse records and chunks.
Constants
value
error{
ConnectionClosed,
WrongRole,
UnknownStream,
StreamClosed,
TooManyStreams,
StreamIdsExhausted,
HeaderBlockTooLarge,
InvalidHead,
WouldBlock,
FlowControlError,
InvalidMaxFrameSize,
InvalidInitialWindowSize,
InvalidHeaderTableSize,
InvalidHeaderListSize,
InvalidHeaderBlockSize,
InvalidOutputSize,
InvalidSetting,
SettingsTooLarge,
BufferTooSmall,
TooLarge,
} || Allocator.ErrorTypes
struct
Options
src
Fields
role: settings.Role
max_streams: u16 = 128
Our SETTINGS_MAX_CONCURRENT_STREAMS and the stream table capacity.
max_frame_size: u32 = frame.default_max_frame_size
The largest frame we accept, advertised in SETTINGS.
header_table_size: u32 = 4096
Our decoder table, advertised in SETTINGS.
initial_window_size: u32 = 65_535
Our stream receive window, advertised in SETTINGS.
max_header_list_size: u32 = 16 * 1024
Our decoded field-section limit, advertised in SETTINGS.
max_in_header_block: u32 = 32 * 1024
The assembled wire block we accept.
max_send_header_block: u32 = 16 * 1024
The outbound header block we can encode.
max_out_bytes: u32 = 256 * 1024
A cap on queued output. Control frames may exceed it.
struct
Session
src
Fields
options: Options
state: State
ids: stream.Ids
streams: std.AutoHashMapUnmanaged(u31, Stream)
active_local: u32
active_remote: u32
closed: [closed_capacity]Closed
closed_len: usize
closed_pos: usize
local: settings.Values
remote: settings.Values
conn_send: flow.Window
conn_recv: flow.Window
encoder: hpack.Encoder
decoder: hpack.Decoder
encoder_storage: hpack.TableStorage
decoder_storage: hpack.TableStorage
in_buf: []u8
in_len: usize
in_start: usize
preface_received: bool
settings_received: bool
settings_acked: bool
goaway_received: bool
goaway_sent: bool
block: header_block.Assembler
pending: ?Pending
field_headers: []Header
field_bytes: []u8
send_block: []u8
frame_buf: []u8
out: OutQueue
out_sent: u64
Bytes drained from out by send/sendFrame.
last_header_run: ?HeaderRun
event: ?Event
Functions
pub fn hasPendingOutput(self: *const Session) bool
src
pub fn queuedOutput(self: *const Session) usize
src
Bytes currently queued for send. The readiness and client engines
use this to mark a request's header block at its wire position.
Append transport bytes. Returns how many were consumed; zero means the input buffer is full and the caller must drain events first.
Copy queued output into out. Never allocates.
Copy exactly one complete frame into out. The caller must drain the
client preface before using this method. Returns 0 when the queue is
empty, and BufferTooSmall when out cannot hold the next frame.
pub fn lastHeaderRun(self: *const Session) ?HeaderRun
src
pub fn submitRequest(
self: *Session,
allocator: Allocator,
fields: []const Header,
end_stream: bool,
) Error!u31
src
pub fn submitResponse(
self: *Session,
allocator: Allocator,
id: u31,
value: Response,
end_stream: bool,
) Error!void
src
pub fn submitTrailers(
self: *Session,
allocator: Allocator,
id: u31,
fields: []const Header,
) Error!void
src
pub fn submitData(
self: *Session,
allocator: Allocator,
id: u31,
bytes: []const u8,
end_stream: bool,
) Error!usize
src
Queue as much of bytes as flow control and the output budget allow.
Returns the number of bytes accepted; the caller keeps the rest.
pub fn submitReset(
self: *Session,
allocator: Allocator,
id: u31,
code: frame.ErrorCode,
) Error!void
src
pub fn submitSettings(
self: *Session,
allocator: Allocator,
entries: []const frame.Setting,
) Error!void
src
Send a local SETTINGS change. Only the entries supplied are sent; the
session validates each one, applies the receive-side effects, and
queues the frame. Local table and buffer growth is capped by what
init allocated.
pub fn submitGoaway(self: *Session, allocator: Allocator, code: frame.ErrorCode) Error!void
src
pub fn submitPing(self: *Session, allocator: Allocator, payload: *const [8]u8) Error!void
src
Return consumed DATA bytes to the flow-control windows and send the two WINDOW_UPDATE frames. A closed stream only gets the connection update.
struct
Session
src
pub const Session = session.Session
Fields
options: Options
state: State
ids: stream.Ids
streams: std.AutoHashMapUnmanaged(u31, Stream)
active_local: u32
active_remote: u32
closed: [closed_capacity]Closed
closed_len: usize
closed_pos: usize
local: settings.Values
remote: settings.Values
conn_send: flow.Window
conn_recv: flow.Window
encoder: hpack.Encoder
decoder: hpack.Decoder
encoder_storage: hpack.TableStorage
decoder_storage: hpack.TableStorage
in_buf: []u8
in_len: usize
in_start: usize
preface_received: bool
settings_received: bool
settings_acked: bool
goaway_received: bool
goaway_sent: bool
block: header_block.Assembler
pending: ?Pending
field_headers: []Header
field_bytes: []u8
send_block: []u8
frame_buf: []u8
out: OutQueue
out_sent: u64
Bytes drained from out by send/sendFrame.
last_header_run: ?HeaderRun
event: ?Event
Functions
pub fn hasPendingOutput(self: *const Session) bool
src
pub fn queuedOutput(self: *const Session) usize
src
Bytes currently queued for send. The readiness and client engines
use this to mark a request's header block at its wire position.
Append transport bytes. Returns how many were consumed; zero means the input buffer is full and the caller must drain events first.
Copy queued output into out. Never allocates.
Copy exactly one complete frame into out. The caller must drain the
client preface before using this method. Returns 0 when the queue is
empty, and BufferTooSmall when out cannot hold the next frame.
pub fn lastHeaderRun(self: *const Session) ?HeaderRun
src
pub fn submitRequest(
self: *Session,
allocator: Allocator,
fields: []const Header,
end_stream: bool,
) Error!u31
src
pub fn submitResponse(
self: *Session,
allocator: Allocator,
id: u31,
value: Response,
end_stream: bool,
) Error!void
src
pub fn submitTrailers(
self: *Session,
allocator: Allocator,
id: u31,
fields: []const Header,
) Error!void
src
pub fn submitData(
self: *Session,
allocator: Allocator,
id: u31,
bytes: []const u8,
end_stream: bool,
) Error!usize
src
Queue as much of bytes as flow control and the output budget allow.
Returns the number of bytes accepted; the caller keeps the rest.
pub fn submitReset(
self: *Session,
allocator: Allocator,
id: u31,
code: frame.ErrorCode,
) Error!void
src
pub fn submitSettings(
self: *Session,
allocator: Allocator,
entries: []const frame.Setting,
) Error!void
src
Send a local SETTINGS change. Only the entries supplied are sent; the
session validates each one, applies the receive-side effects, and
queues the frame. Local table and buffer growth is capped by what
init allocated.
pub fn submitGoaway(self: *Session, allocator: Allocator, code: frame.ErrorCode) Error!void
src
pub fn submitPing(self: *Session, allocator: Allocator, payload: *const [8]u8) Error!void
src
Return consumed DATA bytes to the flow-control windows and send the two WINDOW_UPDATE frames. A closed stream only gets the connection update.
struct
Response
src
pub const Response = @import("../response.zig").Response
Fields
status: Status
content_type: ?[]const u8 = null
content_length: ?u64 = null
The engine sets this; h2.Session.submitResponse encodes it.
headers: []const Header = &.{}
Extra regular fields. Pseudo-headers and content-length belong in the
typed fields, not here.
module
settings
src
SETTINGS state for one direction (RFC 9113 §6.5.2).
more
Two Values live on a connection: the local settings this endpoint
advertises, and the remote settings the peer sent. apply reads a SETTINGS
payload, validates the defined identifiers, and lets the last value win.
Unknown identifiers are ignored, as the registry requires.
A change to initial_window_size does not touch stream windows here; the
flow-control layer applies the delta to every open stream (§6.9.2).
Constants
value
error{
/// ENABLE_PUSH was not 0 or 1.
InvalidEnablePush,
/// A server sent ENABLE_PUSH = 1 (RFC 9113 §6.5.2).
ServerEnablePush,
/// INITIAL_WINDOW_SIZE was above 2^31-1.
InvalidInitialWindowSize,
/// MAX_FRAME_SIZE was outside 16,384..16,777,215.
InvalidMaxFrameSize,
}Functions
Apply a peer's SETTINGS payload. local_role is this endpoint's role; the
peer is the other one.
pub fn applyEntry(values: *Values, local_role: Role, entry: frame.Setting) Error!void
src
Apply one defined setting. Unknown identifiers are ignored; a caller that sends local settings rejects them before this point.
Types
struct
Values
src
Fields
header_table_size: u32 = 4096
enable_push: bool = true
max_concurrent_streams: ?u32 = null
null means unlimited (the initial state).
initial_window_size: u32 = 65_535
max_frame_size: u32 = frame.default_max_frame_size
max_header_list_size: ?u32 = null
null means unlimited.
module
stream
src
HTTP/2 stream states and identifiers (RFC 9113 §5.1, §5.1.1).
more
transition applies one frame-level event from a side to a stream state.
It is a table, not a stream object: the session owns the table of streams.
HEADERS with END_STREAM is two events in order, headers then
end_stream, so the state after the frame is the same.
This module does not decide whether a bad transition is a stream or a
connection error. IdleStream marks the cases RFC 9113 §5.1 calls a
connection error; the rest are stream errors.
Constants
value
error{
/// The frame is not legal in this state from this side. A stream error.
InvalidTransition,
/// RST_STREAM or DATA on an idle stream. A connection error.
IdleStream,
/// A new remote stream identifier that is zero, the wrong parity, or not
/// greater than the last one.
InvalidStreamId,
}Functions
pub fn transition(state: State, side: Side, event: Event) Error!State
src
Types
enum
State
src
Fields
idle idle
reserved_local reserved_local
reserved_remote reserved_remote
open open
half_closed_local half_closed_local
half_closed_remote half_closed_remote
closed closed
enum
Event
src
Fields
headers headers
HEADERS (or a HEADERS opening a reserved stream). On its own this does
not carry END_STREAM; apply end_stream after it when the flag is set.
data data
DATA. It never changes the state, but it is illegal on an idle or closed stream and on the side that already ended.
end_stream end_stream
END_STREAM, from a HEADERS or DATA frame.
reset reset
RST_STREAM.
push_promise push_promise
PUSH_PROMISE, applied to the promised stream, not the current one.
struct
Ids
src
The next local stream identifier and the last remote one.
Fields
Functions
The next stream this endpoint initiates, or null when the 31-bit space is exhausted.
pub fn acceptRemote(self: *Ids, id: u31) Error!void
src
Accept a new stream initiated by the peer. The identifier must have the peer's parity and exceed every remote identifier seen.
module
hpack
src
HPACK header compression, as HTTP/2 uses it (RFC 7541).
more
The pieces are the two tables, a decoder, and an encoder. The static table is the
RFC's 61 fixed fields; the dynamic table is the per-connection, per-
direction list the encoder and decoder keep in step. Decoder owns a
dynamic table and turns one complete header block into Header values.
The primitives the format is built from live outside this namespace
because QPACK reuses them: http.prefix_int and http.huffman.
The encoder shares this table. Its default policy indexes every field,
reuses an exact pair as an indexed field, and Huffman-codes a string only
when that is strictly shorter; encodeModes takes a Mode per field for
the two representations that leave the table alone.
Constants
pub const first_dynamic = decoder.first_dynamic
src
The first dynamic index: 62, one past the static table.
Types
module
decoder
src
HPACK header block decoding (RFC 7541 §3, §6).
more
One instance per connection, per direction. A block is complete when it
reaches decode: HTTP/2 assembles CONTINUATION frames before calling, so
there is no partial-block state here. The dynamic table is the only state
that spans blocks.
Every field is copied into the caller's out_bytes, including indexed
fields that point into the static or dynamic table. That costs one copy and
buys a single rule: every returned slice lives in out_bytes and stays
valid until the caller reuses that buffer, whatever the table does next.
A decoder that borrowed from the table would hand out a field the next
insertion could evict.
decode returns error.Truncated for a block that ends inside a
representation, and error.InvalidIndex, error.InvalidInteger,
error.InvalidHuffman, error.TableSizeTooLarge, or
error.TableSizeUpdateNotFirst for what RFC 7541 calls a decoding error.
An HTTP/2 peer turns any of them into a COMPRESSION_ERROR for the
connection. error.TooManyHeaders and error.OutputTooSmall are local:
the caller's arrays were too small, which is not the peer's fault.
Any error ends the connection. The block stops where the error is, so the
dynamic table can no longer match the peer's, and a later block would
decode against the wrong table. That includes the local errors: size out,
out_bytes, and the table so they cannot trip. An application limit on
header list size belongs after decode, where a stream can be reset and
the connection stays in sync.
Constants
pub const first_dynamic = static_table.first_dynamic
src
The first dynamic index. Static indices are 1..61, dynamic start at 62.
value
error{
/// The block ends inside a representation or a string.
Truncated,
/// An index of 0, or past the end of the static and dynamic tables.
InvalidIndex,
/// A prefix integer that does not fit 32 bits.
InvalidInteger,
/// A Huffman string that is not a code, holds EOS, or has bad padding.
InvalidHuffman,
/// A dynamic table size update above the storage's capacity.
TableSizeTooLarge,
/// A dynamic table size update after a field representation (RFC 7541 §4.2).
TableSizeUpdateNotFirst,
/// The caller's field array is full.
TooManyHeaders,
/// The caller's output buffer is full.
OutputTooSmall,
}Types
struct
Decoder
src
Fields
table: DynamicTable
Functions
The storage is the SETTINGS_HEADER_TABLE_SIZE this decoder advertises,
and so the largest size a peer may ask for. TableStorage.init sizes it
from that value.
Decode one complete header block into out, copying the bytes into
out_bytes. The returned Headers borrows out.
pub fn decodeFlags(
self: *Decoder,
block: []const u8,
out: []Header,
out_bytes: []u8,
never_indexed: []bool,
) Error!Headers
src
decode, and record the RFC 7541 §6.2.3 never-indexed flag per field.
An intermediary that re-encodes must forward such a field with the
same representation, so this is the variant a proxy uses.
never_indexed must have a flag for every field out can hold.
module
encoder
src
HPACK header block encoding (RFC 7541 §3, §5, §6).
more
One instance per connection, per direction. The encoder and the peer's
decoder hold the same dynamic table; setMaxSize follows the peer's
SETTINGS_HEADER_TABLE_SIZE and queues the size update that puts both
tables back in step.
encodedLen is an upper bound: the block is written as if every field had
a literal name and a raw value, which is the largest a field can be. If
out meets that bound, encode cannot run out, so the dynamic table is
never advanced by a block that was not written.
The default policy indexes every field, uses an indexed field when the
exact pair is already in a table, and Huffman-codes a string when that is
shorter. A caller overrides the first with a Mode per field, and can turn
off Huffman on the encoder.
Constants
value
error{
/// The destination buffer is smaller than `encodedLen`.
NoSpace,
}Types
enum
Mode
src
How one field is represented on the wire.
Fields
incremental incremental
Add the field to the dynamic table (§6.2.1). An exact pair that is already in a table becomes an indexed field instead.
without_indexing without_indexing
Leave the table alone (§6.2.2).
never_indexed never_indexed
Leave the table alone, and require an intermediary that forwards the field to use this same representation (§6.2.3).
struct
Encoder
src
Fields
table: DynamicTable
huffman: bool
Huffman-code a string when that is shorter than its raw bytes. On by default; a caller that values encode speed over block size turns it off.
pending_min: ?usize
The smallest maximum size requested since the last block. A reduction followed by a growth has to reach the peer as two updates (§4.2).
Functions
pub fn setMaxSize(self: *Encoder, new_max: usize) error{TooLarge}!void
src
Change the maximum table size the peer allows. Evicts now, and emits the update at the start of the next block.
pub fn encodedLen(self: *const Encoder, headers: []const Header) usize
src
An upper bound on the block encode writes, pending size updates
included. Size out with this.
Encode every field with the default policy: incremental indexing.
pub fn encodeModes(
self: *Encoder,
headers: []const Header,
modes: ?[]const Mode,
out: []u8,
) Error![]const u8
src
Encode with a mode per field. modes is indexed the same way as
headers; null means incremental for all.
pub fn encodedLenResponse(self: *const Encoder, response: Response) usize
src
An upper bound on encodeResponse with the default incremental mode.
pub fn encodeResponse(
self: *Encoder,
response: Response,
out: []u8,
) Error![]const u8
src
Encode a typed response. The status and content-length text are made here and live only for the call; the dynamic table copies what it indexes. The extra headers and content type are borrowed from the caller.
module
dynamic_table
src
The HPACK dynamic table (RFC 7541 §2.3.2, §4).
more
One instance per connection, per direction. The table itself owns nothing:
TableStorage holds the byte arena, the entry records, and the hash array,
and the table reads them. insert accounts entries as RFC 7541 §4.1
requires (name + value + 32) and evicts from the oldest end (§4.4).
get, find, and findName index from the newest entry, 0, the order the
wire uses. The ring's head is the newest entry, so the live hashes run
newest to oldest from head; that lets mem.findScalarPos return the
newest match without a reverse scan.
New entries are copied into the low end of the byte arena, below every live
entry; eviction drops the high end. That leaves gaps the arena reclaims by
compacting when an entry no longer fits below the live data. Compaction
moves live entries, so a slice returned by get is valid only until the
next insert or setMaxSize. A decoder that hands fields to its caller
copies them out for exactly this reason.
Constants
pub const entry_overhead = TableStorage.entry_overhead
src
The per-entry accounting overhead (RFC 7541 §4.1).
Types
struct
DynamicTable
src
Fields
bytes: []u8
Entry names and values. Borrowed from TableStorage.
slots: []Slot
One record per slot, in a ring.
hashes: []u32
One name hash per slot, contiguous for the lookup scan.
head: usize
Ring index of the newest entry.
count: usize
size: usize
Sum of every live entry's size, overhead included (§4.1).
max_size: usize
The current maximum, from the protocol settings and size updates.
top: usize
Bottom of the used region. Everything below it is free.
Functions
The bytes the live entries occupy, which is their accounting size less the fixed overhead.
The entry at a dynamic index: 0 is the most recently inserted. Returns null past the oldest entry.
The position of the newest entry whose name and value both match, or
null. A position is 0 for the newest entry, the same basis as get.
The position of the newest entry with this name, or null.
Add an entry and evict as §4.4 requires. An entry larger than the maximum empties the table and is not added; that is not an error.
name and value must not point into bytes; a decoder copies the
strings it decodes before calling this (§4.4 warns about the case).
pub fn setMaxSize(self: *DynamicTable, new_max: usize) error{TooLarge}!void
src
Change the maximum size, evicting until the table fits (RFC 7541 §4.3). A size above the storage is a decoding error for the caller.
module
static_table
src
The HPACK static table (RFC 7541 Appendix A).
more
Sixty-one fields, fixed for the life of the protocol, shared by every
connection. Index 1 is the first entry, as the RFC numbers them; get
takes that index and returns null outside 1..61. The entries are Header
values with static storage, so a decoder can borrow them or copy them
without a lifetime question.
Constants
The number of static entries.
pub const first_dynamic: u32 = count + 1
src
The first dynamic index: one past the last static entry.
Functions
The index of the first entry whose name and value both match, or null.
The index of the first entry with this name, or null. Entries that share a name are adjacent, so this is the run's first index.
struct
Decoder
src
pub const Decoder = decoder.Decoder
Fields
table: DynamicTable
Functions
The storage is the SETTINGS_HEADER_TABLE_SIZE this decoder advertises,
and so the largest size a peer may ask for. TableStorage.init sizes it
from that value.
Decode one complete header block into out, copying the bytes into
out_bytes. The returned Headers borrows out.
pub fn decodeFlags(
self: *Decoder,
block: []const u8,
out: []Header,
out_bytes: []u8,
never_indexed: []bool,
) Error!Headers
src
decode, and record the RFC 7541 §6.2.3 never-indexed flag per field.
An intermediary that re-encodes must forward such a field with the
same representation, so this is the variant a proxy uses.
never_indexed must have a flag for every field out can hold.
struct
Encoder
src
pub const Encoder = encoder.Encoder
Fields
table: DynamicTable
huffman: bool
Huffman-code a string when that is shorter than its raw bytes. On by default; a caller that values encode speed over block size turns it off.
pending_min: ?usize
The smallest maximum size requested since the last block. A reduction followed by a growth has to reach the peer as two updates (§4.2).
Functions
pub fn setMaxSize(self: *Encoder, new_max: usize) error{TooLarge}!void
src
Change the maximum table size the peer allows. Evicts now, and emits the update at the start of the next block.
pub fn encodedLen(self: *const Encoder, headers: []const Header) usize
src
An upper bound on the block encode writes, pending size updates
included. Size out with this.
Encode every field with the default policy: incremental indexing.
pub fn encodeModes(
self: *Encoder,
headers: []const Header,
modes: ?[]const Mode,
out: []u8,
) Error![]const u8
src
Encode with a mode per field. modes is indexed the same way as
headers; null means incremental for all.
pub fn encodedLenResponse(self: *const Encoder, response: Response) usize
src
An upper bound on encodeResponse with the default incremental mode.
pub fn encodeResponse(
self: *Encoder,
response: Response,
out: []u8,
) Error![]const u8
src
Encode a typed response. The status and content-length text are made here and live only for the call; the dynamic table copies what it indexes. The extra headers and content type are borrowed from the caller.
enum
Mode
src
pub const Mode = encoder.Mode
How one field is represented on the wire.
Fields
incremental incremental
Add the field to the dynamic table (§6.2.1). An exact pair that is already in a table becomes an indexed field instead.
without_indexing without_indexing
Leave the table alone (§6.2.2).
never_indexed never_indexed
Leave the table alone, and require an intermediary that forwards the field to use this same representation (§6.2.3).
struct
DynamicTable
src
pub const DynamicTable = dynamic_table.DynamicTable
Fields
bytes: []u8
Entry names and values. Borrowed from TableStorage.
slots: []Slot
One record per slot, in a ring.
hashes: []u32
One name hash per slot, contiguous for the lookup scan.
head: usize
Ring index of the newest entry.
count: usize
size: usize
Sum of every live entry's size, overhead included (§4.1).
max_size: usize
The current maximum, from the protocol settings and size updates.
top: usize
Bottom of the used region. Everything below it is free.
Functions
The bytes the live entries occupy, which is their accounting size less the fixed overhead.
The entry at a dynamic index: 0 is the most recently inserted. Returns null past the oldest entry.
The position of the newest entry whose name and value both match, or
null. A position is 0 for the newest entry, the same basis as get.
The position of the newest entry with this name, or null.
Add an entry and evict as §4.4 requires. An entry larger than the maximum empties the table and is not added; that is not an error.
name and value must not point into bytes; a decoder copies the
strings it decodes before calling this (§4.4 warns about the case).
pub fn setMaxSize(self: *DynamicTable, new_max: usize) error{TooLarge}!void
src
Change the maximum size, evicting until the table fits (RFC 7541 §4.3). A size above the storage is a decoding error for the caller.
module
TableStorage
src
The storage a dynamic table reads: one opaque byte blob.
more
HPACK's table maximum is runtime (SETTINGS_HEADER_TABLE_SIZE), so the
storage is sized at runtime too. init allocates the blob; initBuffer
wraps a caller buffer for a stack or fixed-size server. deinit frees the
blob, so call it only for a storage from init: a caller that used
initBuffer frees its own memory, exactly as with std.array_list.Aligned.
The blob is laid out as the byte arena, padding to the slot alignment, the
slot records, and the hashes. The blob's own alignment is not trusted: the
views start at the next @alignOf(Slot) boundary, and bytesNeeded
includes that slack. Nothing else is stored, so a table is one slice and one
u32 rather than a set of fat pointers.
The records stay an array of structs and the hashes are split out: the lookup scans the contiguous hashes, then reads one 16-byte record on a hit. Putting the hash in the record makes the scan stride over 20 bytes to read 4.
Constants
pub const entry_overhead: usize = 32
src
The per-entry accounting overhead (RFC 7541 §4.1).
value
Allocator.Error || error{
/// The caller's blob is shorter than `bytesNeeded(max_size)`.
BufferTooSmall,
/// `max_size` does not fit the `u32` the table stores.
TooLarge,
}Functions
How many entries a table of max_size bytes can hold. Every entry costs at
least entry_overhead bytes of accounting.
pub fn bytesNeeded(max_size: usize) usize
src
The bytes a table of max_size bytes needs, including the slack to align
the blob's start to a Slot.
Allocate one blob for a table that can grow to max_size bytes.
pub fn initBuffer(memory: []u8, max_size: usize) Error!TableStorage
src
Wrap a caller's blob. It needs bytesNeeded(max_size) bytes; a shorter
one is error.BufferTooSmall, not an assertion.
Release the blob. Call this only for a storage from init; a caller that
used initBuffer owns and frees its own memory.
The three slices, aligned and laid out inside memory.
Types
module
huffman
src
The static Huffman code from RFC 7541 Appendix B.
more
HPACK uses it for string literals; QPACK reuses it unchanged (RFC 9204 §4.1.3). The code is canonical: symbols of one length hold consecutive code values, so decoding is an 8-bit root table for the short codes and a canonical scan for the rest. Every letter, digit, and most punctuation in a field value is eight bits or shorter, so the scan is the rare path.
Decoding enforces RFC 7541 §5.2: EOS is an error, padding longer than seven bits is an error, and padding must be the leading bits of EOS.
Constants
value
error{
/// Not a code, an encoded EOS symbol, or padding that is not EOS's prefix.
InvalidCode,
/// The destination buffer is too small.
NoSpace,
}pub const EncodeError = error{NoSpace}
src
encode cannot fail on the input, only on the destination.
value
.{
.{ .bits = 0x1ff8, .len = 13 }, // 0x00
.{ .bits = 0x7fffd8, .len = 23 }, // 0x01
.{ .bits = 0xfffffe2, .len = 28 }, // 0x02
.{ .bits = 0xfffffe3, .len = 28 }, // 0x03
.{ .bits = 0xfffffe4, .len = 28 }, // 0x04
.{ .bits = 0xfffffe5, .len = 28 }, // 0x05
.{ .bits = 0xfffffe6, .len = 28 }, // 0x06
.{ .bits = 0xfffffe7, .len = 28 }, // 0x07
.{ .bits = 0xfffffe8, .len = 28 }, // 0x08
.{ .bits = 0xffffea, .len = 24 }, // 0x09
.{ .bits = 0x3ffffffc, .len = 30 }, // 0x0a
.{ .bits = 0xfffffe9, .len = 28 }, // 0x0b
.{ .bits = 0xfffffea, .len = 28 }, // 0x0c
.{ .bits = 0x3ffffffd, .len = 30 }, // 0x0d
.{ .bits = 0xfffffeb, .len = 28 }, // 0x0e
.{ .bits = 0xfffffec, .len = 28 }, // 0x0f
.{ .bits = 0xfffffed, .len = 28 }, // 0x10
.{ .bits = 0xfffffee, .len = 28 }, // 0x11
.{ .bits = 0xfffffef, .len = 28 }, // 0x12
.{ .bits = 0xffffff0, .len = 28 }, // 0x13
.{ .bits = 0xffffff1, .len = 28 }, // 0x14
.{ .bits = 0xffffff2, .len = 28 }, // 0x15
.{ .bits = 0x3ffffffe, .len = 30 }, // 0x16
.{ .bits = 0xffffff3, .len = 28 }, // 0x17
.{ .bits = 0xffffff4, .len = 28 }, // 0x18
.{ .bits = 0xffffff5, .len = 28 }, // 0x19
.{ .bits = 0xffffff6, .len = 28 }, // 0x1a
.{ .bits = 0xffffff7, .len = 28 }, // 0x1b
.{ .bits = 0xffffff8, .len = 28 }, // 0x1c
.{ .bits = 0xffffff9, .len = 28 }, // 0x1d
.{ .bits = 0xffffffa, .len = 28 }, // 0x1e
.{ .bits = 0xffffffb, .len = 28 }, // 0x1f
.{ .bits = 0x14, .len = 6 }, // 0x20
.{ .bits = 0x3f8, .len = 10 }, // 0x21
.{ .bits = 0x3f9, .len = 10 }, // 0x22
.{ .bits = 0xffa, .len = 12 }, // 0x23
.{ .bits = 0x1ff9, .len = 13 }, // 0x24
.{ .bits = 0x15, .len = 6 }, // 0x25
.{ .bits = 0xf8, .len = 8 }, // 0x26
.{ .bits = 0x7fa, .len = 11 }, // 0x27
.{ .bits = 0x3fa, .len = 10 }, // 0x28
.{ .bits = 0x3fb, .len = 10 }, // 0x29
.{ .bits = 0xf9, .len = 8 }, // 0x2a
.{ .bits = 0x7fb, .len = 11 }, // 0x2b
.{ .bits = 0xfa, .len = 8 }, // 0x2c
.{ .bits = 0x16, .len = 6 }, // 0x2d
.{ .bits = 0x17, .len = 6 }, // 0x2e
.{ .bits = 0x18, .len = 6 }, // 0x2f
.{ .bits = 0x0, .len = 5 }, // 0x30
.{ .bits = 0x1, .len = 5 }, // 0x31
.{ .bits = 0x2, .len = 5 }, // 0x32
.{ .bits = 0x19, .len = 6 }, // 0x33
.{ .bits = 0x1a, .len = 6 }, // 0x34
.{ .bits = 0x1b, .len = 6 }, // 0x35
.{ .bits = 0x1c, .len = 6 }, // 0x36
.{ .bits = 0x1d, .len = 6 }, // 0x37
.{ .bits = 0x1e, .len = 6 }, // 0x38
.{ .bits = 0x1f, .len = 6 }, // 0x39
.{ .bits = 0x5c, .len = 7 }, // 0x3a
.{ .bits = 0xfb, .len = 8 }, // 0x3b
.{ .bits = 0x7ffc, .len = 15 }, // 0x3c
.{ .bits = 0x20, .len = 6 }, // 0x3d
.{ .bits = 0xffb, .len = 12 }, // 0x3e
.{ .bits = 0x3fc, .len = 10 }, // 0x3f
.{ .bits = 0x1ffa, .len = 13 }, // 0x40
.{ .bits = 0x21, .len = 6 }, // 0x41
.{ .bits = 0x5d, .len = 7 }, // 0x42
.{ .bits = 0x5e, .len = 7 }, // 0x43
.{ .bits = 0x5f, .len = 7 }, // 0x44
.{ .bits = 0x60, .len = 7 }, // 0x45
.{ .bits = 0x61, .len = 7 }, // 0x46
.{ .bits = 0x62, .len = 7 }, // 0x47
.{ .bits = 0x63, .len = 7 }, // 0x48
.{ .bits = 0x64, .len = 7 }, // 0x49
.{ .bits = 0x65, .len = 7 }, // 0x4a
.{ .bits = 0x66, .len = 7 }, // 0x4b
.{ .bits = 0x67, .len = 7 }, // 0x4c
.{ .bits = 0x68, .len = 7 }, // 0x4d
.{ .bits = 0x69, .len = 7 }, // 0x4e
.{ .bits = 0x6a, .len = 7 }, // 0x4f
.{ .bits = 0x6b, .len = 7 }, // 0x50
.{ .bits = 0x6c, .len = 7 }, // 0x51
.{ .bits = 0x6d, .len = 7 }, // 0x52
.{ .bits = 0x6e, .len = 7 }, // 0x53
.{ .bits = 0x6f, .len = 7 }, // 0x54
.{ .bits = 0x70, .len = 7 }, // 0x55
.{ .bits = 0x71, .len = 7 }, // 0x56
.{ .bits = 0x72, .len = 7 }, // 0x57
.{ .bits = 0xfc, .len = 8 }, // 0x58
.{ .bits = 0x73, .len = 7 }, // 0x59
.{ .bits = 0xfd, .len = 8 }, // 0x5a
.{ .bits = 0x1ffb, .len = 13 }, // 0x5b
.{ .bits = 0x7fff0, .len = 19 }, // 0x5c
.{ .bits = 0x1ffc, .len = 13 }, // 0x5d
.{ .bits = 0x3ffc, .len = 14 }, // 0x5e
.{ .bits = 0x22, .len = 6 }, // 0x5f
.{ .bits = 0x7ffd, .len = 15 }, // 0x60
.{ .bits = 0x3, .len = 5 }, // 0x61
.{ .bits = 0x23, .len = 6 }, // 0x62
.{ .bits = 0x4, .len = 5 }, // 0x63
.{ .bits = 0x24, .len = 6 }, // 0x64
.{ .bits = 0x5, .len = 5 }, // 0x65
.{ .bits = 0x25, .len = 6 }, // 0x66
.{ .bits = 0x26, .len = 6 }, // 0x67
.{ .bits = 0x27, .len = 6 }, // 0x68
.{ .bits = 0x6, .len = 5 }, // 0x69
.{ .bits = 0x74, .len = 7 }, // 0x6a
.{ .bits = 0x75, .len = 7 }, // 0x6b
.{ .bits = 0x28, .len = 6 }, // 0x6c
.{ .bits = 0x29, .len = 6 }, // 0x6d
.{ .bits = 0x2a, .len = 6 }, // 0x6e
.{ .bits = 0x7, .len = 5 }, // 0x6f
.{ .bits = 0x2b, .len = 6 }, // 0x70
.{ .bits = 0x76, .len = 7 }, // 0x71
.{ .bits = 0x2c, .len = 6 }, // 0x72
.{ .bits = 0x8, .len = 5 }, // 0x73
.{ .bits = 0x9, .len = 5 }, // 0x74
.{ .bits = 0x2d, .len = 6 }, // 0x75
.{ .bits = 0x77, .len = 7 }, // 0x76
.{ .bits = 0x78, .len = 7 }, // 0x77
.{ .bits = 0x79, .len = 7 }, // 0x78
.{ .bits = 0x7a, .len = 7 }, // 0x79
.{ .bits = 0x7b, .len = 7 }, // 0x7a
.{ .bits = 0x7ffe, .len = 15 }, // 0x7b
.{ .bits = 0x7fc, .len = 11 }, // 0x7c
.{ .bits = 0x3ffd, .len = 14 }, // 0x7d
.{ .bits = 0x1ffd, .len = 13 }, // 0x7e
.{ .bits = 0xffffffc, .len = 28 }, // 0x7f
.{ .bits = 0xfffe6, .len = 20 }, // 0x80
.{ .bits = 0x3fffd2, .len = 22 }, // 0x81
.{ .bits = 0xfffe7, .len = 20 }, // 0x82
.{ .bits = 0xfffe8, .len = 20 }, // 0x83
.{ .bits = 0x3fffd3, .len = 22 }, // 0x84
.{ .bits = 0x3fffd4, .len = 22 }, // 0x85
.{ .bits = 0x3fffd5, .len = 22 }, // 0x86
.{ .bits = 0x7fffd9, .len = 23 }, // 0x87
.{ .bits = 0x3fffd6, .len = 22 }, // 0x88
.{ .bits = 0x7fffda, .len = 23 }, // 0x89
.{ .bits = 0x7fffdb, .len = 23 }, // 0x8a
.{ .bits = 0x7fffdc, .len = 23 }, // 0x8b
.{ .bits = 0x7fffdd, .len = 23 }, // 0x8c
.{ .bits = 0x7fffde, .len = 23 }, // 0x8d
.{ .bits = 0xffffeb, .len = 24 }, // 0x8e
.{ .bits = 0x7fffdf, .len = 23 }, // 0x8f
.{ .bits = 0xffffec, .len = 24 }, // 0x90
.{ .bits = 0xffffed, .len = 24 }, // 0x91
.{ .bits = 0x3fffd7, .len = 22 }, // 0x92
.{ .bits = 0x7fffe0, .len = 23 }, // 0x93
.{ .bits = 0xffffee, .len = 24 }, // 0x94
.{ .bits = 0x7fffe1, .len = 23 }, // 0x95
.{ .bits = 0x7fffe2, .len = 23 }, // 0x96
.{ .bits = 0x7fffe3, .len = 23 }, // 0x97
.{ .bits = 0x7fffe4, .len = 23 }, // 0x98
.{ .bits = 0x1fffdc, .len = 21 }, // 0x99
.{ .bits = 0x3fffd8, .len = 22 }, // 0x9a
.{ .bits = 0x7fffe5, .len = 23 }, // 0x9b
.{ .bits = 0x3fffd9, .len = 22 }, // 0x9c
.{ .bits = 0x7fffe6, .len = 23 }, // 0x9d
.{ .bits = 0x7fffe7, .len = 23 }, // 0x9e
.{ .bits = 0xffffef, .len = 24 }, // 0x9f
.{ .bits = 0x3fffda, .len = 22 }, // 0xa0
.{ .bits = 0x1fffdd, .len = 21 }, // 0xa1
.{ .bits = 0xfffe9, .len = 20 }, // 0xa2
.{ .bits = 0x3fffdb, .len = 22 }, // 0xa3
.{ .bits = 0x3fffdc, .len = 22 }, // 0xa4
.{ .bits = 0x7fffe8, .len = 23 }, // 0xa5
.{ .bits = 0x7fffe9, .len = 23 }, // 0xa6
.{ .bits = 0x1fffde, .len = 21 }, // 0xa7
.{ .bits = 0x7fffea, .len = 23 }, // 0xa8
.{ .bits = 0x3fffdd, .len = 22 }, // 0xa9
.{ .bits = 0x3fffde, .len = 22 }, // 0xaa
.{ .bits = 0xfffff0, .len = 24 }, // 0xab
.{ .bits = 0x1fffdf, .len = 21 }, // 0xac
.{ .bits = 0x3fffdf, .len = 22 }, // 0xad
.{ .bits = 0x7fffeb, .len = 23 }, // 0xae
.{ .bits = 0x7fffec, .len = 23 }, // 0xaf
.{ .bits = 0x1fffe0, .len = 21 }, // 0xb0
.{ .bits = 0x1fffe1, .len = 21 }, // 0xb1
.{ .bits = 0x3fffe0, .len = 22 }, // 0xb2
.{ .bits = 0x1fffe2, .len = 21 }, // 0xb3
.{ .bits = 0x7fffed, .len = 23 }, // 0xb4
.{ .bits = 0x3fffe1, .len = 22 }, // 0xb5
.{ .bits = 0x7fffee, .len = 23 }, // 0xb6
.{ .bits = 0x7fffef, .len = 23 }, // 0xb7
.{ .bits = 0xfffea, .len = 20 }, // 0xb8
.{ .bits = 0x3fffe2, .len = 22 }, // 0xb9
.{ .bits = 0x3fffe3, .len = 22 }, // 0xba
.{ .bits = 0x3fffe4, .len = 22 }, // 0xbb
.{ .bits = 0x7ffff0, .len = 23 }, // 0xbc
.{ .bits = 0x3fffe5, .len = 22 }, // 0xbd
.{ .bits = 0x3fffe6, .len = 22 }, // 0xbe
.{ .bits = 0x7ffff1, .len = 23 }, // 0xbf
.{ .bits = 0x3ffffe0, .len = 26 }, // 0xc0
.{ .bits = 0x3ffffe1, .len = 26 }, // 0xc1
.{ .bits = 0xfffeb, .len = 20 }, // 0xc2
.{ .bits = 0x7fff1, .len = 19 }, // 0xc3
.{ .bits = 0x3fffe7, .len = 22 }, // 0xc4
.{ .bits = 0x7ffff2, .len = 23 }, // 0xc5
.{ .bits = 0x3fffe8, .len = 22 }, // 0xc6
.{ .bits = 0x1ffffec, .len = 25 }, // 0xc7
.{ .bits = 0x3ffffe2, .len = 26 }, // 0xc8
.{ .bits = 0x3ffffe3, .len = 26 }, // 0xc9
.{ .bits = 0x3ffffe4, .len = 26 }, // 0xca
.{ .bits = 0x7ffffde, .len = 27 }, // 0xcb
.{ .bits = 0x7ffffdf, .len = 27 }, // 0xcc
.{ .bits = 0x3ffffe5, .len = 26 }, // 0xcd
.{ .bits = 0xfffff1, .len = 24 }, // 0xce
.{ .bits = 0x1ffffed, .len = 25 }, // 0xcf
.{ .bits = 0x7fff2, .len = 19 }, // 0xd0
.{ .bits = 0x1fffe3, .len = 21 }, // 0xd1
.{ .bits = 0x3ffffe6, .len = 26 }, // 0xd2
.{ .bits = 0x7ffffe0, .len = 27 }, // 0xd3
.{ .bits = 0x7ffffe1, .len = 27 }, // 0xd4
.{ .bits = 0x3ffffe7, .len = 26 }, // 0xd5
.{ .bits = 0x7ffffe2, .len = 27 }, // 0xd6
.{ .bits = 0xfffff2, .len = 24 }, // 0xd7
.{ .bits = 0x1fffe4, .len = 21 }, // 0xd8
.{ .bits = 0x1fffe5, .len = 21 }, // 0xd9
.{ .bits = 0x3ffffe8, .len = 26 }, // 0xda
.{ .bits = 0x3ffffe9, .len = 26 }, // 0xdb
.{ .bits = 0xffffffd, .len = 28 }, // 0xdc
.{ .bits = 0x7ffffe3, .len = 27 }, // 0xdd
.{ .bits = 0x7ffffe4, .len = 27 }, // 0xde
.{ .bits = 0x7ffffe5, .len = 27 }, // 0xdf
.{ .bits = 0xfffec, .len = 20 }, // 0xe0
.{ .bits = 0xfffff3, .len = 24 }, // 0xe1
.{ .bits = 0xfffed, .len = 20 }, // 0xe2
.{ .bits = 0x1fffe6, .len = 21 }, // 0xe3
.{ .bits = 0x3fffe9, .len = 22 }, // 0xe4
.{ .bits = 0x1fffe7, .len = 21 }, // 0xe5
.{ .bits = 0x1fffe8, .len = 21 }, // 0xe6
.{ .bits = 0x7ffff3, .len = 23 }, // 0xe7
.{ .bits = 0x3fffea, .len = 22 }, // 0xe8
.{ .bits = 0x3fffeb, .len = 22 }, // 0xe9
.{ .bits = 0x1ffffee, .len = 25 }, // 0xea
.{ .bits = 0x1ffffef, .len = 25 }, // 0xeb
.{ .bits = 0xfffff4, .len = 24 }, // 0xec
.{ .bits = 0xfffff5, .len = 24 }, // 0xed
.{ .bits = 0x3ffffea, .len = 26 }, // 0xee
.{ .bits = 0x7ffff4, .len = 23 }, // 0xef
.{ .bits = 0x3ffffeb, .len = 26 }, // 0xf0
.{ .bits = 0x7ffffe6, .len = 27 }, // 0xf1
.{ .bits = 0x3ffffec, .len = 26 }, // 0xf2
.{ .bits = 0x3ffffed, .len = 26 }, // 0xf3
.{ .bits = 0x7ffffe7, .len = 27 }, // 0xf4
.{ .bits = 0x7ffffe8, .len = 27 }, // 0xf5
.{ .bits = 0x7ffffe9, .len = 27 }, // 0xf6
.{ .bits = 0x7ffffea, .len = 27 }, // 0xf7
.{ .bits = 0x7ffffeb, .len = 27 }, // 0xf8
.{ .bits = 0xffffffe, .len = 28 }, // 0xf9
.{ .bits = 0x7ffffec, .len = 27 }, // 0xfa
.{ .bits = 0x7ffffed, .len = 27 }, // 0xfb
.{ .bits = 0x7ffffee, .len = 27 }, // 0xfc
.{ .bits = 0x7ffffef, .len = 27 }, // 0xfd
.{ .bits = 0x7fffff0, .len = 27 }, // 0xfe
.{ .bits = 0x3ffffee, .len = 26 }, // 0xff
.{ .bits = 0x3fffffff, .len = 30 }, // EOS
}Indexed by symbol; 256 is EOS. bits is right-aligned.
Functions
Decode encoded into out, returning the number of bytes written.
Encode src into out, returning the number of bytes written. The last
octet is padded with the leading bits of EOS, as RFC 7541 §5.2 requires.
pub fn encodedLen(src: []const u8) usize
src
The number of octets encode writes for src.
Types
module
Cursor
src
A byte cursor: one read position over borrowed bytes.
more
Every parser here walks a buffer from the front, so the position and the
bounds checks live in one place. The cursor is deliberately infallible: it
never decides whether running out is an error. next, peek, and take
say null and each protocol maps that to its own vocabulary (Incomplete
for HTTP/1.1, Truncated for HPACK). at and slice assert the bounds
the caller already checked, for scanners that look ahead before moving.
Offsets are u32, so a buffer must be shorter than 4 GiB. That is not a
limitation for a message head or a header block, and it keeps offsets half
the size of a usize on a 64-bit target.
Functions
A cursor at the start of buf. buf must be shorter than 4 GiB.
The number of bytes not read yet.
Whether at least count bytes are left.
The next byte, without reading it.
The byte at an absolute offset. The caller guarantees it is in bounds, as a
scanner that looked ahead with has already did.
Read one byte, or null at the end.
Read count bytes, or null when fewer are left. Nothing moves on null.
Advance count bytes. False, and nothing moves, when fewer are left.
A slice by absolute offsets. The caller guarantees the range.
Everything from pos to the end.
struct
Headers
src
pub const Headers = @import("header.zig").Headers
A borrowed view of every field line in a message, in wire order.
items points into the caller's header storage, and each item's name and
value point into the message buffer. The view is valid only while both are
unchanged; see h1.RequestHead. The view owns nothing.
Fields
items: []const Header
Functions
The number of field lines in the section.
The first value named name, or null. Use this only for fields the RFC
defines as a singleton (Host, Content-Length). A list-valued field
may repeat, and duplicates combine; use values and feed them to the
family parser.
Every value named name, in wire order, with OWS trimmed.
Each field line is yielded separately. Set-Cookie needs this; so does
anything that distinguishes "one field with a comma" from "two fields".
pub fn containsToken(self: Headers, name: []const u8, token: []const u8) bool
src
True when any line named name contains token as a list element.
This is the shape for Connection, Upgrade, and Vary: every line
is checked, so a duplicated field cannot hide a token.
enum
Method
src
pub const Method = @import("method.zig").Method
Fields
GET GET
HEAD HEAD
POST POST
PUT PUT
DELETE DELETE
CONNECT CONNECT
OPTIONS OPTIONS
TRACE TRACE
PATCH PATCH
Functions
The exact wire token. Methods are case-sensitive (RFC 9110 §9.1).
The method with this exact token, or null. get is not GET.
pub fn requestHasBody(self: Method) bool
src
Whether a request with this method normally carries content.
pub fn responseHasBody(self: Method) bool
src
Whether a response to this method normally carries content.
Safe methods do not alter server state (RFC 9110 §9.2.1).
pub fn idempotent(self: Method) bool
src
Idempotent methods can repeat without a different effect (RFC 9110 §9.2.2).
Cacheable methods (RFC 9110 §9.2.3).
module
method
src
The common request methods, as a caller-facing vocabulary.
more
On the wire a method is a token, the comparison is case-sensitive, and the
IANA registry is open (RFC 9110 §9.1, §16.1.1). The h1 parser keeps the
exact bytes. This enum names the methods a general client or server routes
in practice; parse returns null for anything else, and the token stays
the source of truth.
The tags are the wire spelling, so Method.GET.token() is "GET" and
Method.parse("get") is null.
Types
enum
Method
src
Fields
GET GET
HEAD HEAD
POST POST
PUT PUT
DELETE DELETE
CONNECT CONNECT
OPTIONS OPTIONS
TRACE TRACE
PATCH PATCH
Functions
The exact wire token. Methods are case-sensitive (RFC 9110 §9.1).
The method with this exact token, or null. get is not GET.
pub fn requestHasBody(self: Method) bool
src
Whether a request with this method normally carries content.
pub fn responseHasBody(self: Method) bool
src
Whether a response to this method normally carries content.
Safe methods do not alter server state (RFC 9110 §9.2.1).
pub fn idempotent(self: Method) bool
src
Idempotent methods can repeat without a different effect (RFC 9110 §9.2.2).
Cacheable methods (RFC 9110 §9.2.3).
module
prefix_int
src
Prefix integers, shared by HPACK and QPACK (RFC 7541 §5.1).
more
An integer of up to 32 bits starts with an N-bit prefix in an octet and
continues into whole octets when the value does not fit. N is 1..8 and is
a property of the representation that owns the integer, not of this module.
The caller keeps the high bits of the first octet. decode masks them off,
and encode leaves them zero, so the caller ORs in the representation tag:
var buf: [5]u8 = undefined;
const out = try prefix_int.encode(&buf, index, 7);
out[0] |= 0x80;
Input is bounded to u32, which covers every index and string length HPACK
or QPACK can express over a buffer that fits in memory. RFC 7541 §5.1
permits this: "Integer encodings that exceed implementation limits -- in
value or octet length -- MUST be treated as decoding errors." Encodings
longer than needed are accepted, as the RFC does not require minimal form.
Constants
value
error{
/// The input ended inside an integer.
Truncated,
/// The value does not fit in 32 bits.
TooLarge,
}pub const EncodeError = error{NoSpace}
src
encode cannot fail on the value, only on the destination.
The largest prefix this representation has: 8 bits, the whole octet.
Functions
Decode the integer whose prefix is the low bits bits of the next octet,
advancing cursor past the prefix and any continuation octets.
pub fn encodedLen(value: u32, bits: u4) usize
src
The number of octets encode writes for value with a bits-wide prefix.
Encode value with a bits-wide prefix. The high bits of the first octet
are zero; the caller ORs in the representation tag.
module
response
src
An outbound HTTP response, with the known fields kept as values.
status, content_type, and content_length are typed. The HPACK encoder
writes their text at wire time, so a caller does not build a Header array
or carry temporary status and length strings. headers holds the regular
fields that are already text.
Types
struct
Response
src
Fields
status: Status
content_type: ?[]const u8 = null
content_length: ?u64 = null
The engine sets this; h2.Session.submitResponse encodes it.
headers: []const Header = &.{}
Extra regular fields. Pseudo-headers and content-length belong in the
typed fields, not here.
enum
Status
src
pub const Status = @import("status.zig").Status
Fields
@"continue" @"continue" = 100
switching_protocols switching_protocols = 101
processing processing = 102
early_hints early_hints = 103
ok ok = 200
created created = 201
accepted accepted = 202
non_authoritative_info non_authoritative_info = 203
no_content no_content = 204
reset_content reset_content = 205
partial_content partial_content = 206
multi_status multi_status = 207
already_reported already_reported = 208
im_used im_used = 226
multiple_choice multiple_choice = 300
moved_permanently moved_permanently = 301
found found = 302
see_other see_other = 303
not_modified not_modified = 304
use_proxy use_proxy = 305
temporary_redirect temporary_redirect = 307
permanent_redirect permanent_redirect = 308
bad_request bad_request = 400
unauthorized unauthorized = 401
payment_required payment_required = 402
forbidden forbidden = 403
not_found not_found = 404
method_not_allowed method_not_allowed = 405
not_acceptable not_acceptable = 406
proxy_auth_required proxy_auth_required = 407
request_timeout request_timeout = 408
conflict conflict = 409
gone gone = 410
length_required length_required = 411
precondition_failed precondition_failed = 412
payload_too_large payload_too_large = 413
uri_too_long uri_too_long = 414
unsupported_media_type unsupported_media_type = 415
range_not_satisfiable range_not_satisfiable = 416
expectation_failed expectation_failed = 417
teapot teapot = 418
misdirected_request misdirected_request = 421
unprocessable_entity unprocessable_entity = 422
locked locked = 423
failed_dependency failed_dependency = 424
too_early too_early = 425
upgrade_required upgrade_required = 426
precondition_required precondition_required = 428
too_many_requests too_many_requests = 429
request_header_fields_too_large request_header_fields_too_large = 431
internal_server_error internal_server_error = 500
not_implemented not_implemented = 501
bad_gateway bad_gateway = 502
gateway_timeout gateway_timeout = 504
http_version_not_supported http_version_not_supported = 505
variant_also_negotiates variant_also_negotiates = 506
insufficient_storage insufficient_storage = 507
loop_detected loop_detected = 508
not_extended not_extended = 510
network_authentication_required network_authentication_required = 511
_ _
Functions
The three-digit wire value.
The three ASCII digits of the wire status. The status must be 100..999;
h2.head.validateResponse enforces that before encoding.
The status with this number, or null when it cannot be three digits. A three-digit code the enum does not name returns a value, not null.
The class of the code, or null when the value is outside 100..599 (RFC 9110 §15).
The canonical reason phrase, or null for an unnamed code. The phrase is advisory: a client does not use it to decide anything (RFC 9110 §15.2).
Types
enum
Class
src
Fields
informational informational
success success
redirect redirect
client_error client_error
server_error server_error
module
status
src
HTTP status codes as an open enum.
more
The wire value is three digits, and RFC 9110 §15 requires a recipient to
treat an unrecognized code as the x00 of its class. So this type is
non-exhaustive: a code the enum does not name is still a value, and class
works on it. parse rejects only a number that cannot be three digits.
The names and values mirror std.http.Status, so Zig callers already know
them. Unlike a re-export, this type is stable across Zig releases and
carries its own phrase table.
Types
enum
Status
src
pub const Status = enum(u10)
Fields
@"continue" @"continue" = 100
switching_protocols switching_protocols = 101
processing processing = 102
early_hints early_hints = 103
ok ok = 200
created created = 201
accepted accepted = 202
non_authoritative_info non_authoritative_info = 203
no_content no_content = 204
reset_content reset_content = 205
partial_content partial_content = 206
multi_status multi_status = 207
already_reported already_reported = 208
im_used im_used = 226
multiple_choice multiple_choice = 300
moved_permanently moved_permanently = 301
found found = 302
see_other see_other = 303
not_modified not_modified = 304
use_proxy use_proxy = 305
temporary_redirect temporary_redirect = 307
permanent_redirect permanent_redirect = 308
bad_request bad_request = 400
unauthorized unauthorized = 401
payment_required payment_required = 402
forbidden forbidden = 403
not_found not_found = 404
method_not_allowed method_not_allowed = 405
not_acceptable not_acceptable = 406
proxy_auth_required proxy_auth_required = 407
request_timeout request_timeout = 408
conflict conflict = 409
gone gone = 410
length_required length_required = 411
precondition_failed precondition_failed = 412
payload_too_large payload_too_large = 413
uri_too_long uri_too_long = 414
unsupported_media_type unsupported_media_type = 415
range_not_satisfiable range_not_satisfiable = 416
expectation_failed expectation_failed = 417
teapot teapot = 418
misdirected_request misdirected_request = 421
unprocessable_entity unprocessable_entity = 422
locked locked = 423
failed_dependency failed_dependency = 424
too_early too_early = 425
upgrade_required upgrade_required = 426
precondition_required precondition_required = 428
too_many_requests too_many_requests = 429
request_header_fields_too_large request_header_fields_too_large = 431
internal_server_error internal_server_error = 500
not_implemented not_implemented = 501
bad_gateway bad_gateway = 502
gateway_timeout gateway_timeout = 504
http_version_not_supported http_version_not_supported = 505
variant_also_negotiates variant_also_negotiates = 506
insufficient_storage insufficient_storage = 507
loop_detected loop_detected = 508
not_extended not_extended = 510
network_authentication_required network_authentication_required = 511
_ _
Functions
The three-digit wire value.
The three ASCII digits of the wire status. The status must be 100..999;
h2.head.validateResponse enforces that before encoding.
The status with this number, or null when it cannot be three digits. A three-digit code the enum does not name returns a value, not null.
The class of the code, or null when the value is outside 100..599 (RFC 9110 §15).
The canonical reason phrase, or null for an unnamed code. The phrase is advisory: a client does not use it to decide anything (RFC 9110 §15.2).
Types
enum
Class
src
Fields
informational informational
success success
redirect redirect
client_error client_error
server_error server_error
module
token_list
src
Comma-separated token lists (RFC 9110 §5.6.1).
more
contains(field, token) answers membership without allocating. iterator
yields each non-empty element with OWS trimmed, in the order it appears.
Elements are returned whole. A field whose grammar allows parameters or
quoted strings (TE, Expect) needs more than this and is not covered.
Empty elements are ignored, as the list rule requires.
Functions
Whether field contains token as an element, ignoring case and OWS.
Types
module
transfer_encoding
src
Transfer-Encoding parsing (RFC 9110 §10.1.4, RFC 9112 §6.1).
more
The field is #transfer-coding, where
transfer-coding = token *( OWS ";" OWS transfer-parameter ) and
transfer-parameter = token BWS "=" BWS ( token / quoted-string ).
Field lines combine in order.
chunked takes no parameters and must appear once (RFC 9112 §7.1). It has
to be the final coding for a request; a response with a non-final chunked
coding is close-delimited, so the framing decision belongs to the caller.
Summary.seen_chunked and Summary.chunked_last expose both facts.
Constants
value
error{
Invalid,
ChunkedDuplicate,
ChunkedParameters,
}Functions
Types
struct
Coding
src
Fields
name: []const u8
The coding token, borrowed from the field value.
has_parameters: bool
Whether the coding carries at least one parameter.
struct
Summary
src
Fields
present: bool
Whether at least one Transfer-Encoding field line is present.
last: ?Coding
The final coding, or null when the field is absent or carries no coding.
seen_chunked: bool
Whether a chunked coding appeared anywhere in the list.
chunked_last: bool
Whether the final coding is chunked.
enum
Version
src
pub const Version = @import("version.zig").Version
The version a recipient processes a message as. A message with major version 1 and a higher minor version is processed as 1.1 (RFC 9110 §6.2).
Fields
@"HTTP/1.0" @"HTTP/1.0"
@"HTTP/1.1" @"HTTP/1.1"
struct
WireVersion
src
pub const WireVersion = @import("version.zig").WireVersion
The two decimal digits from the start-line, exactly as sent.
RFC 9112 §2.3: HTTP-version = HTTP-name "/" DIGIT "." DIGIT.
Fields