std.net — HTTP Server
Create a server, register routes, and listen. Each route maps a path to a handler(req) function. The engine calls handlers on background fibers when requests arrive.
Minimal REST API
var srv = std.net.http_server()
srv.routes([{
path: "/greet", handler: fn(req) {
var who = "world"
if (req.query.name != nil) { who = req.query.name } // /greet?name=Bob
return "hello, " + who // plain string → 200 text/plain
}
}, {
path: "/json", handler: fn(req) {
return {
status: std.net.http_codes.ok,
content_type: "application/json",
body: std.json.stringify({result: 42}),
}
}
}, {
path: "*", handler: fn(req) { // catch-all
return {
status: std.net.http_codes.not_found,
content_type: "application/json",
body: std.json.stringify({error: "nope"}),
}
}
}])
srv.listen(8200)
print("listening on", srv.port()) // actual port (useful when 0)
std.async.sleep(5000) // keep serving
srv.stop()
Handler input — the req object
| Field | Description |
|---|---|
req.method | "GET", "POST", … |
req.path | "/greet" (no query string) |
req.query | Parsed query object, e.g. {name: "Bob"} for ?name=Bob |
req.headers | Request headers object |
req.raw_headers | Raw header block string |
req.body | Request body string |
Handler output — string or response object
Return a plain string for an HTTP 200 with text/plain:
handler: fn(req) { return "hello" } // 200 text/plain
Or return a response object for full control:
| Key | Purpose | Default |
|---|---|---|
status | HTTP status code | 200 |
content_type | Content-Type header | text/plain or guessed for file |
headers | Extra response headers object | {} |
body | Response body string | "" |
file | Path to a file to serve | — |
File serving example:
// inside a handler:
return {file: "./public/index.html"} // content-type guessed from extension
Routing
- First matching route wins — order your
routes([...])array from specific to generic. path: "*"is the catch-all; place it last.- Registering
routesreplaces the previous table — call it once with the full array.
:::tip Parser note
Avoid a trailing comma after a member whose value is a multi-line fn body — write the closing } of the handler directly followed by the object's }, as in the example above. A trailing comma there can trigger a parse edge case.
:::
Lifecycle and errors
srv.listen(8200, "127.0.0.1") // port [, host] — a {cert, key} third argument serves TLS
print(srv.is_running()) // true
print(srv.port()) // actual port
srv.stop() // halt and close
print(srv.last_error()) // most recent background error or null
- Handlers run on background fibers — the main script must
sleep/awaitor the server exits with the script. - Handler exceptions and TLS handshake failures have no caller to throw to — they are stored per-server and readable via
server.last_error()(also echoed to stderr). See Concurrency — Errors. - TLS: pass a
{cert, key}third argument tolisten()(PEM file paths) to servehttps://— requires an OpenSSL build, elseUNSUPPORTED(see Overview). - Use
port: 0to let the OS pick a free port, then sharesrv.port()with clients.
See also
- HTTP Client — request the routes you serve
- URL / DNS
- Overview — TLS note