Default maximum request-body size: 1 MiB.
This is one of the finite production defaults used by
default_config.
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 maximum request-body size: 1 MiB.
This is one of the finite production defaults used by
default_config.
Default maximum chunk delivered by Body.read!:
64 KiB.
Default number of request-body chunks buffered per request.
Default maximum normalized request-target size: 8 KiB.
Default maximum decoded request-header list size: 32 KiB.
Each ordinary field costs its decoded name bytes, value bytes, and the 32-byte per-field overhead defined for HTTP/2 header-list accounting. HTTP/1 uses the same accounting so both protocols expose one contract.
Default maximum number of ordinary request-header fields.
Repeated fields each count once. HTTP/2 pseudo-fields are represented by the request method and target and do not consume this field-count budget.
Default maximum number of request bodies concurrently streaming into host-managed files.
Default deadline for one host-managed request-body sink: 30 seconds.
Default maximum number of concurrently active connections.
Default maximum number of concurrently executing Roc handlers.
Default finite queue capacity for handlers waiting to execute.
Default maximum idle time for completing each request head: 10 seconds.
Default maximum idle time between non-empty request-body chunks: 30 seconds.
Default maximum idle time between requests on a persistent connection: 60 seconds.
Default maximum time an admitted request may wait for a Roc handler: 5 seconds.
Default maximum time without outbound transport progress: 30 seconds.
Default maximum number of host-managed file responses that may be active concurrently. File transfers do not consume Roc handler capacity.
Default chunk size used while streaming files: 64 KiB. A transfer owns at most one queued chunk and one active read buffer, so memory use does not scale with file size.
no_store : CachePolicy
Do not allow caches to store the response.
revalidate : CachePolicy
Allow storage but require revalidation before reuse. This is the conservative default for declared roots.
private_for : U32 -> CachePolicy
Permit private caches to reuse a response for the given number of seconds.
public_for : U32 -> CachePolicy
Permit shared and private caches to reuse a response for the given number of seconds.
Declare a root with the conservative revalidation cache policy. Root identifiers contain 1-64 ASCII letters, digits, '-' or '_'; the complete configuration is validated before listening.
file_root_with_cache : { id : Str, path : Path, cache : CachePolicy } -> FileRoot
Declare a root with an explicit cache policy.
writable_root : { id : Str, path : Path } -> WritableRoot
Declare one writable root. Identifiers use the same 1-64 character syntax as read-only file roots, but live in an independent authority registry.
relative_file : Str -> Try(RelativeFile, [InvalidRelativeFile])
Construct a safe relative child path of at most 4 KiB. Empty components, dot components, dotfiles, separators other than '/', NULs, and Windows drive syntax are rejected so the value has one cross-platform meaning.
inherit_cache : CacheChoice
Inherit the cache policy declared by a file root.
override_cache : CachePolicy -> CacheChoice
Override a root cache policy for one route or response plan.
static_mount : { at : Str, files : FileRoot } -> FileRoute
Declare a public static mount that inherits its root cache policy. Route paths are ASCII absolute URI paths of at most 4 KiB and are validated at startup.
static_mount_with_cache : { at : Str, files : FileRoot, cache : CachePolicy } -> FileRoute
Declare a public static mount with an explicit cache policy.
static_file : { at : Str, files : FileRoot, relative : RelativeFile } -> FileRoute
Declare one exact public file route that inherits its root cache policy.
static_file_with_cache : { at : Str, files : FileRoot, relative : RelativeFile, cache : CachePolicy } -> FileRoute
Declare one exact public file route with an explicit cache policy.
liveness_route : Str -> LivenessRoute
Declare a native liveness route. Paths are validated with all other native routes atomically before the listener is bound.
readiness_route : { at : Str, readiness : Readiness } -> ReadinessRoute
Declare a native readiness route backed by the supplied gate.
no_native_routes : NativeRoutes
No host-native routes.
no_log_target : LogTarget
Do not include a request target in access logs.
path_without_query : LogTarget
Include the bounded parsed path, never its query string, in access logs.
no_access_log : AccessLog
Disable access logging. This is the default.
json_lines_access_log : { target : LogTarget, max_buffered_events : U16 } -> AccessLog
Write bounded structured request-completion events as JSON Lines to standard error. The buffer capacity must be non-zero.
no_metrics : Metrics
Disable the native metrics exporter. This is the default.
open_metrics : { at : Str } -> Metrics
Expose fixed-cardinality host metrics on one native exact path.
The path must satisfy the same startup validation as native file routes
and must not overlap one. GET and HEAD are supported; the response uses
the OpenMetrics content type and Cache-Control: no-store.
default_config : Config
Safe defaults: loopback-only; finite connection, handler, handler queue, request-target, request-header, and request-body limits; one buffered 64 KiB body chunk; and bounded graceful shutdown.
Request metadata limits are exact and checked before native route selection or Roc. Target overflow returns 414 and header byte/count overflow returns 431 whenever the protocol parser can safely construct a response. HTTP/1 closes after a parser-level overflow. An initial HTTP/2 request beyond the advertised hard decoding envelope receives a header-only 431; an overflow that cannot safely receive a response resets only the affected stream. Zero values, target limits above 65,534 bytes, header limits above 1 MiB, and field limits above 1,024 fail startup before the listener is bound.
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_request_body_limits : Config, { max_bytes : U64, chunk_bytes : U32, buffered_chunks : U16 } -> Config
Set all finite inbound request-body limits.
with_request_body_limit : Config, U64 -> Config
Set only the maximum total bytes accepted for each request body.
with_timeouts : Config, { header_ms : U64, body_idle_ms : U64, keep_alive_idle_ms : U64, handler_queue_ms : U64, response_idle_ms : U64 } -> Config
Set the complete inbound and outbound transport timeout policy. Every value is milliseconds in the inclusive range 1 through 86_400_000; zero is invalid and startup fails before listening.
header_ms is an idle deadline while completing a request head and
resets whenever head bytes arrive. body_idle_ms begins when the host
waits for the next body frame and resets only after non-empty body data
arrives. keep_alive_idle_ms begins after a response has completed and
bounds the gap before the next request. handler_queue_ms begins after a
request takes a queue slot and ends before Roc execution begins.
response_idle_ms begins when response transmission starts and resets
whenever the socket or HTTP/2 stream flow-control window makes progress.
with_request_metadata_limits : Config, { max_target_bytes : U32, max_header_bytes : U32, max_header_fields : U16 } -> Config
Set the exact, finite request-target and decoded request-header budgets. Invalid values fail startup atomically before the listener is bound.
with_graceful_shutdown : Config, { drain_timeout_ms : U64, hook_timeout_ms : U64 } -> Config
Set the request-drain deadline and final shutdown-hook deadline.
with_file_roots : Config, List(FileRoot) -> Config
Replace the complete set of startup-declared file roots.
with_writable_roots : Config, List(WritableRoot) -> Config
Replace the complete set of startup-declared writable roots. Writable authority is never inferred from read-only file roots.
with_native_routes : Config, NativeRoutes -> Config
Replace the complete immutable host-native route table.
with_file_transfer_limits : Config, { max_concurrent : U16, chunk_bytes : U32 } -> Config
Set the active-transfer bound and streaming chunk size for host-managed file responses. Saturation returns 503 without queueing. Each transfer owns at most one queued chunk and one active read buffer.
with_body_sink_limits : Config, { max_concurrent : U16, timeout_ms : U64 } -> Config
Set the active staging-file bound and whole-operation deadline for host-managed request-body sinks. Saturation is typed and does not queue.
with_access_log : Config, AccessLog -> Config
Configure host-owned structured request-completion logging.
with_metrics : Config, Metrics -> Config
Configure the host-owned fixed-cardinality metrics exporter.
respond : Response -> Outcome
Return a response and keep serving requests.
file_response : { files : FileRoot, relative : RelativeFile } -> Outcome
Ask the host to stream one authorized file inline, inheriting the root's cache policy. The plan can only name a startup-declared root and a validated relative child path.
file_response_with : {
files : FileRoot,
relative : RelativeFile,
disposition : [Inline, Attachment(Str)],
cache : CacheChoice,
} -> Outcome
Ask the host to stream one authorized file with explicit disposition/cache options.
Render a host-managed file inline.
attachment : Str -> [Inline, Attachment(Str)]
Render a host-managed file as an attachment. The host safely encodes the supplied filename and prevents response-header injection.
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.
:= [NoStore, Revalidate, PrivateFor(U32), PublicFor(U32)]
A small, typed cache policy for host-managed file responses.
:= [
FileRoot({ id : Str, path : Path, cache : CachePolicy }),
]
An immutable descriptor for one startup-declared filesystem root. Its identifier is the only value sent in a response plan; the host rejects plans whose identifier was not activated by the returned Config.
:= [
WritableRoot({ id : Str, path : Path }),
]
A startup-declared writable filesystem authority. This is deliberately a different type from FileRoot: permission to serve files never grants permission to create them.
:= [RelativeFile(Str)]
A validated relative child path used by exact routes and authorized file plans. The host repeats this validation at the ABI boundary.
:= [Inherit, Override(CachePolicy)]
Override a root's cache policy for one native route or response plan, or inherit the root policy.
:= [
FileRoute(
{
at : Str,
files : FileRoot,
kind : U8,
relative : Str,
cache : CacheChoice,
},
),
]
One startup-declared host-native file route. Static mounts own an exact prefix on segment boundaries. Static files own one exact URI path.
ReadinessState : [NotReady, Ready]
The complete readiness state. There are deliberately no names, reasons, dependency callbacks, or intermediate states.
:= { host : Readiness }
A bounded host-owned readiness gate. It is safe to retain in immutable
context and update from concurrent handlers. Final Roc ARC release closes
the capability; graceful drain permanently changes it to NotReady before
shutdown! runs.
Create one readiness gate with an explicit initial state. The host has finite capacity and reports exhaustion instead of growing a registry.
Atomically replace the readiness state. Once graceful drain begins, every update returns ServerStopping and the state remains NotReady.
to_inspect : Readiness -> Str
Render without exposing the host lifecycle token.
to_host : Readiness -> Readiness
Platform ABI conversion hook; not an application API.
:= [LivenessRoute(Str)]
One native exact route whose response proves only that the listener and
HTTP machinery can serve it. init! and complete route validation finish
before the listener is bound, so this route also serves as a deployment
startup probe once it becomes reachable; there is no separate mutable
startup state.
:= [ReadinessRoute({ at : Str, readiness : Readiness })]
One native exact route backed by a typed readiness gate.
NativeRoutes : {
files : List(FileRoute),
liveness : List(LivenessRoute),
readiness : List(ReadinessRoute),
}
The immutable startup route topology. Exact route duplicates are rejected; exact routes take precedence over more general file prefixes.
:= [NoTarget, PathWithoutQuery]
Privacy policy for the request target in structured access logs.
Query strings are never logged. PathWithoutQuery records only the
parsed URI path, truncated by the host to a finite byte limit.
:= [
AccessLogOff,
JsonLines({ target : LogTarget, max_buffered_events : U16 }),
]
Host-owned access logging configuration.
JSON Lines are written to standard error by a dedicated host thread. Request transport never waits for that thread: terminal events enter a finite queue, and overflow is counted by the metrics exporter. Shutdown gives the queue one second to drain; a blocked standard-error sink is detached so it cannot defeat the server's finite shutdown deadlines.
:= [MetricsOff, OpenMetrics({ at : Str })]
Host-owned fixed-cardinality metrics export configuration.
:= [
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,
},
timeouts : {
header_ms : U64,
body_idle_ms : U64,
keep_alive_idle_ms : U64,
handler_queue_ms : U64,
response_idle_ms : U64,
},
request_metadata : {
max_target_bytes : U32,
max_header_bytes : U32,
max_header_fields : U16,
},
graceful_shutdown : {
drain_timeout_ms : U64,
hook_timeout_ms : U64,
},
file_roots : List(FileRoot),
writable_roots : List(WritableRoot),
native_routes : NativeRoutes,
file_transfers : {
max_concurrent : U16,
chunk_bytes : U32,
},
body_sinks : {
max_concurrent : U16,
timeout_ms : U64,
},
operations : {
access_log : AccessLog,
metrics : Metrics,
},
},
),
]
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,
header_timeout_ms : U64,
body_idle_timeout_ms : U64,
keep_alive_idle_timeout_ms : U64,
handler_queue_timeout_ms : U64,
response_idle_timeout_ms : U64,
request_target_max_bytes : U32,
request_header_max_bytes : U32,
request_header_max_fields : U16,
drain_timeout_ms : U64,
hook_timeout_ms : U64,
max_connections : U32,
max_handlers : U16,
max_queued_handlers : U16,
file_roots : List(
{
id : Str,
path_tag : U8,
path_utf8 : Str,
path_unix_bytes : List(U8),
path_windows_u16s : List(U16),
cache_tag : U8,
cache_max_age_seconds : U32,
},
),
writable_roots : List(
{
id : Str,
path_tag : U8,
path_utf8 : Str,
path_unix_bytes : List(U8),
path_windows_u16s : List(U16),
},
),
native_file_routes : List(
{
at : Str,
root_id : Str,
kind : U8,
relative : Str,
cache_override : Bool,
cache_tag : U8,
cache_max_age_seconds : U32,
},
),
liveness_routes : List(Str),
readiness_routes : List({ at : Str, readiness : Readiness }),
file_max_concurrent : U16,
file_chunk_bytes : U32,
body_sink_max_concurrent : U16,
body_sink_timeout_ms : U64,
access_log_enabled : Bool,
access_log_target : U8,
access_log_buffer_events : U16,
metrics_enabled : Bool,
metrics_path : Str,
}
Platform ABI conversion hook; not an application API. Applications
should use default_config and the with_*
builders.
:= [
Body(
{
host : RequestBody,
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.
digest_to_host : Digest -> U8
to_inspect : Body -> Str
Render a body without exposing its request-scoped host identifier.
Platform ABI conversion hook; not an application API.
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 the next bounded chunk. End is stable and may be observed more than once. Concurrent reads of one body return ConcurrentRead.
fold_chunks! : Body, state, (state, List(U8) => Try(state, err)) => Try(state, [ChunkReadErr({ err : Err, state : state }), ChunkStepErr(err)])
Read every remaining chunk sequentially and thread request-local state through an effectful step function. The first step error stops reading; returning from the handler then cancels any unread request bytes.
Read all remaining bytes while enforcing this body's current limit. Prefer read! for large or incrementally processed payloads.
write_file! : Body, {
root : WritableRoot,
relative : RelativeFile,
digest : Digest,
} => Try(WriteFileSuccess, [BodySinkErr(WriteFileErr)])
Stream all remaining body bytes into a securely created staging file, then publish it atomically at a validated child path. Parent directories must already exist. Publication is always CreateNew and never overwrites an existing destination. Atomic publication describes name visibility, not crash durability or fsync.
:= [Authority({ host : Str, port : [Absent, Present(U16)] })]
A validated HTTP authority. host is an ASCII URI host: registered names
and IPv4 addresses are unbracketed, while IPv6 and IPvFuture literals keep
their brackets. port is parsed as a U16, so applications never need to
split an IPv6 authority on :.
Authority values remain untrusted client input. They are not a canonical
public origin and do not interpret Forwarded or X-Forwarded-*.
:= [
Resource(
{
raw_path : Str,
raw_query : [Absent, Present(Str)],
},
),
Authority(Authority),
Asterisk,
]
A protocol-neutral parsed request target.
Resource paths and queries remain percent-encoded. Absent query differs
from Present(""), preserving the distinction between /path and
/path?. Origin-form and absolute-form requests produce the same Resource
shape. CONNECT authority-form and OPTIONS * remain distinct, so neither
can accidentally enter ordinary resource routing.
:= {
method : Method,
headers : List(Header),
target : Target,
authority : [Absent, Present(Authority)],
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.
Return the request headers in received order.
target : Request -> Target
Return the validated, protocol-neutral request target.
authority : Request -> [Absent, Present(Authority)]
Return the effective request authority, when present.
HTTP/1.1 requires exactly one valid Host field. Absolute-form and
CONNECT authority-form take precedence over that field. HTTP/2 uses
:authority, falls back to Host when absent, and rejects disagreement
when both are present. A present but empty Host field represents
Absent, as required when the target authority is undefined.
body : Request -> Body
Return the request-scoped streaming body capability.
Platform ABI conversion hook; not an application API.
:= [
Respond(Response),
ServeFile(
{
files : FileRoot,
relative : RelativeFile,
disposition : [Inline, Attachment(Str)],
cache : CacheChoice,
},
),
StopAfter({ response : Response, exit_code : I64 }),
]
A successful request outcome. Ordinary responses have one host-owned framing contract across HTTP/1.1 and HTTP/2. Header names and values must be valid; connection-specific fields and transfer coding are rejected. Content-Length is optional, but when present every value must agree with the complete returned body before host content coding. The host emits one canonical length for the bytes it will transmit.
HEAD transmits no body but reports the returned representation's length. 204 and 304 require an empty body and no Content-Length; 205 also requires an empty body. Informational responses and successful CONNECT responses are unsupported. An invalid response is logged and replaced with a bounded 500 before any part of it is transmitted.
StopAfter applies the same validation, sends the resulting response while beginning graceful shutdown, and preserves the first shutdown cause.
to_host : Outcome -> {
kind : U8,
response : Response,
stop : Bool,
exit_code : I64,
file_root_id : Str,
file_relative : Str,
file_disposition : U8,
file_download_name : Str,
file_cache_override : Bool,
file_cache_tag : U8,
file_cache_max_age_seconds : U32,
}
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.