Documentation channel: current development main at compiler checkpoint 1d7e15e. The latest tagged release is v0.0.3; pages identify APIs that are not yet released.

HTTP

Release status: buffered requests are available in v0.0.3. Hardened policy, expanded limits, streaming callbacks, and cancellation document current development at compiler checkpoint 1d7e15e and are not yet released.

Strut provides a libcurl-backed outbound HTTP client, a built-in HTTP/HTTPS server, and an RFC 6455 WebSocket server. The current-main implementation has focused protocol and resource certification, but it is not presented as production-grade or as internet-scale soak evidence.

HTTP APIs are selected automatically when referenced; do not add include <http>;. Native client builds require libcurl 7.64.0 or newer and dynamic final linking.

Buffered client (P2)

function main() -> int : HttpError {
    response := http_get("https://example.com/api/status");
    print(response.status);
    print(response.body);
    return 0;
}

http_get(url), http_get_ca(url, ca_file), http_get_json(url), http_request(method, url, options?), http_get_async(url), and http_request_async(method, url, options) use one bounded request engine. A buffered http_response has status, body, lowercase single-valued headers, and json(). HTTP 4xx and 5xx statuses are responses, not checked errors.

Request options

response := http_request("POST", "https://example.com/api/users", {
    "headers": {"Authorization": "Bearer token"},
    "body": {"name": "Ada"},
    "timeout_ms": 5000,
    "follow_redirects": false,
    "max_response_body_bytes": 1048576
});

The strict options object accepts headers, body, timeout_ms, follow_redirects, max_redirects, max_request_body_bytes, max_response_body_bytes, max_response_header_bytes, max_response_header_count, and ca_file. Streaming uploads additionally accept request_body_length. Unknown keys, wrong types, and invalid ranges raise HttpError.

Defaults are a 30-second total operation deadline, 10 redirects, 16 MiB for each request and response body, 64 KiB for cumulative request and response headers, and 100 request or response fields. The deadline covers name resolution, connection, TLS, redirects, and response receipt. Request methods and metadata are validated; Host, Connection, Content-Length, and Transfer-Encoding are transport-owned. Explicit empty bodies remain distinct from absent bodies and embedded NUL bytes retain their length. Malformed response metadata, case-insensitive duplicate final fields, and trailers fail because the public header map is single-valued.

Streaming client (P3)

http_request_stream and http_request_stream_async take a nullable upload producer and download consumer, and return http_response_head with the final status and lowercase headers. Add a final cancellation_token argument to either form when cancellation is required.

function download(string url) -> int_64 : HttpError {
    int_64 initial := 0;
    total := new(initial);
    head := http_request_stream("GET", url,
        {"max_response_body_bytes": 67108864}, null, (bytes chunk) => {
            *total = *total + chunk.length();
            return true;
        });
    if (head.status != 200) { return 0; }
    return *total;
}
function main() -> int {
    return 0;
}

This marked example is compile-only: it demonstrates the callback types without naming a live endpoint. Upload callbacks receive a positive maximum and must return no more than that many owned bytes; empty bytes signal EOF. Download callbacks receive owned binary chunks and return true to continue or false to stop successfully after that prefix. Callbacks are serial and synchronous, providing the backpressure boundary without a body-sized queue. A null upload sends no streamed body; a null download discards response bytes.

request_body_length declares an exact upload length; early EOF fails. Omitting it selects unknown-length framing. body and an upload callback are mutually exclusive. Only final-response bytes reach the consumer; informational and followed-redirect bodies are discarded. Content decoding is not enabled, so response limits apply to representation bytes. Metadata arrives only when the call or future completes, and late metadata, trailer, TLS, or transport errors can occur after delivered bytes.

Callbacks run on the calling thread, or on one shared-executor worker for the async form. Async streaming uses blocking libcurl easy handles, not a multi/event-loop reactor. Executor workers and queued jobs are bounded, with saturated or recursively submitted work running inline; this is intended for moderate blocking concurrency rather than very large transfer counts.

Redirect and replay policy

Only absolute HTTP and HTTPS URLs are accepted. Redirects are followed by default, an initial HTTPS request cannot downgrade to HTTP, and only the final response metadata is exposed. Buffered POST changes to GET for 301, 302, and 303; 307 and 308 preserve and replay its method and buffered body. Automatic 301/302/303 handling for custom methods such as PUT, PATCH, or DELETE fails with HttpError code -105 rather than risking a method rewrite.

A streamed producer is one-shot and is never rewound. A streamed POST may change to GET for 301/302/303 without calling the producer again; custom-method 301/302/303 redirects and every 307/308 or other replay request fail with code -105. Authorization and Cookie are suppressed when the origin changes, but arbitrary custom headers may be replayed. Disable redirects for destination-bound metadata.

TLS, proxies, and endpoints

Certificate-chain and hostname verification are always enabled; there is no insecure convenience mode. http_get_ca or the HTTPS-only ca_file option selects an explicit PEM CA bundle without disabling verification. The client follows libcurl's environment proxy and no-proxy configuration and keeps origin headers separate from proxy headers. There is no public explicit proxy option.

Client certificates and HTTP over Unix-domain sockets are not public client features. The server likewise exposes TCP host/port listeners rather than Unix sockets, and listen_tls does not expose mutual-TLS/client-certificate validation. These limitations apply equally to buffered and streaming calls.

Errors and trust boundaries

Configuration, policy, limit, callback, cancellation, and transport failures raise HttpError. Native libcurl failures retain their numeric CURLcode; cancellation is -103, callback or producer contract failure is -104, and refused replay is -105. Cancellation is cooperative during resolver calls and application callbacks.

HTTP(S)-only protocol policy is not SSRF protection. Loopback, private, link-local, proxy-routed, and DNS-rebound destinations remain reachable. Applications accepting untrusted URLs should disable redirects, enforce their own destination policy, and validate every Location before issuing another request.

Next