Skip to main content

HTTP

Constant HTTP 

Source
pub const HTTP: &str = "//! An HTTP/1.1 server: requests read and parsed, responses built and written,\n//! and a router that serves GET routes.\n//!\n//! `Server` is the simple way in: give it a handler for each path with `get`,\n//! then `listen` on an address. A handler is a plain function from a\n//! `Request` to a `Response`, so it cannot await anything. `read_request`,\n//! `write_response` and `parse_request_head` are the parts underneath, for\n//! serving a `std/net` `TcpStream` yourself. There is no HTTP client. Every\n//! module sees these names without an import, and a module\'s own definition\n//! of one of them shadows the one here.\n//!\n//! Only `Content-Length` says how long a request\'s body is: chunked transfer\n//! encoding is not supported, and a chunked request\'s body is misread as the\n//! next request. A request\'s header names are lower-cased, since HTTP\n//! ignores their case, and the order of a response\'s headers on the wire can\n//! change from one run of a program to the next.\n//!\n//! `Server::listen` serves until the process is stopped, so this example\n//! has the server `dispatch` a request parsed from bytes, with no socket:\n//!\n//! ```\n//! let app = Server::new().get(\"/hello\", |_| Response::text(200, \"hi\"))\n//!\n//! let raw = bytes_from_string(\"GET /hello HTTP/1.1\\r\\nHost: example\\r\\nX-Trace: abc\\r\\n\\r\\n\")\n//! let req = parse_request_head(raw, Limits::default()).unwrap().unwrap()\n//! assert_eq(req.headers.get(\"x-trace\").unwrap(), \"abc\") // names are lower-cased\n//!\n//! let resp = app.dispatch(req)\n//! assert_eq(resp.status, 200)\n//! assert_eq(resp.headers.get(\"content-length\").unwrap(), \"2\")\n//! let wire = resp.to_bytes().to_string().unwrap()\n//! assert(wire.starts_with(\"HTTP/1.1 200 OK\\r\\n\"), \"the status line comes first\")\n//! assert(wire.ends_with(\"\\r\\n\\r\\nhi\"), \"then a blank line and the body\")\n//! ```\n\n// Nova standard library -- HTTP/1.1, server side.\n//\n// Compiled as an implicit module and glob-imported into every user module, so\n// these names need no `import` (see docs/adr/0004-stdlib-compile-model.md).\n// The transport is `std/net`. Parsing and serialising never open a socket;\n// `Server::listen`, at the end of this file, binds one and is the only code\n// here that does.\n//\n// **What ships, against `nova-spec/20-STDLIB.md` section 6.** That section\n// specifies a router over `pub type Handler = async fn(Request) -> Response`.\n// That alias does not parse -- measured, `P0001: expected type (in type\n// alias), found async` -- and a type alias of any kind is `E0900`, so\n// `Server` at the end of this file types each handler inline as\n// `fn(Request) -> Response`. A handler is therefore synchronous: it builds\n// its response without awaiting. Only GET routes ship, and there is no\n// client. `Server::listen` shuts down gracefully on SIGTERM or SIGINT\n// (Ctrl+Break or Ctrl+C on Windows); see its own comment and\n// docs/adr/0022-process-shutdown-signals.md. See also\n// docs/adr/0019-offset-table-intrinsic-boundary.md and section 6\'s own dated\n// amendments.\n//\n// **Parsing is one intrinsic and it returns offsets, not values.**\n// `http_parse_request` hands back a flat `[Int]` table of byte offsets into\n// the caller\'s own buffer -- no copies, no Rust-side state, nothing to\n// release. `parse_offsets` below is a thin `pub` wrapper over it, the same\n// shape `std/bytes`\'s `bytes_from_ints` uses, so a fixture can pin the raw\n// encoding; ordinary callers want `parse_request_head`.\n//\n// **Header names are lower-cased and the original casing is not kept.** HTTP\n// field names are case-insensitive, so a `Map` keyed on the raw casing would\n// answer `get(\"content-length\")` differently depending on what the peer sent.\n//\n// **Two limitations of `read_request` below share one cause: it reads\n// exactly one request\'s worth of bytes per connection turn and trusts\n// `Content-Length` to say where that ends.** `Server` shares the first and\n// not the second: it keeps one buffer per connection, so it answers\n// pipelined requests in order.\n//\n// - `Content-Length` is the only framing either understands. Chunked\n//   transfer-encoding is neither supported nor detected, so a chunked\n//   request\'s body is left on the connection and is misread as the next\n//   request.\n// - `read_request` discards bytes already read past the current request\'s\n//   body, so a pipelined request is lost and its connection deadlocks rather\n//   than merely going unsupported. See `read_request`\'s own doc comment for\n//   the exact wording this needs.\n//\n// **A third limitation, with a different cause.** Neither `conn.read` call\n// in `read_request` below carries a timeout, so a peer that connects and\n// then sends nothing -- or sends a partial head or body and stops -- holds\n// the task serving it parked forever. `std/net::TcpStream::read_timeout`\n// already exists (`std/net/lib.nova`) and was not used there; see\n// `read_request`\'s own doc comment for the reasoning, and\n// docs/adr/0019-offset-table-intrinsic-boundary.md\'s Consequences section\n// for the same gap recorded against that ADR. `Server` does not share this\n// one either: every wait in its `serve` is bounded.\n\n// The request method. `Unknown` carries the raw token rather than discarding\n// it: a request may carry any method a peer invents, and the parser must not\n// abort on one it does not know.\n//\n// Named `Unknown` rather than `nova-spec/20-STDLIB.md` section 6\'s `Other`,\n// deliberately. `Other` is already an `IoErrorKind` variant in `std/io`, every\n// std module is glob-imported into every module, and whether two std modules\n// may export one variant name is not established on this tree. This increment\n// is not the place to find out.\n/// An HTTP request method.\n///\n/// Seven methods have variants of their own, each matched exactly as written\n/// in capitals; any other method, `get` in lower case included, is `Unknown`.\npub type Method =\n    /// `GET`.\n    | Get\n    /// `POST`.\n    | Post\n    /// `PUT`.\n    | Put\n    /// `DELETE`.\n    | Delete\n    /// `PATCH`.\n    | Patch\n    /// `HEAD`.\n    | Head\n    /// `OPTIONS`.\n    | Options\n    /// Any other method, holding its name as the request wrote it.\n    | Unknown(String)\n\n// Why a request head was rejected.\n//\n// **This type is one half of a wire contract, for its first seven kinds.**\n// The other half is the `ERR_*` constants in\n// `crates/nova-runtime/src/http.rs`, which produce those seven codes. The two\n// are independent copies of one numbering -- the shape that has already\n// produced a miscompile in this project -- so `tests/runtime/http_malformed.nova`\n// pins together the ones `http_error_kind_of` actually maps; see that\n// function\'s own doc comment for exactly which those are, since \"per kind\"\n// would overclaim -- not every one of the seven is currently reachable from\n// a fixture through this type.\n//\n// `InvalidUtf8`, `BodyTooLarge` and `Transport` have no runtime counterpart\n// and no `ERR_*` code: `std/http` reports all three itself, so none of them\n// needs to stay in sync with the intrinsic\'s own numbering the way the first\n// seven do.\n/// Why reading or parsing a request failed.\npub type HttpErrorKind =\n    /// The request head is too long: longer than the `max_head_bytes` of the\n    /// `Limits` given, or than 8,192 bytes, whatever the limit.\n    | HeadTooLarge\n    /// The request head has more headers than the `max_header_count` of the\n    /// `Limits` given, or more than 100, whatever the limit.\n    | TooManyHeaders\n    /// The request line is malformed: its method, target or version is not\n    /// valid, or the version is not `HTTP/1.0` or `HTTP/1.1`.\n    | BadRequestLine\n    /// A header line is malformed, or `Content-Length` appears twice or is\n    /// not a whole number written in digits.\n    | BadHeader\n    /// A line ends badly, as with a carriage return not followed by a line\n    /// feed.\n    | BadNewline\n    /// The parser finished a head without its method, target or version,\n    /// which it is not expected to do.\n    | IncompleteHeadFields\n    /// Part of a parsed head lay outside the bytes given, which the parser\n    /// is not expected to do.\n    | SliceOutOfBuffer\n    /// A header\'s value is not valid UTF-8.\n    | InvalidUtf8\n    /// `Content-Length` is more than the `max_body_bytes` of the `Limits`\n    /// given.\n    | BodyTooLarge\n    /// Reading from the connection failed, or the connection closed before\n    /// the request was complete.\n    | Transport\n\n/// The error `read_request` and `parse_request_head` return.\npub record HttpError {\n    /// What went wrong.\n    pub kind: HttpErrorKind,\n    /// A short description, such as `duplicate Content-Length header`. For a\n    /// `Transport` error it is the connection\'s own error message, or\n    /// `connection closed mid-head` or `connection closed mid-body`.\n    pub message: String,\n}\n\n// Map a parse status to its kind. The argument is the **negated** status the\n// intrinsic returned, so a status of `-3` arrives here as `3`.\n//\n// An unrecognised code is `BadRequestLine` rather than a panic: a status this\n// function does not know is a runtime bug, and mapping it to a diagnosable\n// kind keeps an error flowing instead of ending the process -- the same call\n// `std/io`\'s `io_error_kind_of` makes for the same reason.\n//\n// **Which of the seven codes a Nova fixture actually drives through this\n// function, and which do not.** `BadRequestLine`, `BadHeader` and\n// `BadNewline` (3, 4, 5) do: `tests/runtime/http_malformed.nova` feeds each\n// one\'s triggering input to `parse_request_head` under `Limits::default()`\n// and asserts the mapped kind on the `Err` it returns. `HeadTooLarge` and\n// `TooManyHeaders` (1, 2) are different: their own raw negative status is\n// observed directly through `parse_offsets` in `tests/runtime/http_limits.nova`,\n// but every `parse_request_head` call in that fixture supplies a `Limits` at\n// or below the runtime\'s own ceiling, so `parse_request_head`\'s two\n// Nova-side checks (just below) return a literal `HeadTooLarge` or\n// `TooManyHeaders` before ever reaching `status < 0` there -- this\n// function\'s own arms for 1 and 2 are real code, reachable by a caller whose\n// `Limits` is looser than the runtime\'s wall, but no fixture currently\n// drives them. `IncompleteHeadFields` (6) and `SliceOutOfBuffer` (7) are\n// undemonstrated by any fixture, and differ from each other at their Rust\n// call sites: `SliceOutOfBuffer`\'s (`offset_within` in\n// `crates/nova-runtime/src/http.rs`) argues from httparse\'s zero-copy\n// contract that it cannot fire in practice and checks anyway, while\n// `IncompleteHeadFields`\'s call site carries no such argument either way.\n/// Returns the `HttpErrorKind` for an error code from `parse_offsets`, which\n/// reports the code as a negative status: pass the status negated.\n///\n/// Codes 1 to 7 are `HeadTooLarge`, `TooManyHeaders`, `BadRequestLine`,\n/// `BadHeader`, `BadNewline`, `IncompleteHeadFields` and `SliceOutOfBuffer`,\n/// in that order. Any other code gives `BadRequestLine`.\npub fn http_error_kind_of(code: Int) -> HttpErrorKind {\n    if code == 1 { return HeadTooLarge }\n    if code == 2 { return TooManyHeaders }\n    if code == 3 { return BadRequestLine }\n    if code == 4 { return BadHeader }\n    if code == 5 { return BadNewline }\n    if code == 6 { return IncompleteHeadFields }\n    if code == 7 { return SliceOutOfBuffer }\n    BadRequestLine\n}\n\n// Caller-chosen ceilings.\n//\n// **`max_head_bytes` and `max_header_count` may only tighten, never loosen.**\n// The runtime compiles in its own hard wall (8 KiB and 100, in\n// `crates/nova-runtime/src/http.rs`) and checks it before allocating anything\n// on the Nova side, because the intrinsic takes one argument and there is\n// nowhere for a caller\'s limit to arrive. The two fields here are checked on\n// this side, in addition. `max_body_bytes` is this side\'s alone: the intrinsic\n// returns `body_start` and never looks at the body.\n/// Limits on the size of a request, for `read_request` and\n/// `parse_request_head`.\n///\n/// `Limits::default()` allows a head of 8,192 bytes with 100 headers, and a\n/// body of 1,048,576 bytes (1 MiB); `Server` always uses those. The two head\n/// limits can only be made stricter: a head over 8,192 bytes, or with more\n/// than 100 headers, is refused whatever they say.\npub record Limits {\n    /// The most bytes a request head may take.\n    pub max_head_bytes: Int,\n    /// The most headers a request may have.\n    pub max_header_count: Int,\n    /// The largest `Content-Length` accepted.\n    pub max_body_bytes: Int,\n}\n\nimpl Default for Limits {\n    fn default() -> Limits {\n        Limits { max_head_bytes: 8192, max_header_count: 100, max_body_bytes: 1048576 }\n    }\n}\n\n// A parsed request. `body` is the bytes after the head, already bounded by\n// `Content-Length` and by `Limits::max_body_bytes`.\n/// An HTTP request: its method, target, headers and body.\npub record Request {\n    /// The request\'s method.\n    pub method: Method,\n    /// The request\'s target as the request wrote it, query string included,\n    /// such as `/search?q=nova`. Nothing in it is decoded.\n    pub path: String,\n    /// The headers, by lower-cased name. Each value has the spaces and tabs\n    /// around it removed. Of two headers with the same name, the last is\n    /// kept.\n    pub headers: Map<String, String>,\n    /// The body: as many bytes as `Content-Length` says, or none without that\n    /// header. Empty in a request from `parse_request_head`.\n    pub body: Bytes,\n}\n\n// The raw offset table for `buf`, exactly as\n// `crates/nova-runtime/src/http.rs` encodes it.\n//\n// **Element 0 is a status and a caller must read it first.** `0` is a complete\n// head, `1` is a partial one (read more bytes and call again), and a negative\n// value is the negation of an error kind. On any non-zero status **the array\n// has length 1**, so a caller who skips the check indexes out of bounds rather\n// than reading a plausible-looking offset. That is deliberate.\n//\n// Exposed so `tests/runtime/http_offsets.nova` can pin the encoding from this\n// side; ordinary callers want `parse_request_head`.\n/// Returns the raw table of byte offsets that parsing the request head at the\n/// start of `buf` gives. `parse_request_head` is usually the one to call.\n///\n/// Element 0 is a status: 0 for a complete head, 1 for an incomplete one, and\n/// otherwise a negative error code, which `http_error_kind_of` turns into a\n/// kind once negated. Unless the status is 0, the table holds only that\n/// element. A complete head\'s table goes on with the method\'s start\n/// and length, the target\'s start and length, the HTTP minor version (0 or\n/// 1), the number of headers, four elements per header (its name\'s start and\n/// length, then its value\'s), and last the offset where the body starts. All\n/// offsets count bytes from the start of `buf`.\npub fn parse_offsets(buf: Bytes) -> [Int] { http_parse_request(buf) }\n\n// Decode a complete head out of `buf`, or report that more bytes are needed.\n//\n// `Ok(None)` means the head is incomplete: read more and call again with the\n// longer buffer. `Ok(Some(req))` is a complete head, with `body` empty --\n// `read_request` fills that in once it knows `Content-Length`.\n/// Parses the request head at the start of `buf`.\n///\n/// Returns `Ok(None)` if `buf` does not yet hold a whole head: read more and\n/// call again with the longer buffer. Once it does, returns `Ok(Some(req))`,\n/// whose `body` is empty: the body\'s length is in its `content-length`\n/// header. Lines may end with a carriage return and line feed or with a line\n/// feed alone, and empty lines before the request line are skipped.\n///\n/// Returns an `HttpError` whose kind says how the head is malformed, or\n/// `HeadTooLarge` or `TooManyHeaders` if it breaks `limits`. The byte limit\n/// applies to all of `buf`, so bytes after the head count against it too. A\n/// second `Content-Length` header is `BadHeader`, and a header value that is\n/// not valid UTF-8 is `InvalidUtf8`.\n///\n/// Part of a head, then all of it:\n///\n/// ```\n/// let limits = Limits::default()\n///\n/// // Part of a head is `Ok(None)`: read more, then try again with it all.\n/// let start = bytes_from_string(\"GET /items HTTP/1.1\\r\\nHost: shop\")\n/// assert(parse_request_head(start, limits).unwrap().is_none(), \"not a whole head yet\")\n/// let whole = start.concat(bytes_from_string(\"\\r\\n\\r\\n\"))\n/// let req = parse_request_head(whole, limits).unwrap().unwrap()\n/// assert_eq(req.path, \"/items\")\n/// assert_eq(req.headers.get(\"host\").unwrap(), \"shop\")\n///\n/// // A malformed head is an `Err`.\n/// assert(\n///     parse_request_head(bytes_from_string(\"HELLO\\r\\n\\r\\n\"), limits).is_err(),\n///     \"no target or version\",\n/// )\n/// ```\npub fn parse_request_head(buf: Bytes, limits: Limits) -> Result<Option<Request>, HttpError> {\n    // Measures the accumulated read buffer, not the head\'s own exact length:\n    // when called from `read_request`, `buf` may hold a few bytes past the\n    // head\'s end if the read that completed it also pulled in the start of\n    // the body, so a legal head can be rejected slightly before its own\n    // exact byte count if the peer\'s writes happen to land that way. The\n    // design spec\'s own words license exactly this (\"bounds the buffer a\n    // client can force before the head completes\"), not an exact bound on\n    // the head alone.\n    if buf.len() > limits.max_head_bytes {\n        return Err(HttpError { kind: HeadTooLarge, message: \"request head over max_head_bytes\" })\n    }\n    let t = parse_offsets(buf)\n    let status = t[0]\n    if status == 1 { return Ok(None) }\n    if status < 0 {\n        let kind = http_error_kind_of(0 - status)\n        return Err(HttpError { kind: kind, message: \"request head rejected by the parser\" })\n    }\n    let n = t[6]\n    if n > limits.max_header_count {\n        return Err(\n            HttpError { kind: TooManyHeaders, message: \"header count over max_header_count\" },\n        )\n    }\n    let method = method_from(text_at(buf, t[1], t[2]))\n    let path = text_at(buf, t[3], t[4])\n    let mut headers: Map<String, String> = Map::new()\n    let mut i = 0\n    while i < n {\n        let at = 7 + 4 * i\n        // Lower-cased because HTTP field names are case-insensitive. Full\n        // Unicode lowercasing has nothing to bite on here: `httparse`\n        // validates a name as an HTTP token, which is ASCII-only, so this\n        // sees only ASCII.\n        let name = text_at(buf, t[at], t[at + 1]).to_lower()\n        let mut value = \"\"\n        match text_at_utf8(buf, t[at + 2], t[at + 3]) {\n            Ok(s) => value = s\n            Err(e) => return Err(e)\n        }\n        // A second `Content-Length` header is rejected outright rather than\n        // silently overwritten by `insert` below. RFC 9112 section 6.3 treats\n        // multiple `Content-Length` fields with differing values as invalid\n        // framing; this takes the simpler and stricter position of rejecting\n        // any repeat, matching or not, rather than keeping the first value\n        // and comparing it against every later one -- there is exactly one\n        // value available to compare against here regardless, since this\n        // loop (like `Map::insert`) only ever sees the latest one once a\n        // name repeats, so \"compare instead of reject\" would need its own\n        // separate bookkeeping to buy anything.\n        if name == \"content-length\" && headers.contains_key(name) {\n            return Err(HttpError { kind: BadHeader, message: \"duplicate Content-Length header\" })\n        }\n        headers.insert(name, value)\n        i = i + 1\n    }\n    Ok(Some(Request { method: method, path: path, headers: headers, body: bytes_from_string(\"\") }))\n}\n\n// The bytes `buf[start .. start+len]` as text, defaulting to the empty\n// string if they are not valid UTF-8.\n//\n// Safe for the method, the path, and a header\'s name -- but not for a\n// header\'s value, which is why that fourth call site uses `text_at_utf8`\n// below instead of this function. Checked directly against the locked\n// `httparse` 1.10.1 source (`crates/nova-runtime`\'s `Cargo.lock` entry)\n// rather than assumed:\n//\n// - The method is matched against literal `\"GET \"`/`\"POST \"` or, failing\n//   that, `parse_token`\'s `is_method_token`, which is backed by `TOKEN_MAP`\n//   -- ASCII-only -- so it cannot be anything but ASCII.\n// - A header\'s name is matched against `TOKEN_MAP` the same way\n//   (`is_header_name_token`), so the same argument applies.\n// - The path looks like it should be the same risk as a header\'s value --\n//   `parse_uri` scans it against `URI_MAP`, which (like `HEADER_VALUE_MAP`\n//   below) admits `0x80..=0xFF` -- but `parse_uri` then validates the whole\n//   scanned slice with `str::from_utf8` and fails the *entire request line*\n//   (`Error::Token`) if that fails. So `httparse::Request::path` is\n//   `Option<&str>`, not bytes, and a `Complete` parse cannot have produced\n//   an invalid one. Measured, not assumed: a lone `0x80` byte placed in the\n//   path (no valid UTF-8 continuation) was tried against this parser\n//   directly, and it rejected the whole request line as `BadRequestLine`\n//   rather than completing with an undecodable path -- this function\'s\n//   `None` branch is not reachable from that call site.\nfn text_at(buf: Bytes, start: Int, len: Int) -> String {\n    match buf.slice(start, start + len).to_string() {\n        Some(s) => s\n        None => \"\"\n    }\n}\n\n// The bytes `buf[start .. start+len]` as text, or `InvalidUtf8` if they are\n// not valid UTF-8.\n//\n// Used only for a header\'s value -- the one call site `text_at`\'s doc\n// comment, just above, does not cover. `httparse::Header::value` is `&[u8]`,\n// not `&str`: httparse\'s own doc comment on that field says a value \"may\n// not\" be ASCII and stores it as bytes with no UTF-8 check anywhere, unlike\n// the method, path and header-name sites, which are already `&str` by the\n// time this module sees them. A byte outside ASCII is legal there under\n// HTTP\'s obs-text allowance (`HEADER_VALUE_MAP` admits `0x80..=0xFF`, the\n// same range `URI_MAP` admits), and such a byte need not be valid UTF-8 on\n// its own -- measured directly: a request whose entire header value is one\n// literal `0x80` byte parses to `status = 0` like any other request, with a\n// value offset pointing at a byte `to_string` reports as not UTF-8.\n// Silently substituting \"\" there would discard real request data with no\n// signal, which is what an earlier version of this module did at every call\n// site alike. See `tests/runtime/http_malformed.nova`\'s non-UTF-8 case.\nfn text_at_utf8(buf: Bytes, start: Int, len: Int) -> Result<String, HttpError> {\n    match buf.slice(start, start + len).to_string() {\n        Some(s) => Ok(s)\n        None => Err(HttpError { kind: InvalidUtf8, message: \"header value is not valid UTF-8\" })\n    }\n}\n\nfn method_from(token: String) -> Method {\n    if token == \"GET\" { return Get }\n    if token == \"POST\" { return Post }\n    if token == \"PUT\" { return Put }\n    if token == \"DELETE\" { return Delete }\n    if token == \"PATCH\" { return Patch }\n    if token == \"HEAD\" { return Head }\n    if token == \"OPTIONS\" { return Options }\n    Unknown(token)\n}\n\n// A response to serialise onto a connection.\n//\n// **Header order on the wire is `headers`\' iteration order**, which is a\n// `Map`\'s bucket order and therefore varies per process: `impl Hash for\n// String` is seeded per process, which is exactly the property that makes a\n// `Map` keyed on attacker-supplied header names resist a precomputed\n// collision set. HTTP does not require a header order, so nothing here\n// depends on it -- but that same seeding means a whole-response `.eq()`\n// against one fixed byte sequence cannot be a stable test, not merely an\n// inconvenient one to write: which of two headers comes first changes from\n// run to run. Measured, not just argued from the seeding --\n// `tests/runtime/http_serialise.nova` compared the whole buffer against one\n// fixed ordering first, and printed `wire matches false` about as often as\n// `true` across repeated separate runs. It checks the parts a header order\n// cannot move instead: the exact status line, the exact tail (the blank line\n// and the body), and that each expected header line occurs somewhere\n// between them.\n/// An HTTP response: a status code, headers and a body.\n///\n/// `ok`, `text`, `json` and `not_found` build the common ones, and each sets\n/// `content-length`. A response built by hand should set it too: `Server`\n/// keeps the connection open after a response, so without it a client cannot\n/// tell where the body ends.\npub record Response {\n    /// The status code, such as 200 or 404. It is written as given, without\n    /// a check.\n    pub status: Int,\n    /// The headers, by name. Each name is written as given, and their order\n    /// on the wire can change from one run of a program to the next.\n    pub headers: Map<String, String>,\n    /// The body.\n    pub body: Bytes,\n}\n\nimpl Response {\n    // `200 OK` carrying `body`, with `content-length` set from it.\n    /// Returns a `200 OK` response carrying `body`, with `content-length` set\n    /// and no `content-type`.\n    pub fn ok(body: Bytes) -> Response {\n        let mut h: Map<String, String> = Map::new()\n        h.insert(\"content-length\", \"${body.len()}\")\n        Response { status: 200, headers: h, body: body }\n    }\n\n    // `status` carrying `s` as UTF-8 text.\n    /// Returns a response with status `status` carrying `s` as UTF-8 text,\n    /// with `content-type` set to `text/plain; charset=utf-8` and\n    /// `content-length` to the text\'s length in bytes.\n    pub fn text(status: Int, s: String) -> Response {\n        let body = bytes_from_string(s)\n        let mut h: Map<String, String> = Map::new()\n        h.insert(\"content-length\", \"${body.len()}\")\n        h.insert(\"content-type\", \"text/plain; charset=utf-8\")\n        Response { status: status, headers: h, body: body }\n    }\n\n    /// Returns a `404 Not Found` response whose body is the text `not found`.\n    ///\n    /// `Server` answers with it when no route matches.\n    pub fn not_found() -> Response { Response::text(404, \"not found\") }\n\n    // `200 OK` carrying `v` as JSON text, with `content-type` and\n    // `content-length` set. It takes the name section 6 gives a client-side\n    // decoder (`json<T: FromJson>(self)`): there is no client, and one type\n    // cannot have both, so the constructor has it.\n    /// Returns a `200 OK` response carrying `v` as JSON text, as `stringify`\n    /// writes it, with `content-type` set to `application/json` and\n    /// `content-length` set.\n    ///\n    /// Panics where `stringify` does, if arrays and objects in `v` nest too\n    /// deep.\n    pub fn json(v: JsonValue) -> Response {\n        let body = bytes_from_string(stringify(v))\n        let mut h: Map<String, String> = Map::new()\n        h.insert(\"content-length\", \"${body.len()}\")\n        h.insert(\"content-type\", \"application/json\")\n        Response { status: 200, headers: h, body: body }\n    }\n\n    // The response as it goes on the wire.\n    //\n    // No intrinsic backs this: a response is a status line, a header block and\n    // a body, which Nova builds with string interpolation and `Bytes::concat`.\n    // That is the design\'s decision rather than a gap -- see\n    // docs/adr/0019-offset-table-intrinsic-boundary.md.\n    /// Returns the response as it is sent: the status line, one line per\n    /// header, a blank line, and the body. Each line ends with a carriage\n    /// return and a line feed.\n    ///\n    /// The status line holds `HTTP/1.1`, the status code and a reason phrase:\n    /// the usual one for 200, 201, 204, 400, 404, 405, 413 and 500, and for\n    /// any other code a phrase for its class, such as `Client Error`. Each\n    /// header is written `name: value`. A header whose name or value holds a\n    /// carriage return or a line feed is left out, so it cannot add lines of\n    /// its own. Nothing else is added: no header the response does not hold.\n    pub fn to_bytes(self) -> Bytes {\n        let mut head = \"HTTP/1.1 ${self.status} ${reason_phrase(self.status)}\\r\\n\"\n        let names = self.headers.keys()\n        let mut i = 0\n        while i < names.len() {\n            let k = names[i]\n            match self.headers.get(k) {\n                // A header whose name or value carries a CR or LF is\n                // skipped rather than emitted: see `is_crlf_free`\'s own doc\n                // comment for why this is the right layer to refuse it.\n                Some(v) => {\n                    if is_crlf_free(k) && is_crlf_free(v) { head = \"${head}${k}: ${v}\\r\\n\" }\n                }\n                None => head = head\n            }\n            i = i + 1\n        }\n        bytes_from_string(\"${head}\\r\\n\").concat(self.body)\n    }\n}\n\n// Whether `s` contains no CR and no LF, and so is safe to place verbatim in\n// one header line.\n//\n// **Response splitting is a real vulnerability class, and a stdlib\n// serialiser is the right layer to refuse it rather than every caller\n// separately.** A header name or value carrying an embedded CRLF can\n// terminate the line `to_bytes` is building early and forge arbitrary\n// further header lines -- or even a second status line -- into what the\n// peer reads as one response. Request-derived strings cannot carry either\n// byte: `httparse` itself terminates the request line and every header\n// value at CRLF, so the reachable case is a handler that writes body-derived\n// or otherwise untrusted data straight into a response header.\n//\n// **This paragraph used to name `examples/05-json-api` as exactly that\n// shape, and that example is the opposite of it.** The sentence was written\n// before the example existed, so nothing checked it. `json_response` there\n// sets two headers -- a literal `application/json`, and `\"${b.len()}\"`, an\n// `Int` interpolation -- and neither can carry CR or LF, which makes it the\n// pattern to copy rather than the hazard to point at. The argument for this\n// check never rested on an exemplar anyway: the serialiser cannot see where\n// a caller\'s value came from, so it refuses the bytes whatever the current\n// set of callers happens to do. Whether some handler in the tree does feed\n// untrusted data into a header is a search of `*.nova` for `insert(`, read\n// for which hits write into a header map and where each value comes from --\n// it is not this comment.\n//\n// `to_bytes` above skips (does not emit) any header whose name or value\n// fails this check, rather than emitting it verbatim or panicking, and its\n// own `Bytes`-returning signature is unchanged for that -- becoming\n// fallible here was judged out of scope for this fix. A caller that must\n// know whether a header was dropped needs to check its own data before\n// handing it to `Response`; silently dropping is still strictly safer than\n// the alternative of emitting it.\nfn is_crlf_free(s: String) -> Bool { !s.contains(\"\\r\") && !s.contains(\"\\n\") }\n\n// The reason phrase for a status code. Unknown codes get the class\'s own\n// phrase rather than an empty string: HTTP/1.1 lets a client ignore the\n// phrase entirely, so a plausible one costs nothing and reads better in a\n// packet capture than a blank.\nfn reason_phrase(status: Int) -> String {\n    if status == 200 { return \"OK\" }\n    if status == 201 { return \"Created\" }\n    if status == 204 { return \"No Content\" }\n    if status == 400 { return \"Bad Request\" }\n    if status == 404 { return \"Not Found\" }\n    if status == 405 { return \"Method Not Allowed\" }\n    if status == 413 { return \"Payload Too Large\" }\n    if status == 500 { return \"Internal Server Error\" }\n    if status < 200 { return \"Informational\" }\n    if status < 300 { return \"Success\" }\n    if status < 400 { return \"Redirection\" }\n    if status < 500 { return \"Client Error\" }\n    \"Server Error\"\n}\n\n// The transport half: reading a request off a connection and writing a\n// response back onto one, honouring keep-alive. `std/net`\'s `TcpStream` is\n// the only socket type either function below touches, through the `Read` and\n// `Write` impls that module defines for it -- neither function opens one.\n// Only `Server::listen`, at the end of this file, binds a listener.\n\n// Read one request from `conn`, honouring keep-alive: the caller loops, and\n// this reads exactly one request\'s head and body, leaving anything after it\n// on the connection.\n//\n// **This accumulates the head with `Bytes::concat`, which is O(n) per call**,\n// so building an n-byte head out of k reads is O(n*k). Bounded by\n// `max_head_bytes` at 8 KiB that is not worth a rope or a growable buffer;\n// it is noted here so a reader knows it was weighed rather than missed.\n//\n// **One socket wait per task.** Staging two I/O waits in one poll ends the\n// process (`stage_park` in `crates/nova-runtime/src/task.rs`), so a task\n// parked in `accept` cannot also be here. A server needs one task accepting\n// and one per connection -- see `std/net`\'s `accept` doc comment.\n//\n// **Neither `conn.read` call below carries a timeout, and that is a live\n// denial-of-service, not a hygiene gap.** Both park with no deadline\n// (`crates/nova-runtime/src/net.rs`\'s `poll_read` calls\n// `stage_io_park(socket, Interest::Read, None)` -- the trailing `None` is\n// the deadline argument). A peer that connects and sends nothing, or sends a\n// partial head or body and then goes silent, holds this task parked\n// forever, and on the single-threaded executor this project runs, one such\n// connection costs one socket and one task, indefinitely, for the price of\n// a single `connect`. `std/net`\'s `TcpStream::read_timeout`\n// (`std/net/lib.nova`) already exists and documents EOF semantics identical\n// to plain `read`\'s; it was not used here. That is recorded as a gap in\n// this increment\'s own spec, not an oversight this code silently carries --\n// section 6\'s limits table is entirely byte-valued, so no task in this\n// increment was ever asked for a temporal bound. Implementing one is later\n// work; disclosing its absence is not.\n//\n// **A pipelined second request deadlocks the connection; it is not merely\n// unsupported.** This function reads into a local buffer and returns\n// `body.slice(0, want)`, so any bytes already pulled off the socket past the\n// end of the current request\'s body are discarded when it returns. A client\n// that writes a second request before reading the first response -- which\n// HTTP/1.1 permits -- has that second request\'s bytes eaten here. The server\n// then waits on its next `read` for a request that has already arrived, while\n// the client waits for a response that will never come: both sides block\n// forever, which is a different and worse claim than \"request 2 is ignored\".\n//\n// **`tests/runtime/http_keepalive.nova` does not exercise this case.** Its\n// client `await`s each reply before writing the next request, so it never\n// pipelines the two requests it sends. That fixture passing is evidence this\n// function\'s ordinary keep-alive path works; it is not evidence the pipelined\n// path is safe, and a reader should not conclude otherwise from seeing it\n// green.\n/// Reads one request from `conn`: its head, then a body of `Content-Length`\n/// bytes.\n///\n/// An `async fn`: it suspends the calling task while it waits for the client,\n/// and other tasks run meanwhile. It waits as long as the client takes, so a\n/// client that sends nothing keeps it waiting for ever; `std/time`\'s\n/// `timeout` can bound the wait. On a connection kept open for more requests,\n/// call it again for the next one only after answering this one: any bytes\n/// it read past this request\'s body are dropped, so a request that a client\n/// sends before reading the response can be lost, and both sides then wait\n/// for ever.\n///\n/// Returns an `HttpError` of kind `Transport` if reading fails or the\n/// connection closes before the request is complete, `BodyTooLarge` if\n/// `Content-Length` is more than `limits` allows, `BadHeader` if it is not a\n/// whole number written in digits, and the kinds `parse_request_head`\n/// returns for a malformed head. As there, the head\'s byte limit counts\n/// every byte read so far, which can include the start of the body.\n///\n/// One request read from a connection and answered, over the loopback\n/// address:\n///\n/// ```\n/// async fn serve_one(listener: TcpListener) {\n///     let conn = listener.accept().await.unwrap()\n///     let req = read_request(conn, Limits::default()).await.unwrap()\n///     let reply = Response::text(200, \"got ${req.body.len()} bytes\")\n///     write_response(conn, reply).await.unwrap()\n///     conn.close().await.unwrap()\n/// }\n///\n/// async fn main() {\n///     let listener = bind(\"127.0.0.1:0\").unwrap()\n///     let server = spawn(serve_one(listener))\n///     let client = connect(\"127.0.0.1:${listener.local_port().unwrap()}\").await.unwrap()\n///     let ask = \"POST /upload HTTP/1.1\\r\\nContent-Length: 5\\r\\n\\r\\nhello\"\n///     client.write(bytes_from_string(ask)).await.unwrap()\n///     let reply = client.read_to_end().await.unwrap().to_string().unwrap()\n///     assert(reply.starts_with(\"HTTP/1.1 200 OK\\r\\n\"), reply)\n///     assert(reply.ends_with(\"\\r\\n\\r\\ngot 5 bytes\"), reply)\n///     server.join().await\n///     listener.close().await.unwrap()\n/// }\n/// ```\npub async fn read_request(conn: TcpStream, limits: Limits) -> Result<Request, HttpError> {\n    let mut buf = bytes_from_string(\"\")\n    let mut head: Option<Request> = None\n    while head.is_none() {\n        let chunk = match conn.read(4096).await {\n            Ok(b) => b\n            Err(e) => return Err(HttpError { kind: Transport, message: e.message })\n        }\n        if chunk.len() == 0 {\n            return Err(HttpError { kind: Transport, message: \"connection closed mid-head\" })\n        }\n        buf = buf.concat(chunk)\n        // One arm, not two: `parse_request_head` already returns\n        // `Option<Request>`, so the loop assigns it straight through. It is\n        // also the form the language permits -- `Ok(Some(r))` is\n        // `E0900: nested patterns inside variants are not supported yet`.\n        match parse_request_head(buf, limits) {\n            Ok(maybe) => head = maybe\n            Err(e) => return Err(e)\n        }\n    }\n    let req = match head {\n        Some(r) => r\n        None => return Err(HttpError { kind: BadRequestLine, message: \"no head after parsing\" })\n    }\n\n    // The head\'s own length, so the body can be split off what was already\n    // read. `parse_offsets` is cheap and the buffer is unchanged, so re-reading\n    // `body_start` costs less than threading it out of `parse_request_head`\n    // and widening that function\'s return type for one caller.\n    //\n    // Indexed straight to the last element with no status check first --\n    // safe here specifically because reaching this line already required\n    // `parse_request_head(buf, limits)` above to return `Ok(Some(_))` on\n    // this same, unmutated `buf`, which happens only at status `0`; see\n    // `parse_offsets`\'s own doc comment for the check a caller in general\n    // must make first.\n    let t = parse_offsets(buf)\n    let body_start = t[t.len() - 1]\n\n    let want = match content_length_of(req, limits.max_body_bytes) {\n        Ok(n) => n\n        Err(e) => return Err(e)\n    }\n    if want > limits.max_body_bytes {\n        return Err(HttpError { kind: BodyTooLarge, message: \"Content-Length over max_body_bytes\" })\n    }\n    let mut body = buf.slice(body_start, buf.len())\n    // **The head\'s \"not worth engineering around\" conclusion above does not\n    // transfer here.** `max_body_bytes` defaults to 1 MiB, not 8 KiB, and the\n    // same per-call-O(n) `concat` accumulation is work amplification on\n    // attacker-controlled input at that size, not a rounding error.\n    //\n    // **Each read below asks for exactly `want - body.len()` bytes, not a\n    // fixed chunk size, which changes the common case but not the worst\n    // one.** A peer able to deliver the whole remainder in one read now\n    // costs this loop exactly one concatenation, regardless of `want` --\n    // previously, a fixed 4096-byte ask meant even a peer that had already\n    // sent the entire body cost exactly 256 concatenations to drain at the 1\n    // MiB default (not \"up to\" -- with the whole body already available,\n    // every read but the last returned the full 4096 bytes asked for), since\n    // this loop could only take 4096 bytes at a time regardless of how much\n    // more was already there to read. `Read::read` may still legally hand\n    // back fewer bytes than asked for on any call, so the worst case is\n    // exactly what it was before: a peer that sends the\n    // body `k` bytes at a time forces about `want / k` reads and\n    // concatenations, and because each concatenation copies everything\n    // accumulated so far plus the new chunk, the bytes actually moved are\n    // triangular in the read count -- about 134,742,016 bytes copied to\n    // assemble a 1,048,576-byte body at 4096 bytes per read (a ~128x\n    // amplification), or about 537,395,200 bytes at 1024 bytes per read\n    // (~512x). **Both figures are floors, not ceilings**: they hold only if\n    // the peer hands back exactly that many bytes on every read, and since\n    // `Read::read` may legally return fewer, a slower or more adversarial\n    // peer only raises the read count and the bytes copied -- never lowers\n    // them. At the worse of the two, that is roughly 230 microseconds in GC\n    // allocations alone at this project\'s own measured ~900 ns each\n    // (`docs/superpowers/specs/2026-09-01-std-http-request-parsing-design.md`\n    // section 7), against the ~100 microsecond/request budget a 10k req/sec\n    // gate implies on a single-threaded executor. Bounded, so not unlimited,\n    // but a lever attacker-controlled input can still pull on the worst-case\n    // path, recorded here rather than eliminated: no restructuring beyond\n    // sizing each read to what is still wanted, and no intrinsic, are part\n    // of this change -- that choice is recorded for the whole-branch review.\n    //\n    // **The `body.len() > limits.max_body_bytes` check this loop used to run\n    // on every iteration is gone, not merely untriggered.** It could only\n    // ever fire on overshoot -- reading more than `want` bytes in one call,\n    // which needed the old fixed 4096-byte ask to happen at all -- and when\n    // it did fire on a legitimate request it was a false rejection: with the\n    // defaults, any declared length in `(1044481, 1048576]` plus one\n    // trailing byte pulled into the same read pushed `body.len()` past\n    // `max_body_bytes` while `want` itself was still under it. Reading\n    // exactly `want - body.len()` bytes below means `body.len()` can never\n    // exceed `want`, and `want` is already checked against\n    // `limits.max_body_bytes` above before this loop starts, so the\n    // allocation this loop builds is bounded exactly now, not\n    // approximately -- there is no overshoot left for that check to catch.\n    while body.len() < want {\n        let chunk = match conn.read(want - body.len()).await {\n            Ok(b) => b\n            Err(e) => return Err(HttpError { kind: Transport, message: e.message })\n        }\n        if chunk.len() == 0 {\n            return Err(HttpError { kind: Transport, message: \"connection closed mid-body\" })\n        }\n        body = body.concat(chunk)\n    }\n\n    Ok(\n        Request {\n            method: req.method,\n            path: req.path,\n            headers: req.headers,\n            body: body.slice(0, want),\n        },\n    )\n}\n\n// `Content-Length`, or `Ok(0)` when the header is absent.\n//\n// **`Content-Length` is the only body framing v1 understands.** A request\n// without one is treated as bodiless; chunked transfer-encoding is out of\n// scope and is not detected, so a chunked request\'s body is left on the\n// connection and will be misread as the next request. That is a known\n// limitation of v1 rather than a defect to work around here -- it is recorded\n// in the design spec\'s section 10 and in this module\'s header.\n//\n// **Returns `Err(BadHeader)` when the header is present but is not a\n// non-empty run of decimal digits, rather than defaulting it to `0`.** RFC\n// 9112 section 6.3 requires rejecting such a message outright: treating\n// `\"5x\"`, `\"abc\"` or `\"\"` as `0` would accept the request with an empty body\n// while the peer still has the declared bytes queued to send, which desyncs\n// the connection exactly like the chunked-encoding and pipelining cases this\n// module\'s own header already documents -- the undelivered bytes sit on the\n// socket, and `read_request`\'s next call parses them as the start of a new\n// request. This was checked once and found NOT to be absorbed downstream:\n// `Bytes::slice` clamping a negative bound to `0` (which is what the old,\n// silently-defaulting version of this function relied on) only kept a\n// negative `want` from corrupting memory safety -- it said nothing about the\n// connection-framing consequence above, which is the one that actually\n// matters here and is what this function now prevents directly.\n//\n// `max` is `limits.max_body_bytes`, threaded through to `digits_to_int` so a\n// pathologically long digit string cannot wrap its accumulator negative; see\n// that function\'s own doc comment.\nfn content_length_of(req: Request, max: Int) -> Result<Int, HttpError> {\n    match req.headers.get(\"content-length\") {\n        Some(v) => match digits_to_int(v.trim(), max) {\n            Some(n) => Ok(n)\n            None => Err(\n                HttpError {\n                    kind: BadHeader,\n                    message: \"Content-Length is not a valid non-negative integer\",\n                },\n            )\n        }\n        None => Ok(0)\n    }\n}\n\n// A non-empty run of decimal digits as an `Int`, or `None` if `s` is empty\n// or contains anything else. Nova has no `String`-to-number conversion, so\n// this walks the characters.\n//\n// **Capped at `max`, rather than accumulating without bound.** `Int` wraps\n// on overflow (measured elsewhere in this project), so a pathologically\n// long digit string could otherwise wrap the accumulator negative. The\n// value this returns eventually reaches exactly one comparison against\n// `max` itself: `content_length_of` passes it straight through as `want`,\n// and `read_request` checks `want > limits.max_body_bytes`, the same `max`\n// passed in here. So once the running total exceeds `max` the exact value\n// stops mattering, it is already \"too large,\" and returning as soon as\n// that is known bounds the accumulator.\n//\n// **What that bound actually is, since the guarantee is conditional and an\n// earlier version of this comment stated it flatly.** Every iteration that\n// continues leaves `n <= max`, so the largest value `n` can reach is\n// `max * 10 + 9`. That cannot wrap while `max` stays below roughly\n// `Int::MAX / 10`. At the default `max_body_bytes` of 1 MiB the accumulator\n// tops out around ten million, so the margin is enormous -- but the\n// condition is on `max`, not on this function, and `max_body_bytes` is the\n// one ceiling here with no backstop underneath it: the runtime hard-walls\n// `max_head_bytes` and `max_header_count` before allocating, and never sees\n// the body at all. A caller passing a `max_body_bytes` near `Int::MAX`\n// would wrap this accumulator, and nothing clamps it. That value is\n// absurd -- hundreds of petabytes -- which is why this is a scoping note\n// rather than a check.\n//\n// This is narrower than a general overflow check, and is sufficient only\n// because of that comparison-only usage; it is not a general-purpose\n// decimal parser. One consequence of stopping early: a value\n// that is both longer than `max` warrants and would eventually prove\n// invalid past that point (digits followed by a stray non-digit) is\n// reported as a valid, too-large number rather than a malformed one, since\n// scanning stops the moment the running total is confirmed to exceed `max`\n// and the remaining characters are never inspected. The caller rejects the\n// request either way -- as `BodyTooLarge` instead of `BadHeader` in that one\n// edge case -- so this does not change whether the request is accepted.\nfn digits_to_int(s: String, max: Int) -> Option<Int> {\n    let cs = s.chars()\n    if cs.len() == 0 { return None }\n    let mut n = 0\n    let mut i = 0\n    while i < cs.len() {\n        let d = char_digit(cs[i])\n        if d < 0 { return None }\n        n = n * 10 + d\n        if n > max { return Some(n) }\n        i = i + 1\n    }\n    Some(n)\n}\n\nfn char_digit(c: Char) -> Int {\n    if c == \'0\' { return 0 }\n    if c == \'1\' { return 1 }\n    if c == \'2\' { return 2 }\n    if c == \'3\' { return 3 }\n    if c == \'4\' { return 4 }\n    if c == \'5\' { return 5 }\n    if c == \'6\' { return 6 }\n    if c == \'7\' { return 7 }\n    if c == \'8\' { return 8 }\n    if c == \'9\' { return 9 }\n    0 - 1\n}\n\n// Write `resp` to `conn`, looping until every byte is out.\n//\n// **`std/net`\'s `write` is one non-blocking attempt, not a `write_all`**, and\n// it returns the count it managed -- so a caller that ignored the count would\n// silently truncate a response under back-pressure. Returns the total written,\n// which equals the response\'s length on success.\n/// Writes all of `resp` to `conn`, laid out as `to_bytes` lays it out, and\n/// returns the number of bytes written.\n///\n/// An `async fn`: it suspends the calling task while the connection has no\n/// room for more, and other tasks run meanwhile. Returns the `IoError` a\n/// write fails with, or one of kind `Other` if a write makes no progress.\npub async fn write_response(conn: TcpStream, resp: Response) -> Result<Int, IoError> {\n    let wire = resp.to_bytes()\n    let total = wire.len()\n    let mut sent = 0\n    while sent < total {\n        let n = match conn.write(wire.slice(sent, total)).await {\n            Ok(n) => n\n            Err(e) => return Err(e)\n        }\n        if n <= 0 { return Err(IoError { kind: Other, message: \"write made no progress\" }) }\n        sent = sent + n\n    }\n    Ok(total)\n}\n\n// ---------------------------------------------------------------------------\n// The router: `Server`.\n//\n// Written in Nova over the pieces above. Handlers are `fn(Request) ->\n// Response` values -- this module\'s header says why not section 6\'s\n// `Handler` alias -- so a handler is synchronous: it builds its response\n// without awaiting.\n\n// How often `Server::listen` and every connection it serves look at the\n// shutdown flag, in milliseconds. A signal is noticed within one tick.\nconst SERVER_TICK_MS: Int = 100\n\n// How long one request may take, in milliseconds: from the moment `serve`\n// takes it up until its response has been written. A stalled client is\n// dropped at this deadline, so a shutdown always finishes.\nconst SERVER_REQUEST_MS: Int = 10000\n\n// A routing table and nothing else. Build one with `Server::new()` and `get`,\n// then `listen`.\n/// A router that serves GET routes over HTTP/1.1.\n///\n/// Build one with `Server::new()` and a call to `get` for each route, then\n/// serve it with `listen`. A request goes to the handler of the route whose\n/// path equals the request\'s target exactly, query string included; any\n/// other target, and any method but `GET`, gets `Response::not_found()`.\npub record Server { routes: Vec<Route> }\n\n// One GET route: an exact path, and the handler that answers it.\nrecord Route { path: String, handler: fn(Request) -> Response }\n\n// A request read off a connection, and the bytes that followed it: a\n// pipelined next request, or the start of one.\nrecord InFlight { req: Request, rest: Bytes }\n\nimpl Server {\n    /// Returns a server with no routes.\n    pub fn new() -> Server { Server { routes: Vec::new() } }\n\n    // Adds a GET route. Takes `self` rather than `mut self`, and returns\n    // `Server` rather than section 6\'s `Self`: a `mut self` call on the\n    // temporary `Server::new()` returns is `E0060`, and `Self` as a type in an\n    // impl block is `E0001`.\n    //\n    // The new server gets a copy of the route table, and `self` is left\n    // alone. A `Vec` is shared, not copied, on assignment, so pushing onto\n    // `self.routes` would add the route to every server built from the same\n    // base as well.\n    /// Returns a copy of this server with one more route: `GET` requests for\n    /// `path` go to `handler`.\n    ///\n    /// `self` is left unchanged, so calls chain, as in\n    /// `Server::new().get(\"/\", home).get(\"/about\", about)`. `path` must equal\n    /// a request\'s target exactly: `/a` matches neither `/a/` nor `/a?x=1`. Of\n    /// two routes with the same path, the one added first answers.\n    pub fn get(self, path: String, handler: fn(Request) -> Response) -> Server {\n        let mut rs: Vec<Route> = Vec::new()\n        let mut i = 0\n        while i < self.routes.len() {\n            match self.routes.get(i) {\n                Some(r) => rs.push(r)\n                None => {}\n            }\n            i = i + 1\n        }\n        rs.push(Route { path: path, handler: handler })\n        Server { routes: rs }\n    }\n\n    // The response to `req`: the handler of the GET route whose path equals\n    // `req.path` exactly, or `Response::not_found()` for any other path or\n    // method. Socket-free, so a test can call it directly.\n    /// Returns the response to `req`: what the handler of the route whose\n    /// path equals `req.path` returns, or `Response::not_found()` if there is\n    /// none.\n    ///\n    /// Only a `GET` request reaches a handler; any other method, `HEAD`\n    /// included, gets `not_found`. It touches no connection, so a test can\n    /// call it directly with a `Request` it builds.\n    pub fn dispatch(self, req: Request) -> Response {\n        match req.method {\n            Get => {\n                let mut i = 0\n                while i < self.routes.len() {\n                    match self.routes.get(i) {\n                        Some(r) => {\n                            if r.path == req.path {\n                                // Bound to a local first: calling a fn-typed\n                                // field directly, `r.handler(req)`, is `E0014`.\n                                let h = r.handler\n                                return h(req)\n                            }\n                        }\n                        None => {}\n                    }\n                    i = i + 1\n                }\n                Response::not_found()\n            }\n            _ => Response::not_found()\n        }\n    }\n\n    // Serves `addr` until the process is asked to stop: SIGTERM or SIGINT on\n    // Unix, Ctrl+Break or Ctrl+C on Windows.\n    //\n    // **Graceful.** On the first such signal it stops accepting within one\n    // tick, closes the listener, and returns `Ok(())`. Connections are not\n    // touched: each finishes the request it has started, and any pipelined\n    // request that has already arrived whole, within that request\'s deadline,\n    // then closes; an idle one closes at its next tick. A second signal ends\n    // the process at once, by the signal\'s default action.\n    //\n    // **It returns before those connections finish.** Their tasks run on\n    // alongside any code after `.await`, and the process exits only once\n    // `block_on`\'s implicit join has finished them\n    // (docs/adr/0009-async-execution-model.md).\n    //\n    // **The signal handler is installed by the first `listen`, before `bind`,\n    // and stays installed for the rest of the process.** So a client that can\n    // connect can also stop the server gracefully. But a program that recovers\n    // from this function\'s `Err` and keeps running swallows its next signal,\n    // and only a second one ends it; docs/adr/0022-process-shutdown-signals.md\n    // records why.\n    //\n    // **Errors.** A failed `bind` or `accept` returns `Err`. An accept error\n    // closes the listener first and leaves the connections running; the flag\n    // is not set, so they keep being served.\n    /// Serves HTTP on `addr`, a `\"host:port\"` string such as\n    /// `\"127.0.0.1:8080\"`, until the process is asked to stop.\n    ///\n    /// An `async fn`: it suspends the calling task while it waits for\n    /// connections, and serves each connection in a task of its own. A\n    /// handler that panics ends the whole program.\n    ///\n    /// A connection stays open for more requests until the client closes it,\n    /// and they are answered in order, including requests sent before the\n    /// previous response arrived; a `Connection: close` header does not close\n    /// it. Requests are limited as `Limits::default()` says. A malformed or\n    /// oversized request, or one not read and answered within 10 seconds of\n    /// the server starting on it, closes its connection without a response.\n    /// No `100 Continue` is ever sent.\n    ///\n    /// On SIGTERM or SIGINT (Ctrl+C or Ctrl+Break on Windows) it stops\n    /// accepting within about a tenth of a second, and returns `Ok(())`.\n    /// Connections already open are not cut off: each finishes the request it\n    /// has started, and any further request that has already arrived whole,\n    /// then closes; an idle one closes within about a tenth of a second. The\n    /// program does not end until they have, and a second signal ends it at\n    /// once. Once `listen` has run, the program goes on catching those\n    /// signals, even after `listen` returns an `Err`: a program that carries\n    /// on after that is not ended by the next signal, only by a second.\n    ///\n    /// Returns an `IoError` if `addr` cannot be bound, as when it is already\n    /// in use, or if accepting a connection fails. After a failed accept the\n    /// connections already open go on being served.\n    pub async fn listen(self, addr: String) -> Result<(), IoError> {\n        let _ = shutdown_requested()\n        let l = match bind(addr) {\n            Ok(l) => l\n            Err(e) => return Err(e)\n        }\n        // The flag is checked on every turn, not only when an accept times\n        // out: under steady traffic the timeout arm may never run. The accept\n        // is polled under `timeout` because a bare one never returns without a\n        // connection, and closing the listener under it does not wake it.\n        while !shutdown_requested() {\n            match timeout(Duration::from_millis(SERVER_TICK_MS), l.accept()).await {\n                Ok(r) => {\n                    match r {\n                        Ok(conn) => { let _ = spawn(serve(self, conn)) }\n                        Err(e) => {\n                            let _ = l.close().await\n                            return Err(e)\n                        }\n                    }\n                }\n                Err(_) => {}\n            }\n        }\n        let _ = l.close().await\n        Ok(())\n    }\n}\n\n// One connection, for its whole life, with one buffer it keeps throughout.\n//\n// - **Idle (buffer empty):** waits for a request\'s first bytes one tick at a\n//   time. EOF closes; a timeout closes once the shutdown flag is set and\n//   otherwise keeps waiting.\n// - **In flight (buffer not empty):** a request is taken up -- its deadline\n//   starts now -- read to completion by `read_in_flight`, and answered. The\n//   response is written under the same deadline, so a peer that stops\n//   reading cannot hold the connection forever either. A request taken up\n//   after a stop gets no further reads: it is answered only if it has already\n//   arrived whole, so a peer that keeps sending cannot hold the connection\n//   open either.\n// - **After an answer:** bytes left in the buffer are the next request,\n//   answered even after a stop if they hold all of it. With the buffer\n//   empty, the connection closes if the flag is set and goes idle otherwise.\n//\n// Anything else that goes wrong -- EOF mid-request, a read or write error, a\n// malformed head, an oversized body, a deadline passing -- closes this\n// connection without a response, and touches no other.\nasync fn serve(s: Server, conn: TcpStream) {\n    let mut buf = bytes_from_string(\"\")\n    let mut open = true\n    while open {\n        if buf.len() == 0 {\n            match conn.read_timeout(4096, SERVER_TICK_MS).await {\n                Ok(chunk) => { if chunk.len() == 0 { open = false } else { buf = chunk } }\n                Err(e) => {\n                    match e.kind {\n                        TimedOut => { if shutdown_requested() { open = false } }\n                        _ => open = false\n                    }\n                }\n            }\n        } else {\n            let started = Instant::now()\n            let may_read = !shutdown_requested()\n            match read_in_flight(conn, buf, started, may_read).await {\n                Some(f) => {\n                    buf = f.rest\n                    let left = SERVER_REQUEST_MS - started.elapsed().as_millis()\n                    match timeout(\n                        Duration::from_millis(left),\n                        write_response(conn, s.dispatch(f.req)),\n                    ).await {\n                        Ok(w) => {\n                            match w {\n                                Ok(n) => {\n                                    if buf.len() == 0 && shutdown_requested() { open = false }\n                                }\n                                Err(e) => open = false\n                            }\n                        }\n                        Err(_) => open = false\n                    }\n                }\n                None => open = false\n            }\n        }\n    }\n    let _ = conn.close().await\n}\n\n// The request at the front of `first`, reading more from `conn` as needed, or\n// `None` to close the connection. The head is parsed from at most\n// `max_head_bytes` of the buffer, so bytes buffered behind it -- a body, or a\n// pipelined request -- never count against the head\'s own limit. With\n// `may_read` false, for a request taken up after a stop, nothing more is\n// read, and a request not already whole in `first` is `None`.\nasync fn read_in_flight(\n    conn: TcpStream,\n    first: Bytes,\n    started: Instant,\n    may_read: Bool,\n) -> Option<InFlight> {\n    let limits = Limits::default()\n    let mut buf = first\n    let mut head: Option<Request> = None\n    while head.is_none() {\n        let window = head_window(buf, limits.max_head_bytes)\n        match parse_request_head(window, limits) {\n            Ok(maybe) => head = maybe\n            Err(_) => return None\n        }\n        if head.is_none() {\n            // A head still incomplete at `max_head_bytes` never will be.\n            if window.len() >= limits.max_head_bytes { return None }\n            if !may_read { return None }\n            match read_more(conn, 4096, started).await {\n                Some(chunk) => buf = buf.concat(chunk)\n                None => return None\n            }\n        }\n    }\n    let req = match head {\n        Some(r) => r\n        None => return None\n    }\n    // `parse_request_head` returned `Ok(Some(_))` on this same window, so its\n    // offset table has status 0 and its last element is where the body starts.\n    let t = parse_offsets(head_window(buf, limits.max_head_bytes))\n    let body_start = t[t.len() - 1]\n    let want = match content_length_of(req, limits.max_body_bytes) {\n        Ok(n) => n\n        Err(_) => return None\n    }\n    if want > limits.max_body_bytes { return None }\n    let end = body_start + want\n    while buf.len() < end {\n        if !may_read { return None }\n        match read_more(conn, end - buf.len(), started).await {\n            Some(chunk) => buf = buf.concat(chunk)\n            None => return None\n        }\n    }\n    Some(\n        InFlight {\n            req: Request {\n                method: req.method,\n                path: req.path,\n                headers: req.headers,\n                body: buf.slice(body_start, end),\n            },\n            rest: buf.slice(end, buf.len()),\n        },\n    )\n}\n\n// Up to `max` more bytes for the request taken up at `started`, or `None` at\n// EOF, on a read error, or once that request\'s deadline has passed.\nasync fn read_more(conn: TcpStream, max: Int, started: Instant) -> Option<Bytes> {\n    let left = SERVER_REQUEST_MS - started.elapsed().as_millis()\n    if left <= 0 { return None }\n    match conn.read_timeout(max, left).await {\n        Ok(chunk) => {\n            if chunk.len() == 0 { return None }\n            Some(chunk)\n        }\n        Err(_) => None\n    }\n}\n\n// The first `max` bytes of `buf`, or all of it if it is shorter.\nfn head_window(buf: Bytes, max: Int) -> Bytes {\n    if buf.len() <= max { return buf }\n    buf.slice(0, max)\n}\n";
Expand description

std/http/lib.nova.