Module http/server
Minimal HTTP/1.1 server on TcpListener (plans/archive/STD_API_AUDIT.md §7 P1).
One connection at a time, one request per connection (Connection: close
is added to every response) — the shape a CLI, a test fixture or a local
tool needs; it shares its wire code (std/http/wire.yo) with the client,
so request bodies may be Content-Length or chunked.
{ HttpServer, HttpResponse } :: import("std/http");
server := io.await(HttpServer.bind(SocketAddr.loopback(u16(8080)), io), e);
io.await(server.serve(req -> HttpResponse.with_status(StatusCode.OK()).with_body(`hi ${req.path}`), io), e);
serve runs until stop() is called (checked after each connection) —
from another task, or from the handler itself. serve_once handles exactly
one connection, which is what a test wants.
A client's mistake never takes the server down: a request that does not
parse gets 400, one that exceeds max_request_bytes gets 413, one
with broken framing (Content-Length that is not a number, malformed
chunking) gets 400 — and serve goes on to the next connection. An I/O
failure on the accepted socket itself still propagates out of serve.
Stability
unstable — the SHAPE (one connection at a time, one request per
connection, Connection: close on every response) is expected to change
when a connection loop / keep-alive lands; bind, local_addr,
with_max_request_bytes, serve, serve_once, stop and close are
intended to keep their names and meanings.
Types
An HTTP/1.1 server bound to a TcpListener.
A shared ref handle: a handler may call stop() on the server it runs
under. The listener is move-only, so the server holds it through an Rc,
which is what lets close's future borrow it across a suspension.
Fields
| Name | Type | Description |
|---|---|---|
listener | Rc(TcpListener) | |
max_request_bytes | usize | Requests larger than this are answered with |
stopped | bool |
impl(HttpServer, ...)
bind : (HttpServer) fn(addr : SocketAddr, io : Io) -> Impl(Future(HttpServer, IoExn))Bind a listening socket on addr (port 0 picks a free port — read it
back with local_addr()).
Parameters
| Name | Type | Notes |
|---|---|---|
addr | SocketAddr | |
io | Io |
Returns: Impl(Future(HttpServer, IoExn))
local_addr : (HttpServer) fn(self : &HttpServer) -> SocketAddrwith_max_request_bytes : (HttpServer) fn(self : &HttpServer, n : usize) -> HttpServerSet the request-size ceiling (builder pattern; 0 = unlimited).
Parameters
| Name | Type | Notes |
|---|---|---|
self | HttpServer | |
n | usize |
Returns: HttpServer
_accept_request : (HttpServer) fn(self : &HttpServer, io : Io) -> Impl(Future(Tuple(0 : TcpStream, 1 : Result(HttpRequest, HttpResponse)), IoExn))Accept ONE connection and read one request from it. Returns the
connection and either the request or the response it gets without
reaching the handler: a request that does not parse gets a 400 Bad Request whose body names the defect, and a framing defect (oversize,
unreadable Content-Length, malformed chunking) is answered the same way
(413 for oversize) instead of being thrown out of the server
(issues/fixed/one-malformed-request-killed-http-server-serve.md).
Parameters
| Name | Type | Notes |
|---|---|---|
self | HttpServer | |
io | Io |
Returns: Impl(Future(Tuple(0 : TcpStream, 1 : Result(HttpRequest, HttpResponse)), IoExn))
serve_once : (HttpServer) fn(self : &HttpServer, handler : Impl(Fn(HttpRequest) -> HttpResponse), io : Io) -> Impl(Future(unit, IoExn))Accept ONE connection, read one request, run handler, write the
response and close. A request that does not parse, or whose framing is
broken, is answered without calling handler (_accept_request).
The returned future owns handler (sink), so it can be spawned: a
closure argument moves its captures into it (plans/VALUES_BY_DEFAULT.md
decision 22). A future that only borrowed the handler would be
second-class, and an io.spawn of it is E0909
(issues/fixed/2026-10-15-http-server-captures-its-borrowed-handler-into-the-connection-future.md).
Parameters
| Name | Type | Notes |
|---|---|---|
self | HttpServer | |
handler | Impl(Fn(HttpRequest) -> HttpResponse) | |
io | Io |
Returns: Impl(Future(unit, IoExn))
serve : (HttpServer) fn(self : &HttpServer, handler : Impl(Fn(HttpRequest) -> HttpResponse), io : Io) -> Impl(Future(unit, IoExn))Serve connections one after another until stop() has been called
(checked after each connection completes). The future owns handler,
as serve_once's does.
Parameters
| Name | Type | Notes |
|---|---|---|
self | HttpServer | |
handler | Impl(Fn(HttpRequest) -> HttpResponse) | |
io | Io |
Returns: Impl(Future(unit, IoExn))
stop : (HttpServer) fn(self : &HttpServer) -> unitAsk serve to return after the connection it is currently handling.
Parameters
| Name | Type | Notes |
|---|---|---|
self | HttpServer |
Returns: unit
close : (HttpServer) fn(self : &HttpServer, io : Io) -> Impl(Future(unit, IoExn))Close the listening socket.
Parameters
| Name | Type | Notes |
|---|---|---|
self | HttpServer | |
io | Io |
Returns: Impl(Future(unit, IoExn))
Constants
Default ceiling on a request (headers + body): 16 MiB.
Value: 16777216