Module std::net::http::http_async_server

HTTP/1.1 codec over ordinary std.net calls.

Call conn.recv() directly; pending reads can suspend the caller. Use fork for concurrent work and await for the resulting task. The codec buffers bodies and uses one connection per request; HTTPS and connection pooling are not provided here.

Example

import std.net;
import std.net.http.http_async_server;
import std.string;
import std.encoding.utf8;

actor Server {
    let addr: string;   // "127.0.0.1:8080"
    receive fn serve(unused: i64) {
        let listener = match net.listen(addr) { .Ok(value) => value, .Err(error) => panic("network operation failed"), };
        let conn = listener.accept();               // suspends on readiness
        var raw = "";
        var reads = 0;
        var done = false;
        while !done {
            let chunk = match conn.recv() {
                .Some(data) => utf8.decode(data) handle error {
                    println(f"invalid UTF-8: {error}");
                    return;
                },
                .None => "",
            };    // 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.send(reply.to_bytes()) handle error {
            println(f"response send failed: {error}");
            return;
        };
        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.