std.http
Client and server in one module.
Client
import std.http
r = http.get(url)
print(r.status)
print(r.body)
print(r.headers)
http.post(url, body, headers)
data = http.get(url).body_bytes # bytes, for binary
Server
fn handler(req) {
if req.path == "/" { http.text("hello") } else { http.not_found() }
}
http.serve(8080, handler)
serve blocks. A handler takes a request map and returns a response.
Responses
http.text(s)
http.html(body)
http.json(value)
http.response(status, body, headers)
http.not_found()
Or build one as a map - { status, headers, body } - which is all a response is.
Requests
A request map carries method, path, params, headers, body, body_bytes, form, files. http.form(req) parses a submitted form and http.files(req) gives uploads with filename and content_type.
Routing
serve takes one handler. For routes, :params, middleware and static files, use std.web, which is a router over this:
import std.web
http.serve(8080, web.router(routes))
Concurrency
Handlers run on a worker pool (ECKO_HTTP_WORKERS, default 8), and each handler gets a snapshot of captured state - the usual share-nothing model. To share across requests, use a cell:
hits = cell(0)
fn handler(req) {
n = cell_update(hits, fn(v) v + 1)
http.text("request #{n}")
}
Limits
ECKO_HTTP_MAX_BODY (10 MiB) bounds request bodies and ECKO_HTTP_REQUEST_TIMEOUT_MS bounds a handler. Both matter on a public port: an unbounded body is a memory exhaustion, and an unbounded handler is a worker held forever.
Streaming and WebSockets
http.stream sends a body fed by a channel, which is how you push tokens as they arrive - see Streaming responses & SSE. For a WebSocket upgrade, see std.ws.
TLS
Client requests to https:// verify certificates. The Linux builds link OpenSSL statically, so there is nothing to install.
Errors
Client failures raise { kind: "net", url, message }. Wrap calls to anything you do not control, and consider retry for transient failure:
body = retry(3, fn() http.get(url).body)