Module std::net::http::http_async_server

Async HTTP/1.1 server codec — idiomatic Hew over a suspending listener+conn.

The await-suspended sibling of the blocking std::net::http server (which is backed by a native engine and cannot suspend an actor). The codec is pure Hew layered on NEW-2's await listener.accept() carrier and NEW-1's await conn.read(): an acceptor handler creates a listener with net.listen, awaits a connection, drives an await conn.read() loop until the request is fully buffered ([request_complete]), parses it ([parse_request]), and replies by writing the bytes from [response_text] / [response_json] / [build_response] over the connection.

Scope (v0.5.0): HTTP/1.1 only, fully-buffered request/response bodies, one connection handled per accept (the OTP acceptor-per-conn shape). The Listener/Connection are handler LOCALs, never actor state (the supervisor-restart clone gate rejects opaque handles in state). Streaming bodies are NEW-7; HTTPS is BUG-NET-3.

Example (the await-accept + await-read request loop, inline in a handler)

import std::net;
import std::net::http::http_async_server;
import std::string;

actor Server {
    let addr: string;   // "127.0.0.1:8080"
    receive fn serve(unused: i64) {
        let listener = net.listen(addr);
        let conn = await listener.accept();         // suspends on readiness
        var raw = "";
        var reads = 0;
        var done = false;
        while !done {
            let chunk = await conn.read_string();    // suspends the worker
            reads = reads + 1;
            if string.is_empty(chunk) {
                done = true;
            } else {
                raw = raw + chunk;
                if http_async_server.over_limit(raw) {       // F2: fail closed
                    done = true;
                } else if http_async_server.request_complete(raw) {
                    done = true;
                }
            }
            if reads > http_async_server.MAX_READS {         // F2: read ceiling
                done = true;
            }
        }
        let req = http_async_server.parse_request(raw);
        var reply = http_async_server.response_text(req.reject_status(), "rejected");
        if req.is_valid() {                                  // F2/F3: fail closed
            reply = match req.path() {
                "/hello" => http_async_server.response_text(200, "hi from hew"),
                _        => http_async_server.response_text(404, "nope"),
            };
        }
        conn.write_string(reply);
        let _ = conn.close();
        let _ = listener.close();
    }
}

Contents

Functions

Function request_complete

pub fn request_complete(raw: string) -> bool

Whether raw holds a complete HTTP/1.1 request: the header terminator (CRLF-CRLF) is present AND, when a Content-Length header is declared, the buffered body is at least that long. Drives the acceptor's read loop so it stops reading as soon as the request is fully buffered (rather than waiting for the peer to close).

Function over_limit

pub fn over_limit(raw: string) -> bool

Whether raw has exceeded a size bound and the read loop must STOP and fail closed (F2). True when the buffered bytes exceed the total ceiling, the head exceeds the header ceiling (even before a terminator is seen), or the declared Content-Length exceeds the body ceiling. The acceptor's read loop checks this before [request_complete] so a malicious peer cannot OOM the worker by streaming an unbounded head/body or declaring an enormous Content-Length.

Function parse_request

pub fn parse_request(raw: string) -> AsyncRequest

Parse the buffered bytes into an [AsyncRequest]. The request line is the first CRLF-terminated line (GET /path HTTP/1.1); the method and path are its first two space-separated tokens. The body is everything after the first CRLF-CRLF. The framing is validated and the request FAILS CLOSED — see [AsyncRequest::reject_status] — on a malformed request line, an unsupported Transfer-Encoding, a duplicate/conflicting/non-numeric Content-Length, or any size bound (F2/F3); the acceptor replies with that status and closes.

Function response_text

pub fn response_text(status: i64, body: string) -> string

Build a plain-text HTTP/1.1 response (Content-Type: text/plain).

Function response_json

pub fn response_json(status: i64, json: string) -> string

Build a JSON HTTP/1.1 response (Content-Type: application/json).

Function build_response

pub fn build_response(status: i64, content_type: string, body: string) -> string

Build a full HTTP/1.1 response: the status line, Content-Type, Content-Length (derived from body), Connection: close, the blank line, then the body. The status text is a small well-known mapping (anything unknown is labelled Status).

FAILS CLOSED on header smuggling (F4): a content_type carrying a \r/\n or other control character would inject extra headers into the response, so such a value is rejected and an empty string is returned (the acceptor writes nothing and closes) rather than emitting an attacker-controlled header.

Types

Struct AsyncRequest

A fully-buffered HTTP/1.1 request parsed from the bytes read off an accepted connection. verb is the method (GET/POST/…), target the request path, header_section the raw header block (CRLF-separated lines after the request line), and body_text the request body. reject_code is 0 for a well-formed request, or the HTTP status the codec failed closed with (400 malformed framing, 413 over a size bound).

Fields

verb: string
target: string
header_section: string
body_text: string
reject_code: i64

Traits

Trait AsyncRequestMethods

Methods on [AsyncRequest] — the request-side accessor surface.

Methods

fn method(self: Self) -> string

The HTTP method (e.g. "GET", "POST"). Empty on a malformed request.

fn path(self: Self) -> string

The request path (e.g. "/api/users"). Empty on a malformed request.

fn header(self: Self, name: string) -> string

Look up a request header by name (case-insensitive). Empty if absent.

fn body(self: Self) -> string

The request body as a string.

fn reject_status(self: Self) -> i64

The fail-closed reject status (0 when the request is well-formed, else the HTTP status to reply with: 400 malformed framing, 413 too large).

fn is_valid(self: Self) -> bool

Whether the request passed all framing + size validation.

Constants

Constant MAX_HEADER_BYTES

pub const MAX_HEADER_BYTES: i64 = 65536

Maximum bytes accepted in the request head (request line + headers). A head larger than this fails closed with 413 rather than buffering unbounded.

Constant MAX_BODY_BYTES

pub const MAX_BODY_BYTES: i64 = 8388608

Maximum bytes accepted in the request body — also the Content-Length ceiling. A declared or buffered body larger than this fails closed with 413.

Constant MAX_REQUEST_BYTES

pub const MAX_REQUEST_BYTES: i64 = 8454144

Maximum total request bytes buffered before the read loop fails closed (MAX_HEADER_BYTES + MAX_BODY_BYTES). The single overall cap that bounds the accumulator regardless of framing.

Constant MAX_READS

pub const MAX_READS: i64 = 4096

Maximum number of read iterations a request loop will accept before failing closed. Bounds an endless-trickle peer that never satisfies a framing rule.