Server

Server :: # (opaque)

Configure and run an inbound HTTP server.

Server requests deliberately use a different type from outbound http.Request: their bodies are request-scoped streams instead of complete byte lists.

init! = || Ok({
    config: Server.default_config.with_listen({ host: "127.0.0.1", port: 8080 }),
    context: {},
})

respond! = |request, {}| {
    body = request.body().with_limit(64 * 1024).read_all!()?
    response = Response.from_status(200).with_body(body)
    Ok(Server.respond(response))
}

shutdown! = |_, {}| Ok({})

An unhandled semantic error returned by respond! is inspected, logged to stderr with request context, and converted to a 500 response. Unhandled init! and shutdown! errors are logged and produce process exit code 1.

default_config : Config

Safe defaults: loopback-only; finite connection, handler, and handler queue limits; a 1 MiB request limit; one buffered 64 KiB chunk; and bounded graceful shutdown. Exceeding the drain deadline forces process exit without running the shutdown hook, because a request handler may still be using the application context.

with_listen : Config, { host : Str, port : U16 } -> Config

Set the listener host and port. The default is loopback-only on port 8000.

with_limits : Config, { max_connections : U32, max_handlers : U16, max_queued_handlers : U16 } -> Config

Set connection, active-handler, and queued-handler capacity together. Saturated handler queues receive 503 responses.

with_graceful_shutdown : Config, { drain_timeout_ms : U64, hook_timeout_ms : U64 } -> Config

Set the request-drain deadline and final shutdown-hook deadline.

respond : Response -> Outcome

Return a response and keep serving requests.

stop_after : Response -> Outcome

Return a final response, then begin graceful shutdown with exit code 0.

stop_after_with_code : Response, I64 -> Outcome

Return a final response, then begin graceful shutdown with the given exit code.

Config

:= [
    Config(
        {
            listen : { host : Str, port : U16 },
            limits : {
                max_connections : U32,
                max_handlers : U16,
                max_queued_handlers : U16,
            },
            request_bodies : {
                max_bytes : U64,
                chunk_bytes : U32,
                buffered_chunks : U16,
            },
            graceful_shutdown : {
                drain_timeout_ms : U64,
                hook_timeout_ms : U64,
            },
        },
    ),
]

Opaque runtime configuration returned from the application's init! function. Use the builders below so future server settings can be added without invalidating application record construction.

to_host : Config -> {
    host : Str,
    port : U16,
    body_max_bytes : U64,
    body_chunk_bytes : U32,
    body_buffered_chunks : U16,
    drain_timeout_ms : U64,
    hook_timeout_ms : U64,
    max_connections : U32,
    max_handlers : U16,
    max_queued_handlers : U16,
}

Platform ABI conversion hook; not an application API. Applications should use default_config and the with_* builders.

Body

:= [
    Body(
        {
            host_id : U64,
            limit_bytes : U64,
            content_length : [Unknown, Known(U64)],
        },
    ),
]

A request-scoped inbound body. The host expires this capability when the request handler returns, and permits only one active reader at a time.

to_inspect : Body -> Str

Render a body without exposing its request-scoped host identifier.

from_host : U64, U64, [Unknown, Known(U64)] -> Body

Platform ABI conversion hook; not an application API.

limit : Body -> U64

The maximum number of bytes this request body may deliver.

content_length : Body -> [Unknown, Known(U64)]

The declared Content-Length, when the request supplied one. The stream still enforces its byte limit independently of this untrusted value.

with_limit : Body, U64 -> Body

Return the same request stream with a stricter total byte limit. Limits may only be narrowed, never widened beyond the server configuration.

read! : Body => Try(Read, [RequestBodyErr(Err)])

Read the next bounded chunk. End is stable and may be observed more than once. Concurrent reads of one body return ConcurrentRead.

read_all! : Body => Try(List(U8), [RequestBodyErr(Err)])

Read all remaining bytes while enforcing this body's current limit. Prefer read! for large or incrementally processed payloads.

Request

:= {
    method : Method,
    headers : List(Header),
    target : Str,
    body : Body,
}

An inbound server request. Its body is always streaming; use Body.read_all! only when a bounded complete body is appropriate.

method : Request -> Method

Return the request method.

headers : Request -> List(Header)

Return the request headers in received order.

target : Request -> Str

Return the request target, including any query string.

body : Request -> Body

Return the request-scoped streaming body capability.

from_host : Method, List(Header), Str, Body -> Request

Platform ABI conversion hook; not an application API.

Outcome

:= [
    Respond(Response),
    StopAfter({ response : Response, exit_code : I64 }),
]

A successful request outcome. StopAfter sends its response while beginning graceful shutdown; the first shutdown cause wins.

to_host : Outcome -> { response : Response, stop : Bool, exit_code : I64 }

Platform ABI conversion hook; not an application API.

ShutdownReason : [
    ApplicationRequested,
    Interrupt,
    Terminate,
    StartupFailed(Str),
    RuntimeFailed(Str),
]

Why the server is invoking the application's final shutdown hook.