std::net::http::http_async_clientAsync 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.
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());
}
}
build_getBuild a GET request line + headers for path on host, with the
Connection: close framing this buffered codec relies on (read-to-close).
build_requestBuild 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.
split_addressParse 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.
host_ofReturn the host component of url_str for the Host: header (no port).
default_portDefault TCP port for a URL scheme (https → 443, everything else → 80).
response_over_limitWhether 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.
parse_responseParse 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).
AsyncResponseA 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].
AsyncResponseMethodsMethods on [AsyncResponse] — byte-identical surface to the blocking
Response (status / body / header / content_type).
The HTTP status code (e.g. 200, 404), or -1 on a transport/parse error.
The response body as a string.
Look up a response header by name (case-insensitive). Returns an empty string if the header is not present.
The Content-Type response header (empty string if absent).
CLIENT_MAX_HEADER_BYTESMaximum bytes accepted in the response head (status line + headers). A head
larger than this fails closed (status == -1) rather than buffering unbounded.
CLIENT_MAX_BODY_BYTESMaximum bytes accepted in the response body — also the Content-Length
ceiling. A declared or buffered body larger than this fails closed.
CLIENT_MAX_RESPONSE_BYTESMaximum 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.
CLIENT_MAX_READSMaximum number of read iterations a response loop will accept before failing closed. Bounds an endless-trickle server that never closes the connection.