http API reference Changelog View on GitHub

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

pub const Error = error{ Invalid, Conflicting, Overflow }

src

Functions

pub fn parse(values: []const []const u8) Error!?u64

src

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

pub fn feed(self: *Parser, value: []const u8) Error!void

src

Add one field line. Repetition is allowed only when every value is the same decimal number.

pub fn finish(self: Parser) ?u64

src

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 q parameter, 0..1 with at most three fractional digits; no parameter means 1. q=0 is not acceptable.
  • * gives a weight to every coding not named explicitly.
  • identity is acceptable by default. It is excluded by identity;q=0, or by *;q=0 when 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

pub const identity = "identity"

src

The token for "no content coding". It is not looked up in supported.

Functions

pub fn negotiate(header: []const u8, supported: []const []const u8) ?[]const u8

src

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

pub const ReadError = …

src
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 ReadResult

src

A single successful read. The slice is never empty.

Fields

data: []const u8

struct ReadStream

src

Fields

ptr: *anyopaque
vtable: *const VTable

Functions

pub fn next(self: ReadStream) ReadError!ReadResult

src

pub fn close(self: ReadStream) void

src

Types

struct VTable

src

Fields

next: *const fn (ptr: *anyopaque) ReadError!ReadResult
close: *const fn (ptr: *anyopaque) void

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

io: Io
queue: *Io.Queue([]const u8)
stream: ReadStream

Functions

pub fn init(io: Io, queue: *Io.Queue([]const u8)) QueueStream

src

pub fn source(self: *QueueStream) ReadStream

src

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

reader: *Io.Reader
buffer: []u8
stream: ReadStream

Functions

pub fn init(reader: *Io.Reader, buffer: []u8) ReaderStream

src

pub fn source(self: *ReaderStream) ReadStream

src

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 PolicyContext

src

The context for a machine rejection. The head may not exist yet.

Fields

id: usize

The engine slot that rejected input.

status: Status

The status chosen by the parser or connection machine.

reason: RejectReason

The precise rejection category.

arena: Allocator

Storage for a replacement body. It lives until the response completes.

struct PolicyResponse

src

A replacement for the default status-and-empty-body rejection.

Fields

options: ResponseOptions
body: []const u8

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

ptr: *anyopaque
vtable: *const VTable

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 Protocol

src

The negotiated HTTP protocol a request arrived on.

Fields

h1 h1
h2 h2

struct Waker

src

A wake handle from the engine. It is safe to call from another thread and remains valid until the server is deinitialized. Copy it out of the exchange before returning from handle.

Fields

ptr: *anyopaque
vtable: *const VTable

Functions

pub fn wake(self: Waker) void

src

Types

struct VTable

src

Fields

wake: *const fn (ptr: *anyopaque) void

type fn Deferred

src
pub fn Deferred(comptime Exchange: type) type

A handler that has returned without a response and will be resumed when its external wait completes.

Fields

context: *anyopaque
callback: *const fn ( context: *anyopaque, exchange: *Exchange, ) anyerror!ResumeResult
cancel: *const fn (context: *anyopaque) void

Called if the exchange ends before the callback responds, so the external task can be stopped before the arena is reset.

enum ResumeResult

src

Fields

responded responded

The callback queued a response or a response stream.

waiting waiting

The callback has more waiting to do. The slot stays parked.

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

pub const ReadError = …

src
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 ReadResult

src

A single successful read. The slice is never empty.

Fields

data: []const u8

struct ReadStream

src

Fields

ptr: *anyopaque
vtable: *const VTable

Functions

pub fn next(self: ReadStream) ReadError!ReadResult

src

pub fn close(self: ReadStream) void

src

Types

struct VTable

src

Fields

next: *const fn (ptr: *anyopaque) ReadError!ReadResult
close: *const fn (ptr: *anyopaque) void

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

io: Io
queue: *Io.Queue([]const u8)
stream: ReadStream

Functions

pub fn init(io: Io, queue: *Io.Queue([]const u8)) QueueStream

src

pub fn source(self: *QueueStream) ReadStream

src

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

reader: *Io.Reader
buffer: []u8
stream: ReadStream

Functions

pub fn init(reader: *Io.Reader, buffer: []u8) ReaderStream

src

pub fn source(self: *ReaderStream) ReadStream

src

module client

src

Shared HTTP client types and adapters.

Constants

pub const BodyError = common.BodyError

src

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

pub const BodyError = …

src
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

enum Protocol

src

The application protocol selected by the transport.

Fields

h1 h1
h2 h2

enum Scheme

src

Fields

http http
https https

Functions

pub fn token(self: Scheme) []const u8

src

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

protocol: Protocol
status: Status
headers: Headers

Client-owned snapshot, valid until Body.close or the next request.

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

ptr: *anyopaque
vtable: *const VTable

Functions

pub fn step(self: BodyStep) BodyStepError!BodyResult

src

pub fn resetCode(self: BodyStep) ?u32

src

pub fn localCause(self: BodyStep) ?Local

src

pub fn close(self: BodyStep) void

src

Types

struct VTable

src

Fields

step: *const fn (ptr: *anyopaque) BodyStepError!BodyResult
resetCode: *const fn (ptr: *anyopaque) ?u32
localCause: *const fn (ptr: *anyopaque) ?Local
close: *const fn (ptr: *anyopaque) void

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

ptr: *anyopaque
vtable: *const VTable

Functions

pub fn read(self: Body) BodyError!BodyResult

src

pub fn resetCode(self: Body) ?u32

src

pub fn localCause(self: Body) ?Local

src

pub fn close(self: Body) void

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 Response

src

Fields

head: ResponseHead
body: Body

struct GoAway

src

A connection-level GOAWAY. Streams at or below last_stream_id remain valid; streams above it were not processed.

Fields

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

pub const PushError = …

src
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.

enum State

src

Retry state for the request being written.

Fields

staged staged

No byte of this request has left the queue.

in_flight in_flight

Some byte has been accepted; END_HEADERS has not completed.

committed committed

The frame carrying END_HEADERS was fully accepted.

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

pub fn init(storage: []u8, meta: []Meta) Queue

src

pub fn count(self: *const Queue) usize

src

pub fn isEmpty(self: *const Queue) bool

src

pub fn isPartial(self: *const Queue) bool

src

True when a frame is partially written. Nothing may be appended.

pub fn currentState(self: *const Queue) State

src

pub fn unsent(self: *const Queue) ?[]const u8

src

Bytes of the head frame still to be written, or null when empty.

pub fn push(self: *Queue, frame: Frame) PushError!void

src

pub fn accepted(self: *Queue, n: usize) void

src

Report bytes the transport accepted. Never more than unsent().?.len.

pub fn reset(self: *Queue) void

src

Start a new request. Requires an empty queue.

Types

struct Meta

src

Fields

start: usize
end: usize
stream_id: u31
class: Class
end_headers: bool
end_stream: bool

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

enum Protocol

src
pub const Protocol = common.Protocol

The application protocol selected by the transport.

Fields

h1 h1
h2 h2

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 Response

src
pub const Response = common.Response

Fields

head: ResponseHead
body: Body

struct Config

src

Fields

protocol: Protocol = .h1
common: common.Config

struct Client

src

Fields

inner: Inner

Functions

pub fn init( self: *Client, reader: *Io.Reader, writer: *Io.Writer, config: Config, ) !void

src

pub fn deinit(self: *Client) void

src

pub fn request(self: *Client, value: Request) !Response

src

pub fn protocol(self: *const Client) Protocol

src

enum Protocol

src
pub const Protocol = common.Protocol

The application protocol selected by the transport.

Fields

h1 h1
h2 h2

enum Scheme

src
pub const Scheme = common.Scheme

Fields

http http
https https

Functions

pub fn token(self: Scheme) []const u8

src

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 ResponseHead

src
pub const ResponseHead = common.ResponseHead

Fields

protocol: Protocol
status: Status
headers: Headers

Client-owned snapshot, valid until Body.close or the next request.

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

ptr: *anyopaque
vtable: *const VTable

Functions

pub fn read(self: Body) BodyError!BodyResult

src

pub fn resetCode(self: Body) ?u32

src

pub fn localCause(self: Body) ?Local

src

pub fn close(self: Body) void

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 Response

src
pub const Response = common.Response

Fields

head: ResponseHead
body: Body

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

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

ptr: *anyopaque
vtable: *const VTable

Functions

pub fn step(self: BodyStep) BodyStepError!BodyResult

src

pub fn resetCode(self: BodyStep) ?u32

src

pub fn localCause(self: BodyStep) ?Local

src

pub fn close(self: BodyStep) void

src

Types

struct VTable

src

Fields

step: *const fn (ptr: *anyopaque) BodyStepError!BodyResult
resetCode: *const fn (ptr: *anyopaque) ?u32
localCause: *const fn (ptr: *anyopaque) ?Local
close: *const fn (ptr: *anyopaque) void

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

pub const Error = …

src
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,
}

pub const HostError = error{DuplicateHost}

src

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.

pub fn host(headers: Headers) HostError!?[]const u8

src

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

major: u8
minor: u8

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

pub const Error = …

src
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.

struct Step

src

Fields

consumed: u32

Bytes consumed from the front of the input, even when need_more.

event: Event

struct Decoder

src

A resumable chunked body decoder. The state is caller-owned and survives reads; the trailer storage is caller-owned and must outlive the decoder.

Fields

state: State
remaining: u64
trailer_storage: []Header
trailer_state: h1.SearchState

Functions

pub fn init(trailer_storage: []Header) Decoder

src

pub fn isDone(self: *const Decoder) bool

src

pub fn next(self: *Decoder, buf: []const u8) Error!Step

src

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

pub fn init(config: Config) ServerConn

src

pub fn start(self: *ServerConn, now: Io.Timestamp) void

src

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.

pub fn received(self: *ServerConn, n: usize, now: Io.Timestamp) Event

src

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.

pub fn advance(self: *ServerConn, now: Io.Timestamp) Event

src

Parse bytes already in the request buffer. Call after .keep_alive before the next read, so a pipelined request is handled without a syscall.

pub fn eof(self: *ServerConn, now: Io.Timestamp) Event

src

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.

pub fn tick(self: *ServerConn, now: Io.Timestamp) Tick

src

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.

pub fn sent(self: *ServerConn, n: usize, now: Io.Timestamp) Done

src

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.

pub fn deadline(self: *const ServerConn) ?Io.Timestamp

src

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

headers: []Header
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 Rejection

src

Fields

status: Status
reason: RejectReason

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.

struct Slices

src

Fields

head: []const u8
body: []const u8

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.

union Tick

src
pub const Tick = union(enum)

The action for an expired poll deadline.

Fields

none none

No deadline has expired.

reject: Rejection

The machine queued this 408. Send it, then close.

close close

Close now. Part of a response is on the wire, or a drain deadline expired.

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

pub const Error = …

src
value
h1.Error || h1.framing.Error || h1.chunked.Error || error{
    HeadTooLarge,
    BodyTooLarge,
}

Functions

pub fn init(config: Config) ClientConn

src

pub fn start(self: *ClientConn, method: ?Method) void

src

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.

pub fn received(self: *ClientConn, n: usize) Error!Event

src

Account for a non-empty read into recvBuffer. A zero-byte read is not data: call eof when the transport reports a clean close.

pub fn next(self: *ClientConn) Error!Event

src

Process bytes already in the receive buffer. Call after Event.response to get body data, or after Event.body to get the next slice.

pub fn eof(self: *ClientConn) Error!Event

src

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.

struct Response

src

Fields

head: h1.ResponseHead
framing: h1.framing.ResponseBody

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

pub fn fetch( io: Io, stream: Io.net.Stream, request: Request, config: Config, ) !Response

src

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 Request

src

Fields

method: Method
target: []const u8
headers: []const Header = &.{}
body: []const u8 = ""

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.

struct Response

src

Fields

head: h1.ResponseHead
framing: h1.framing.ResponseBody
body: []u8

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

pub const BodyError = common.BodyError

src

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 ResponseHead

src
pub const ResponseHead = common.ResponseHead

Fields

protocol: Protocol
status: Status
headers: Headers

Client-owned snapshot, valid until Body.close or the next request.

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

ptr: *anyopaque
vtable: *const VTable

Functions

pub fn read(self: Body) BodyError!BodyResult

src

pub fn resetCode(self: Body) ?u32

src

pub fn localCause(self: Body) ?Local

src

pub fn close(self: Body) void

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 Response

src
pub const Response = common.Response

Fields

head: ResponseHead
body: Body

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_headers: usize
active: bool
body_eof: bool
body_local: ?Local
dirty: bool
pending: []const u8

Functions

pub fn init( self: *Client, reader: *Io.Reader, writer: *Io.Writer, config: Config, ) !void

src

pub fn deinit(self: *Client) void

src

pub fn request(self: *Client, value: Request) !Response

src

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

pub const Handler = @import("common.zig").Handler

src

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

pub const Handler = @import("common.zig").Handler

src

A type-erased HTTP handler over this engine's exchange.

Functions

pub fn run( io: Io, listener: *Io.net.Server, handler: Handler, config: Config, ) !void

src

Accept connections until an unrecoverable accept error.

pub fn serve( io: Io, stream: Io.net.Stream, handler: Handler, config: Config, ) !void

src

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.

pub fn wake(self: *Exchange) void

src

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

id: usize

The engine slot that rejected input.

status: Status

The status chosen by the parser or connection machine.

reason: RejectReason

The precise rejection category.

arena: Allocator

Storage for a replacement body. It lives until the response completes.

struct PolicyResponse

src
pub const PolicyResponse = @import("common.zig").PolicyResponse

A replacement for the default status-and-empty-body rejection.

Fields

options: ResponseOptions
body: []const u8

struct Config

src

Buffers and policy for one connection. The request buffer holds the head and body.

Fields

allocator: mem.Allocator
headers: []Header
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

pub const Handler = readiness.Handler

src

A type-erased HTTP handler over this engine's exchange.

pub const Server = readiness.Server(Wait)

src

Types

struct Config

src
pub const Config = readiness.Config

Fields

allocator: Allocator
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.

pub fn wake(self: *Exchange) void

src

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

id: usize

The engine slot that rejected input.

status: Status

The status chosen by the parser or connection machine.

reason: RejectReason

The precise rejection category.

arena: Allocator

Storage for a replacement body. It lives until the response completes.

struct PolicyResponse

src
pub const PolicyResponse = readiness.PolicyResponse

A replacement for the default status-and-empty-body rejection.

Fields

options: ResponseOptions
body: []const u8

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

pub const Handler = readiness.Handler

src

A type-erased HTTP handler over this engine's exchange.

pub const Server = readiness.Server(Wait)

src

Types

struct Config

src
pub const Config = readiness.Config

Fields

allocator: Allocator
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.

pub fn wake(self: *Exchange) void

src

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

id: usize

The engine slot that rejected input.

status: Status

The status chosen by the parser or connection machine.

reason: RejectReason

The precise rejection category.

arena: Allocator

Storage for a replacement body. It lives until the response completes.

struct PolicyResponse

src
pub const PolicyResponse = readiness.PolicyResponse

A replacement for the default status-and-empty-body rejection.

Fields

options: ResponseOptions
body: []const u8

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

pub const Handler = readiness.Handler

src

A type-erased HTTP handler over this engine's exchange.

pub const Server = readiness.Server(Wait)

src

Types

struct Config

src
pub const Config = readiness.Config

Fields

allocator: Allocator
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.

pub fn wake(self: *Exchange) void

src

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

id: usize

The engine slot that rejected input.

status: Status

The status chosen by the parser or connection machine.

reason: RejectReason

The precise rejection category.

arena: Allocator

Storage for a replacement body. It lives until the response completes.

struct PolicyResponse

src
pub const PolicyResponse = readiness.PolicyResponse

A replacement for the default status-and-empty-body rejection.

Fields

options: ResponseOptions
body: []const u8

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.

pub fn wake(self: *Exchange) void

src

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

pub const Error = …

src
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

pub fn request(version: Version, headers: Headers) Error!RequestBody

src

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

none none

No body. A request with no framing fields.

length: u64

A body of exactly this many octets.

chunked chunked

The chunked transfer coding.

union ResponseBody

src
pub const ResponseBody = union(enum)

The body framing of a response.

Fields

none none

No body, because the request method or status forbids one.

length: u64

A body of exactly this many octets.

chunked chunked

The chunked transfer coding.

close close

The body ends when the connection closes.

tunnel tunnel

A successful CONNECT response: the connection becomes a tunnel.

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

pub const Error = …

src
value
Io.Writer.Error || error{
    InvalidMethod,
    InvalidTarget,
    InvalidFieldName,
    InvalidFieldValue,
    InvalidReason,
    EmptyChunk,
}

Functions

pub fn init(writer: *Io.Writer) Writer

src

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.

pub fn field(self: Writer, name: []const u8, value: []const u8) Error!void

src

Write one field line.

pub fn assumeField(self: Writer, name: []const u8, value: []const u8) Error!void

src

field without validation.

pub fn endFields(self: Writer) Error!void

src

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.

pub fn body(self: Writer, bytes: []const u8) Error!void

src

Write body bytes. The body is opaque; no validation applies.

pub fn chunked(self: Writer) Error!void

src

Write the Transfer-Encoding: chunked field line.

pub fn chunk(self: Writer, bytes: []const u8) Error!void

src

Write one non-final chunk.

pub fn lastChunk(self: Writer, trailers: []const Header) Error!void

src

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

pub const init: SearchState = .{ .searched = 0 }

src

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

head: RequestHead
consumed: u32

Bytes consumed, including any leading empty lines.

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

head: ResponseHead
consumed: u32

enum TargetForm

src

The request-target forms RFC 9112 §3.2 defines.

Fields

origin origin

absolute-path [ "?" query ], for an origin server.

absolute absolute

An absolute URI, for a proxy.

authority authority

uri-host ":" port, for CONNECT.

asterisk asterisk

*, for OPTIONS.

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

headers: Headers
consumed: u32

Bytes consumed, including the terminating empty line.

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

enum Protocol

src
pub const Protocol = shared.Protocol

The application protocol selected by the transport.

Fields

h1 h1
h2 h2

enum Scheme

src
pub const Scheme = shared.Scheme

Fields

http http
https https

Functions

pub fn token(self: Scheme) []const u8

src

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 ResponseHead

src
pub const ResponseHead = shared.ResponseHead

Fields

protocol: Protocol
status: Status
headers: Headers

Client-owned snapshot, valid until Body.close or the next request.

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

ptr: *anyopaque
vtable: *const VTable

Functions

pub fn read(self: Body) BodyError!BodyResult

src

pub fn resetCode(self: Body) ?u32

src

pub fn localCause(self: Body) ?Local

src

pub fn close(self: Body) void

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 Response

src
pub const Response = shared.Response

Fields

head: ResponseHead
body: Body

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.

enum RequestState

src

Fields

idle idle
staged staged
in_flight in_flight
committed committed

struct Client

src

Fields

allocator: Allocator
reader: *Io.Reader
writer: *Io.Writer
arena: std.heap.ArenaAllocator
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

pub fn init( self: *Client, reader: *Io.Reader, writer: *Io.Writer, config: Config, ) !void

src

Initialize in its final location. The caller owns the reader, writer, header storage, and allocator.

pub fn deinit(self: *Client) void

src

pub fn commitState(self: *const Client) RequestState

src

pub fn isConnectionPoisoned(self: *const Client) bool

src

pub fn goAway(self: *const Client) ?shared.GoAway

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.

pub fn streamId(self: *const Client) u31

src

The last request's stream id. Preserved after Body.close so a caller can compare it with goAway().last_stream_id.

pub fn request(self: *Client, value: Request) !Response

src

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

pub const Handler = blocking.Handler

src

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

pub const Handler = common.Handler

src

A type-erased HTTP/2 handler over this engine's exchange.

Functions

pub fn run( io: Io, listener: *Io.net.Server, handler: Handler, config: Config, ) !void

src

Accept connections until an unrecoverable accept error.

pub fn serve( io: Io, stream: Io.net.Stream, handler: Handler, config: Config, ) !void

src

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 Response

src

Fields

options: ResponseOptions
body: []const u8

struct Request

src
pub const Request = common.Request

One complete request, copied into the exchange arena.

Fields

head: head.RequestHead
body: []const u8

The whole request body, up to Config.body_bytes.

trailers: Headers

The trailer section, empty when the request had none.

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

pub const Config = readiness.Config(transport.Transport)

src

pub const Handler = readiness.Handler

src

A type-erased HTTP/2 handler over this engine's exchange.

pub const Server = readiness.Server(Wait, transport.Transport)

src

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

struct Response

src

Fields

options: ResponseOptions
body: []const u8

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

pub const Config = readiness.Config(transport.Transport)

src

pub const Handler = readiness.Handler

src

A type-erased HTTP/2 handler over this engine's exchange.

pub const Server = readiness.Server(Wait, transport.Transport)

src

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

struct Response

src

Fields

options: ResponseOptions
body: []const u8

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

pub const Config = readiness.Config(transport.Transport)

src

pub const Handler = readiness.Handler

src

A type-erased HTTP/2 handler over this engine's exchange.

pub const Server = readiness.Server(Wait, transport.Transport)

src

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

struct Response

src

Fields

options: ResponseOptions
body: []const u8

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

pub const Handler = common.Handler

src

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 Response

src

Fields

options: ResponseOptions
body: []const u8

struct Request

src
pub const Request = common.Request

One complete request, copied into the exchange arena.

Fields

head: head.RequestHead
body: []const u8

The whole request body, up to Config.body_bytes.

trailers: Headers

The trailer section, empty when the request had none.

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
head_timeout: Io.Duration
body_timeout: Io.Duration
head_deadline: ?Io.Timestamp

Functions

pub fn init(options: Options) !Connection

src

pub fn deinit(self: *Connection) void

src

pub fn receive(self: *Connection, bytes: []const u8) usize

src

pub fn send(self: *Connection, out: []u8) usize

src

pub fn isClosed(self: *const Connection) bool

src

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.

pub fn start(self: *Connection, now: Io.Timestamp) void

src

Arm the connection-level head deadline. A zero timeout leaves it unset.

pub fn deadline(self: *const Connection) ?Io.Timestamp

src

The earliest head or body deadline, if any.

pub fn drive(self: *Connection, now: Io.Timestamp) !bool

src

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
head_timeout: Io.Duration
body_timeout: Io.Duration
head_deadline: ?Io.Timestamp

Functions

pub fn init(options: Options) !Connection

src

pub fn deinit(self: *Connection) void

src

pub fn receive(self: *Connection, bytes: []const u8) usize

src

pub fn send(self: *Connection, out: []u8) usize

src

pub fn isClosed(self: *const Connection) bool

src

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.

pub fn start(self: *Connection, now: Io.Timestamp) void

src

Arm the connection-level head deadline. A zero timeout leaves it unset.

pub fn deadline(self: *const Connection) ?Io.Timestamp

src

The earliest head or body deadline, if any.

pub fn drive(self: *Connection, now: Io.Timestamp) !bool

src

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

pub const Handler = common.Handler

src

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 Response

src

Fields

options: ResponseOptions
body: []const u8

type fn Config

src
pub fn Config(comptime Transport: type) type

Fields

allocator: Allocator
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

pub const Config = ConfigType

src

Functions

pub fn init( io: Io, bind_address: Io.net.IpAddress, handler: Handler, config: ConfigType, ) !Self

src

pub fn deinit(self: *Self) void

src

pub fn address(self: *const Self) Io.net.IpAddress

src

pub fn requestStop(self: *Self) void

src

pub fn run(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

pub const Interest = std.enums.EnumSet(enum { read, write })

src

What the waiter should watch for.

pub const Flags = std.enums.EnumSet(enum { readable, writable, failed, hup })

src

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

union Owner

src
pub const Owner = union(enum)

Fields

stop stop
listener listener
wake wake
slot: usize

struct Ready

src

One ready descriptor, translated out of the backend's native event form.

Fields

owner: Owner
flags: Flags

enum protocol

src
pub const protocol = @import("../../engine/common.zig").Protocol

The negotiated HTTP protocol a request arrived on.

Fields

h1 h1
h2 h2

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

pub fn init(allocator: Allocator, max_entries: usize) !Wait

src

pub fn deinit(self: *Wait) void

src

pub fn set(self: *Wait, owner: types.Owner, fd: posix.fd_t, interest: types.Interest) !void

src

pub fn remove(self: *Wait, owner: types.Owner) void

src

pub fn wait(self: *Wait, timeout_ms: i32) ![]types.Ready

src

Types

module Platform

src

Linux transport edge for the readiness engines.

Constants

pub const fd_t = linux.fd_t

src

pub const AcceptError = …

src
value
error{
    ConnectionAborted,
    ListenerFailed,
    SystemResources,
}

pub const SendError = error{ WouldBlock, BrokenPipe }

src

Functions

pub fn accept(listener_fd: fd_t) AcceptError!?fd_t

src

pub fn read(fd: fd_t, buf: []u8) !usize

src

pub fn send(fd: fd_t, head: []const u8, body: []const u8) SendError!usize

src

pub fn close(fd: fd_t) void

src

pub fn openListener(address: Io.net.IpAddress, tcp_defer_accept: bool) !Listener

src

Types

struct Listener

src

Fields

fd: fd_t
address: Io.net.IpAddress

Functions

pub fn deinit(self: *Listener) void

src

struct Stop

src

Fields

read_fd: fd_t
write_fd: fd_t

Functions

pub fn init() !Stop

src

pub fn deinit(self: Stop) void

src

pub fn wake(self: Stop) void

src

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

pub fn init(allocator: Allocator, max_entries: usize) !Wait

src

pub fn deinit(self: *Wait) void

src

pub fn set(self: *Wait, owner: types.Owner, fd: linux.fd_t, interest: types.Interest) !void

src

pub fn remove(self: *Wait, owner: types.Owner) void

src

pub fn wait(self: *Wait, timeout_ms: i32) ![]types.Ready

src

Types

module Platform

src

Linux transport edge for the readiness engines.

Constants

pub const fd_t = linux.fd_t

src

pub const AcceptError = …

src
value
error{
    ConnectionAborted,
    ListenerFailed,
    SystemResources,
}

pub const SendError = error{ WouldBlock, BrokenPipe }

src

Functions

pub fn accept(listener_fd: fd_t) AcceptError!?fd_t

src

pub fn read(fd: fd_t, buf: []u8) !usize

src

pub fn send(fd: fd_t, head: []const u8, body: []const u8) SendError!usize

src

pub fn close(fd: fd_t) void

src

pub fn openListener(address: Io.net.IpAddress, tcp_defer_accept: bool) !Listener

src

Types

struct Listener

src

Fields

fd: fd_t
address: Io.net.IpAddress

Functions

pub fn deinit(self: *Listener) void

src

struct Stop

src

Fields

read_fd: fd_t
write_fd: fd_t

Functions

pub fn init() !Stop

src

pub fn deinit(self: Stop) void

src

pub fn wake(self: Stop) void

src

module wait_kqueue

src

kqueue registration and wait for the Darwin readiness engine.

Types

struct Wait

src

Fields

allocator: Allocator
entries: []Entry
kq: c.fd_t
events: []c.Kevent
ready: []types.Ready

Functions

pub fn init(allocator: Allocator, max_entries: usize) !Wait

src

pub fn deinit(self: *Wait) void

src

pub fn set(self: *Wait, owner: types.Owner, fd: c.fd_t, interest: types.Interest) !void

src

pub fn remove(self: *Wait, owner: types.Owner) void

src

pub fn wait(self: *Wait, timeout_ms: i32) ![]types.Ready

src

Types

module Platform

src

Darwin transport edge for the kqueue readiness engine.

Constants

pub const fd_t = c.fd_t

src

pub const AcceptError = …

src
value
error{
    ConnectionAborted,
    ListenerFailed,
    SystemResources,
}

pub const SendError = error{ WouldBlock, BrokenPipe }

src

Functions

pub fn accept(listener_fd: fd_t) AcceptError!?fd_t

src

pub fn read(fd: fd_t, buf: []u8) !usize

src

pub fn send(fd: fd_t, head: []const u8, body: []const u8) SendError!usize

src

pub fn close(fd: fd_t) void

src

pub fn openListener(address: Io.net.IpAddress, tcp_defer_accept: bool) !Listener

src

Types

struct Listener

src

Fields

fd: fd_t
address: Io.net.IpAddress

Functions

pub fn deinit(self: *Listener) void

src

struct Stop

src

Fields

read_fd: fd_t
write_fd: fd_t

Functions

pub fn init() !Stop

src

pub fn deinit(self: Stop) void

src

pub fn wake(self: Stop) void

src

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 Response

src

Fields

options: ResponseOptions
body: []const u8

struct Request

src
pub const Request = blocking.Request

One complete request, copied into the exchange arena.

Fields

head: head.RequestHead
body: []const u8

The whole request body, up to Config.body_bytes.

trailers: Headers

The trailer section, empty when the request had none.

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.

pub const Error = …

src
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

pub fn init(initial: u32) Window

src

pub fn consume(self: *Window, count: u32) Error!void

src

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.

pub fn increase(self: *Window, increment: u31) Error!void

src

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

pub const preface = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"

src

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

pub const Error = …

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

pub fn parse(buf: []const u8, max_frame_size: u32) Error!Parsed

src

Parse one frame from the front of buf, rejecting a payload larger than max_frame_size before waiting for it.

pub fn body(header: Header, payload: []const u8) Error!Body

src

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.

pub const ack: u8 = 0x1

src

SETTINGS, PING.

pub const end_headers: u8 = 0x4

src

HEADERS, PUSH_PROMISE, CONTINUATION.

pub const padded: u8 = 0x8

src

DATA, HEADERS, PUSH_PROMISE.

pub const priority: u8 = 0x20

src

HEADERS.

struct Header

src

Fields

length: u24

Payload length, excluding the 9-byte header.

type: FrameType
flags: u8
stream_id: u31

31 bits. The reserved high bit is ignored on receipt (§4.1).

struct Parsed

src

Fields

header: Header
payload: []const u8

The payload, borrowed from the input.

consumed: u32

9 plus header.length.

struct Data

src

Fields

data: []const u8

Payload with the pad length and padding removed.

struct Priority

src

Fields

exclusive: bool
dependency: u31
weight: u8

The wire value; the effective weight is this plus one.

struct Headers

src

Fields

priority: ?Priority

Present when the PRIORITY flag is set. Priority signals are deprecated (RFC 9113 §5.3.2), but the fields are still parsed.

block: []const u8

The field block fragment, padding removed.

struct RstStream

src

Fields

error_code: ErrorCode

struct Setting

src

Fields

id: SettingId
value: u32

struct Settings

src

Fields

ack: bool
payload: []const u8

The 6-byte entries, in wire order. Use iterator.

Functions

pub fn iterator(self: Settings) SettingsIterator

src

struct SettingsIterator

src

Fields

payload: []const u8
pos: usize

Functions

pub fn next(self: *SettingsIterator) ?Setting

src

struct PushPromise

src

Fields

block: []const u8

The field block fragment, padding removed.

struct Ping

src

Fields

ack: bool
payload: []const u8

Exactly 8 octets.

struct GoAway

src

Fields

error_code: ErrorCode
debug: []const u8

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.

struct Continuation

src

Fields

block: []const u8

struct Unknown

src

Fields

type: FrameType
payload: []const u8

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

buf: []u8
pos: usize
start: usize

Constants

pub const WriterError = error{NoSpace}

src

Functions

pub fn init(buf: []u8) Writer

src

pub fn begin( self: *Writer, frame_type: FrameType, frame_flags: u8, stream_id: u31, ) WriterError!void

src

pub fn bytes(self: *Writer, data: []const u8) WriterError!void

src

pub fn byte(self: *Writer, value: u8) WriterError!void

src

pub fn int(self: *Writer, comptime T: type, value: T) WriterError!void

src

pub fn end(self: *Writer) void

src

pub fn written(self: *const Writer) []const u8

src

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

pub const Error = …

src
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

pub fn request(fields: Headers) Error!RequestHead

src

pub fn trailers(fields: Headers) Error!Headers

src

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.

pub fn response(fields: Headers) Error!ResponseHead

src

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

status: Status
headers: Headers

The regular fields, in wire order. :status is not included.

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

pub const Error = …

src
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 init(buffer: []u8) Assembler

src

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

pub const Error = …

src
value
error{
    ConnectionClosed,
    WrongRole,
    UnknownStream,
    StreamClosed,
    TooManyStreams,
    StreamIdsExhausted,
    HeaderBlockTooLarge,
    InvalidHead,
    WouldBlock,
    FlowControlError,
    InvalidMaxFrameSize,
    InvalidInitialWindowSize,
    InvalidHeaderTableSize,
    InvalidHeaderListSize,
    InvalidHeaderBlockSize,
    InvalidOutputSize,
    InvalidSetting,
    SettingsTooLarge,
    BufferTooSmall,
    TooLarge,
} || Allocator.Error

Types

enum Origin

src

Fields

local local
remote remote

struct RequestEvent

src

Fields

stream_id: u31
head: head.RequestHead
end_stream: bool

struct ResponseEvent

src

Fields

stream_id: u31
head: head.ResponseHead
end_stream: bool

struct DataEvent

src

Fields

stream_id: u31
bytes: []const u8

The DATA payload with padding removed. Borrowed until the next event.

flow_len: u32

The whole DATA payload, padding included, as flow control counts it.

end_stream: bool

struct TrailersEvent

src

Fields

stream_id: u31
headers: Headers
end_stream: bool

struct ResetEvent

src

Fields

stream_id: u31
code: frame.ErrorCode
origin: Origin

struct WindowUpdateEvent

src

Fields

stream_id: u31

Zero is the connection window.

available: i64

struct GoAwayEvent

src

Fields

code: frame.ErrorCode
debug: []const u8

struct ConnectionErrorEvent

src

Fields

code: frame.ErrorCode

struct HeaderRun

src

Absolute wire offsets of one request's complete HEADERS+CONTINUATION run.

Fields

stream_id: u31
start: u64
end: u64

union Event

src
pub const Event = union(enum)

Fields

request: RequestEvent
response: ResponseEvent
data: DataEvent
trailers: TrailersEvent
reset: ResetEvent
window_update: WindowUpdateEvent
goaway: GoAwayEvent
settings: settings.Values
connection_error: ConnectionErrorEvent

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)
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
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 init(allocator: Allocator, options: Options) Error!Session

src

pub fn deinit(self: *Session, allocator: Allocator) void

src

pub fn isClosed(self: *const Session) bool

src

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.

pub fn receive(self: *Session, bytes: []const u8) usize

src

Append transport bytes. Returns how many were consumed; zero means the input buffer is full and the caller must drain events first.

pub fn send(self: *Session, out: []u8) usize

src

Copy queued output into out. Never allocates.

pub fn sendFrame(self: *Session, out: []u8) Error!usize

src

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 nextEvent(self: *Session, allocator: Allocator) Error!?Event

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

pub fn release( self: *Session, allocator: Allocator, id: u31, count: u32, ) 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)
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
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 init(allocator: Allocator, options: Options) Error!Session

src

pub fn deinit(self: *Session, allocator: Allocator) void

src

pub fn isClosed(self: *const Session) bool

src

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.

pub fn receive(self: *Session, bytes: []const u8) usize

src

Append transport bytes. Returns how many were consumed; zero means the input buffer is full and the caller must drain events first.

pub fn send(self: *Session, out: []u8) usize

src

Copy queued output into out. Never allocates.

pub fn sendFrame(self: *Session, out: []u8) Error!usize

src

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 nextEvent(self: *Session, allocator: Allocator) Error!?Event

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

pub fn release( self: *Session, allocator: Allocator, id: u31, count: u32, ) 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

pub const Error = …

src
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

pub fn apply(values: *Values, local_role: Role, settings: frame.Settings) Error!void

src

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

enum Role

src

The endpoint's own role. The peer has the other role.

Fields

client client
server server

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

pub const Error = …

src
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 Role

src
pub const Role = settings.Role

The endpoint's own role. The peer has the other role.

Fields

client client
server server

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 Side

src

Fields

local local
remote remote

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

role: Role
local_next: u32

Functions

pub fn init(role: Role) Ids

src

pub fn nextLocal(self: *Ids) ?u31

src

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.

pub const Error = …

src
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

pub fn init(storage: TableStorage) Decoder

src

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.

pub fn decode(self: *Decoder, block: []const u8, out: []Header, out_bytes: []u8) Error!Headers

src

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

pub const Error = …

src
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 init(storage: TableStorage) Encoder

src

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.

pub fn encode(self: *Encoder, headers: []const Header, out: []u8) Error![]const u8

src

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

pub fn init(storage: TableStorage) DynamicTable

src

pub fn used(self: *const DynamicTable) usize

src

The bytes the live entries occupy, which is their accounting size less the fixed overhead.

pub fn isEmpty(self: *const DynamicTable) bool

src

pub fn get(self: *const DynamicTable, index: usize) ?Header

src

The entry at a dynamic index: 0 is the most recently inserted. Returns null past the oldest entry.

pub fn find(self: *const DynamicTable, header: Header) ?usize

src

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.

pub fn findName(self: *const DynamicTable, name: []const u8) ?usize

src

The position of the newest entry with this name, or null.

pub fn insert(self: *DynamicTable, name: []const u8, value: []const u8) void

src

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.

pub fn clear(self: *DynamicTable) void

src

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

pub const count = fields.len

src

The number of static entries.

pub const first_dynamic: u32 = count + 1

src

The first dynamic index: one past the last static entry.

Functions

pub fn get(index: u32) ?Header

src

pub fn find(header: Header) ?u32

src

The index of the first entry whose name and value both match, or null.

pub fn findName(name: []const u8) ?u32

src

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

pub fn init(storage: TableStorage) Decoder

src

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.

pub fn decode(self: *Decoder, block: []const u8, out: []Header, out_bytes: []u8) Error!Headers

src

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 init(storage: TableStorage) Encoder

src

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.

pub fn encode(self: *Encoder, headers: []const Header, out: []u8) Error![]const u8

src

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

pub fn init(storage: TableStorage) DynamicTable

src

pub fn used(self: *const DynamicTable) usize

src

The bytes the live entries occupy, which is their accounting size less the fixed overhead.

pub fn isEmpty(self: *const DynamicTable) bool

src

pub fn get(self: *const DynamicTable, index: usize) ?Header

src

The entry at a dynamic index: 0 is the most recently inserted. Returns null past the oldest entry.

pub fn find(self: *const DynamicTable, header: Header) ?usize

src

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.

pub fn findName(self: *const DynamicTable, name: []const u8) ?usize

src

The position of the newest entry with this name, or null.

pub fn insert(self: *DynamicTable, name: []const u8, value: []const u8) void

src

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.

pub fn clear(self: *DynamicTable) void

src

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).

pub const Error = …

src
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

pub fn slotCount(max_size: anytype) usize

src

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.

pub fn init(allocator: Allocator, max_size: usize) Error!TableStorage

src

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.

pub fn deinit(self: *TableStorage, allocator: Allocator) void

src

Release the blob. Call this only for a storage from init; a caller that used initBuffer owns and frees its own memory.

pub fn views(self: *const TableStorage) Views

src

The three slices, aligned and laid out inside memory.

Types

struct Slot

src

Where one entry lives in the byte region.

Fields

name_off: u32
name_len: u32
value_off: u32
value_len: u32

type fn Buffer

src
pub fn Buffer(comptime max_size: usize) type

The blob type initBuffer needs for a table that can grow to max_size bytes: var memory: TableStorage.Buffer(4096) = undefined;.

struct Views

src

Fields

bytes: []u8
slots: []Slot
hashes: []u32

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

pub const Error = …

src
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.

pub const table: [257]Code = …

src
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

pub fn decode(encoded: []const u8, out: []u8) Error!usize

src

Decode encoded into out, returning the number of bytes written.

pub fn encode(src: []const u8, out: []u8) EncodeError!usize

src

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

struct Code

src

Fields

bits: u32
len: u5

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

pub fn init(buf: []const u8) Cursor

src

A cursor at the start of buf. buf must be shorter than 4 GiB.

pub fn atEnd(self: *const Cursor) bool

src

pub fn remaining(self: *const Cursor) u32

src

The number of bytes not read yet.

pub fn has(self: *const Cursor, count: u32) bool

src

Whether at least count bytes are left.

pub fn peek(self: *const Cursor) ?u8

src

The next byte, without reading it.

pub fn at(self: *const Cursor, index: u32) u8

src

The byte at an absolute offset. The caller guarantees it is in bounds, as a scanner that looked ahead with has already did.

pub fn next(self: *Cursor) ?u8

src

Read one byte, or null at the end.

pub fn take(self: *Cursor, count: u32) ?[]const u8

src

Read count bytes, or null when fewer are left. Nothing moves on null.

pub fn skip(self: *Cursor, count: u32) bool

src

Advance count bytes. False, and nothing moves, when fewer are left.

pub fn slice(self: *const Cursor, start: u32, end: u32) []const u8

src

A slice by absolute offsets. The caller guarantees the range.

pub fn rest(self: *const Cursor) []const u8

src

Everything from pos to the end.

struct Header

src
pub const Header = @import("header.zig").Header

One field line, as the parser found it. Both slices borrow the message buffer and are valid only while it is unchanged. The header owns nothing.

Fields

name: []const u8
value: []const u8

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

pub fn len(self: Headers) usize

src

The number of field lines in the section.

pub fn get(self: Headers, name: []const u8) ?[]const u8

src

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.

pub fn values(self: Headers, name: []const u8) Values

src

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

pub fn token(self: Method) []const u8

src

The exact wire token. Methods are case-sensitive (RFC 9110 §9.1).

pub fn parse(wire: []const u8) ?Method

src

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.

pub fn safe(self: Method) bool

src

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).

pub fn cacheable(self: Method) bool

src

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

pub fn token(self: Method) []const u8

src

The exact wire token. Methods are case-sensitive (RFC 9110 §9.1).

pub fn parse(wire: []const u8) ?Method

src

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.

pub fn safe(self: Method) bool

src

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).

pub fn cacheable(self: Method) bool

src

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

pub const Error = …

src
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.

pub const max_bits: u4 = 8

src

The largest prefix this representation has: 8 bits, the whole octet.

Functions

pub fn decode(cursor: *Cursor, bits: u4) Error!u32

src

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.

pub fn encode(buf: []u8, value: u32, bits: u4) EncodeError![]u8

src

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
service_unavailable service_unavailable = 503
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

pub fn text(self: Status) [3]u8

src

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.

pub fn code(self: Status) u16

src

pub fn parse(value: u16) ?Status

src

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.

pub fn class(self: Status) ?Class

src

The class of the code, or null when the value is outside 100..599 (RFC 9110 §15).

pub fn phrase(self: Status) ?[]const u8

src

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
service_unavailable service_unavailable = 503
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

pub fn text(self: Status) [3]u8

src

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.

pub fn code(self: Status) u16

src

pub fn parse(value: u16) ?Status

src

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.

pub fn class(self: Status) ?Class

src

The class of the code, or null when the value is outside 100..599 (RFC 9110 §15).

pub fn phrase(self: Status) ?[]const u8

src

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

pub fn contains(field: []const u8, token: []const u8) bool

src

Whether field contains token as an element, ignoring case and OWS.

pub fn iterator(field: []const u8) Iterator

src

Types

struct Iterator

src

Fields

field: []const u8
pos: u32

Functions

pub fn init(field: []const u8) Iterator

src

pub fn next(self: *Iterator) ?[]const u8

src

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

pub const Error = …

src
value
error{
    Invalid,
    ChunkedDuplicate,
    ChunkedParameters,
}

Functions

pub fn summarize(headers: Headers) Error!Summary

src

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.

struct Iterator

src

A borrowed iterator over every coding, in field order. Empty list elements are skipped. It enforces the chunked rules that apply wherever the field appears: no parameters and no repetition.

Fields

headers: Headers
line: []const u8
pos: usize
present: bool
seen_chunked: bool

Functions

pub fn init(headers: Headers) Iterator

src

pub fn next(self: *Iterator) Error!?Coding

src

struct Values

src
pub const Values = @import("header.zig").Values

Fields

headers: Headers
name: []const u8
index: u32

Functions

pub fn init(headers: Headers, name: []const u8) Values

src

pub fn next(self: *Values) ?[]const u8

src

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

major: u8
minor: u8