Silk

HTTP head parsing and serialization

Import the incremental parsers, borrowed heads, limits, and memory serializers from silk.http_head. The module parses exactly one strict HTTP/1.0 or HTTP/1.1 request or response head. It stops at the terminating empty line; body bytes, a pipelined message, and all transport state remain with the caller.

import silk.allocator { Allocator, OutOfMemoryError }
import silk.effect { Effect }
import silk.http_head { Limits, ParseError, Progress, ProgressState, RequestParser }
import silk.http_headers { Limits as ValueLimits }
import silk.result { Result }

effect fn parse() -> i32 ! OutOfMemoryError ? &mut Allocator {
  let limits = Limits {
    maxHeadBytes: 4096,
    maxStartLineBytes: 1024,
    maxFieldLineBytes: 1024,
    maxOwnedBytes: 5120,
    values: ValueLimits {
      maxMethodBytes: 32,
      maxTargetBytes: 1024,
      maxNameBytes: 128,
      maxValueBytes: 1024,
      maxFields: 32,
      maxFieldBytes: 3072,
      maxOwnedBytes: 4096,
    },
  }
  let made = run RequestParser.make(limits)
  let mut parser = match move made {
    Result<RequestParser, ParseError>.Failure {error} => { return 1 }
    Result<RequestParser, ParseError>.Success {value} => move value
  }
  let progress = RequestParser.feed(
    &mut parser,
    b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\nbody",
    false,
  )
  return match move progress {
    Result<Progress, ParseError>.Failure {error} => 2
    Result<Progress, ParseError>.Success {value} => {
      if value.state == ProgressState.Complete && value.consumed == 37 { return 0 }
      return 3
    }
  }
}

effect fn recover(error: OutOfMemoryError) -> i32 { return 4 }

pub fn main() -> i32 {
  let mut allocator = Allocator.systemAllocatorProvider()
  return run Effect.catchAll(
    parse() |> Effect.provideMut<Allocator>(&mut allocator),
    recover,
  )
}

RequestParser.make and ResponseParser.make validate checked capacity arithmetic and acquire all parser storage before returning. maxHeadBytes budgets stored syntax, maxStartLineBytes and maxFieldLineBytes bound individual lines including CRLF, the nested ValueLimits apply the shared HTTP method, target, field, and owned-copy limits, and maxOwnedBytes covers the head backing plus 32 bytes of offset metadata per configured field. Zero is always a real bound. Feeding, head access, reset, iteration, sizing, and serialization never grow or replace that storage.

Exact incremental progress

feed(input, final) returns a Progress with a count relative to that call and a state of NeedInput or Complete. A successful nonempty NeedInput consumes the complete supplied slice. Complete consumes through the final LF and no farther, so the caller retains the exact body or next-message suffix. Empty nonfinal input consumes zero. Final input before completion returns Truncated.

A syntax or limit failure poisons the parser and preserves its original ParseError for failure. Further feed calls return InvalidState and consume zero. reset is available after completion or failure and reuses the original allocation. A completed RequestHead or ResponseHead borrows the parser, including every HeaderIterator value; Silk therefore rejects reset or release while such a view is live. Use RequestHead.copy or ResponseHead.copy when the values must outlive the parser.

parseRequest and parseResponse are whole-slice conveniences over the same incremental core. They return the completed parser together with exact Progress, so a suffix is still never hidden.

Selected strict grammar

Request lines require exactly method SP request-target SP HTTP/1.0|HTTP/1.1 CRLF. Response lines require exactly HTTP/1.0|HTTP/1.1 SP 3DIGIT SP reason CRLF, including the second space for an empty reason. Status codes are limited to 100–599. Field names must meet the shared HTTP token contract and must touch : directly. Values preserve interior bytes and trim only surrounding SP and HTAB. Original field case, order, duplicates, empty values, and permitted response obs-text are preserved.

The parser rejects leading blank lines, bare LF, CR not followed by LF, obs-fold, whitespace before a colon, invalid controls, HTTP/0.9, and ICY. HTTP/1.1 requests require exactly one syntactically valid Host field. For an absolute-form target, the target authority remains effective even when Host names a different authority.

ParseError identifies the reason, syntactic component, absolute head offset, exact valid prefix consumed from the failing call, and a field ordinal when applicable. LimitExceeded additionally reports the exact limit kind, allowed count, and attempted count. Allocation failure remains the separate OutOfMemoryError channel, and transport failures are not parser errors.

Atomic memory serialization

requestSerializedSize and responseSerializedSize validate a shared silk.http head and calculate its exact canonical size with checked arithmetic. writeRequestInto and writeResponseInto perform that entire pass before their first write. They emit one SP between start-line components, : for each ordered field, CRLF for every line, and one terminating empty line. Responses always retain the required reason separator, even for an empty reason.

Invalid values, overflow, or an insufficient destination leave every output byte unchanged. These operations write no body bytes and allocate nothing.

Availability and boundaries

The parser and serializers are ordinary target-neutral Silk and are available on supported native and intended LLVM-to-Wasm targets. They require no socket, TLS, buffered-I/O, stream, or operating system service. Only construction and explicit owned copies require Allocator.

This module ends at the empty line. Body framing, chunk coding, trailers, connection persistence, content decoding, transport reads and writes, proxy policy, and HTTP client/server exchange state belong to their respective actors.

The implementation and portable acceptance evidence live in http_head.silk and httpHeadAcceptance.ts.

On this page