Module std::net::http::http_async_client

Async HTTP/1.1 client codec — idiomatic Hew over a suspending net.Connection.

This is the await-suspended sibling of std::net::http::http_client (which uses a blocking native engine and cannot suspend an actor). The codec here is pure Hew: an actor handler (or any execution-context caller) connects with net.connect, writes an HTTP/1.1 request built by [build_request], then drives an await conn.read() loop that suspends the worker until the server replies — exactly the NEW-1 escapee shape. The accumulated bytes are parsed by [parse_response] into an [AsyncResponse] exposing the same status() / body() / header() / content_type() accessor surface as the blocking client's Response.

Scope (v0.5.0): HTTP/1.1 only, fully-buffered bodies, one connection per request (Connection: close, read-to-close framing). Chunked transfer + streaming bodies are NEW-7; HTTPS is BUG-NET-3; connection pooling is v0.5.1.

Example (the await-suspended round trip, inline in a handler)

import std::net;
import std::net::http::http_async_client;
import std::string;

actor Fetcher {
    let addr: string;   // "127.0.0.1:8080"
    let path: string;   // "/hello"
    let host: string;   // "127.0.0.1"
    receive fn go(unused: i64) {
        let conn = net.connect(addr);
        conn.write_string(http_async_client.build_get(host, path));
        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_client.response_over_limit(raw) {   // F2: fail closed
                    done = true;
                }
            }
            if reads > http_async_client.CLIENT_MAX_READS {       // F2: read ceiling
                done = true;
            }
        }
        let _ = conn.close();
        let resp = http_async_client.parse_response(raw);
        println(resp.body());
    }
}

Contents

Functions

Function build_get

pub fn build_get(host: string, path: string) -> string

Build a GET request line + headers for path on host, with the Connection: close framing this buffered codec relies on (read-to-close).

Function build_request

pub fn build_request(method: string, host: string, path: string, body: string, content_type: string) -> string

Build an HTTP/1.1 request: the request line, Host, Connection: close, and — for a non-empty body — Content-Type + Content-Length, then the body. path is normalised to / when empty.

FAILS CLOSED on header smuggling (F4): a method, host, path, or content_type carrying a \r/\n or other control character would inject extra headers (or split the request line), so any such value is rejected and an empty string is returned — the caller writes nothing and the request is never sent with an attacker-controlled header.

Function split_address

pub fn split_address(url_str: string) -> (string, string)

Parse url_str into a (host:port, path) pair ready for net.connect and [build_request]. Plaintext HTTP/1.1 only (HTTPS is BUG-NET-3): a scheme prefix (http://) is stripped, the authority is split into host + optional port (defaulting to 80, or 443 for an https scheme so a caller can detect it), and the path defaults to /. Parsed with string ops — no dependency on the opaque url engine.

Function host_of

pub fn host_of(url_str: string) -> string

Return the host component of url_str for the Host: header (no port).

Function default_port

pub fn default_port(scheme: string) -> i64

Default TCP port for a URL scheme (https → 443, everything else → 80).

Function response_over_limit

pub fn response_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 response read loop checks this each iteration so a malicious server cannot OOM the client with an unbounded head/body or an enormous Content-Length.

Function parse_response

pub fn parse_response(raw: string) -> AsyncResponse

Parse the raw bytes read off a connection into an [AsyncResponse]. The header/body boundary is the first CRLF-CRLF; the status code is parsed and VALIDATED from the status line. A response is rejected with code == -1 (the transport-failure convention) when it is empty, exceeds a size bound (F2/SF1), is missing the CRLF-CRLF header terminator (SF2), or has a status line that is not a valid HTTP/1.x version + 3-digit 100-599 status (SF2).

Types

Struct AsyncResponse

A fully-buffered HTTP/1.1 response parsed from the raw bytes read off a suspending connection. Exposes the same accessor surface as the blocking client's Response so downstream code is agnostic to which path produced it.

code is the parsed status code (-1 on a malformed/empty response — the transport-failure convention the blocking client also uses). body_text is the response body. header_section is the raw header block (the lines between the status line and the blank line, CRLF-separated), parsed on demand by [AsyncResponse::header].

Fields

code: i64
body_text: string
header_section: string

Traits

Trait AsyncResponseMethods

Methods on [AsyncResponse] — byte-identical surface to the blocking Response (status / body / header / content_type).

Methods

fn status(self: Self) -> i64

The HTTP status code (e.g. 200, 404), or -1 on a transport/parse error.

fn body(self: Self) -> string

The response body as a string.

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

Look up a response header by name (case-insensitive). Returns an empty string if the header is not present.

fn content_type(self: Self) -> string

The Content-Type response header (empty string if absent).

Constants

Constant CLIENT_MAX_HEADER_BYTES

pub const CLIENT_MAX_HEADER_BYTES: i64 = 65536

Maximum bytes accepted in the response head (status line + headers). A head larger than this fails closed (status == -1) rather than buffering unbounded.

Constant CLIENT_MAX_BODY_BYTES

pub const CLIENT_MAX_BODY_BYTES: i64 = 16777216

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

Constant CLIENT_MAX_RESPONSE_BYTES

pub const CLIENT_MAX_RESPONSE_BYTES: i64 = 16842752

Maximum total response bytes buffered before the read loop fails closed (CLIENT_MAX_HEADER_BYTES + CLIENT_MAX_BODY_BYTES). Bounds the accumulator regardless of framing so a malicious server cannot OOM the client.

Constant CLIENT_MAX_READS

pub const CLIENT_MAX_READS: i64 = 8192

Maximum number of read iterations a response loop will accept before failing closed. Bounds an endless-trickle server that never closes the connection.