Skip to main content

Module wire

Module wire 

Source
Expand description

HTTP/1.1 wire format — hand-rolled parsers and writers over plain byte buffers. No hyper, no allocations beyond the owned result. The server half is parse_request + serialize_response; the client half is its exact mirror, serialize_request + parse_response — same error taxonomy, same limits, same ParseError::Incomplete retry contract.

Scope (v1, deliberately bounded — documented so callers know the edges):

  • Methods: any token (GET/POST/… are not restricted).
  • Bodies: framed by Content-Length only. Transfer-Encoding: chunked is rejected (ParseError::UnsupportedTransferEncoding) rather than silently mishandled.
  • Header names are normalized to lowercase on parse so lookups are case-insensitive (HTTP header names are case-insensitive by RFC 7230).
  • Connection reuse: the SERVER speaks HTTP/1.1 persistent connections — request_wants_keep_alive is the one law both execution tiers apply (keep-alive unless the client’s Connection header lists close), and serialize_response_conn emits the matching Connection: header. serialize_response remains the one-shot (Connection: close) form, and the CLIENT half (serialize_request) still requests one-shot exchanges — a deliberate v1 client scope.
  • Sizes are bounded: MAX_BODY caps bodies (requests and responses alike), MAX_HEADERS caps the header block, so a malicious peer cannot exhaust memory.
  • Framing symmetry: both writers own the framing headers (an accurate Content-Length computed from the body, Connection: close, never Transfer-Encoding), so neither parser needs read-to-end-of-input framing — a missing Content-Length always means an empty body, and torn input is always ParseError::Incomplete, never a guess at EOF.

serialize_response always emits an accurate Content-Length (computed from the body) and Connection: close, and omits Date / other volatile headers, so a response is a pure function of the handler’s output — which is what lets the cross-tier differential assert byte-for-byte equality. serialize_request obeys the same law on the client side.

Structs§

HttpRequest
A parsed HTTP request. path and query values are percent-decoded; header keys are lowercased. body is the raw bytes (a LOGOS Request.body: Text is derived from this at the interpreter/AOT bridge).
HttpResponse
A response to serialize. status is the numeric code; headers are written verbatim (except Content-Length/Connection/Transfer-Encoding, which the writer owns); body is raw bytes.

Enums§

ParseError
Why a byte buffer could not be parsed into a complete request.

Constants§

MAX_BODY
Largest request body accepted, in bytes. Requests declaring a larger Content-Length are rejected with ParseError::BodyTooLarge.
MAX_HEADERS
Largest header block (request line + headers, up to the blank line) accepted.

Functions§

parse_request
Parse a complete HTTP/1.1 request from bytes.
parse_request_consuming
parse_request plus the number of bytes the request occupied — the keep-alive keystone: a persistent connection drains exactly consumed bytes and keeps the remainder (a pipelined next request) buffered.
parse_response
Parse a complete HTTP/1.1 response from bytes — the client-side mirror of parse_request: same ParseError::Incomplete retry contract, same MAX_BODY/MAX_HEADERS caps, same lowercased header keys, same Transfer-Encoding rejection.
request_wants_keep_alive
Whether the server should keep the connection open after answering req — THE ONE keep-alive law both execution tiers (the interpreter’s serve loop and the AOT serve_on) apply, so their Connection: responses can never drift: HTTP/1.1 connections persist unless the client’s Connection header token list contains close (case-insensitive).
response_header_allowed
Whether a handler-supplied response header may pass to the wire — THE ONE law both execution tiers (the interpreter bridge and AOT-emitted code) apply before merging a LOGOS Response.headers map, so they can never drift: the name must be non-empty, must not be writer-owned framing or hop-by-hop (connection, content-length, transfer-encoding, keep-alive, upgrade), must not be content-type (the Response.contentType field is authoritative), and neither name nor value may contain CR, LF, or NUL (response splitting). The typed surface (withHeader in http.lg) rejects these with an Err up front; this filter is the defense-in-depth for maps poked directly.
safe_content_type
The Content-Type actually emitted for a handler’s contentType field: the field verbatim when it is wire-safe, else the text/plain default — so a CR/LF smuggled into contentType can never split the response. Both execution tiers route the field through this, keeping them byte-identical.
serialize_request
Serialize a request to bytes: request line (method, percent-encoded path and query, HTTP/1.1), the request’s headers (minus the writer-owned framing ones), an accurate Content-Length, Connection: close, the blank line, then the body — the exact client-side mirror of serialize_response.
serialize_response
Serialize a response to bytes: status line, the response’s headers (minus the writer-owned ones), an accurate Content-Length, Connection: close, the blank line, then the body — the one-shot form of serialize_response_conn.
serialize_response_conn
Serialize a response with an explicit connection disposition: the writer owns the Connection: header (keep-alive to hold the connection open for the next request, close to end it) exactly as it owns Content-Length — a handler can never desync framing or force persistence.