Start with a plain request handler. A handler receives a Request and returns a Response, so the routing and application logic can be tested without opening a port.
use std.http.Response
use std.http_server.Request
fn handle(request: Request) -> Response {
if request.method == "GET" && request.path == "/health" {
std.http_server.json(200, "{\"ok\":true}")
} else if request.method == "POST" && request.path == "/echo" {
std.http_server.text(200, request.body)
} else {
std.http_server.not_found()
}
}
fn main() {
std.http_server.serve("127.0.0.1", 8080, handle)
}Use serve for a straightforward service. It accepts connections and applies the handler to each request.
Test a handler
Create a detached request to exercise a handler directly:
fn test_echo() {
let request = std.http_server.detached_request("POST", "/echo", "", "moon")
let response = handle(request)
std.testing.assert_eq(response.status, 200)
std.testing.assert_eq(response.body, "moon")
}This keeps route and response tests fast. Use a real local connection only for behavior that depends on streaming, disconnects, or HTTP framing.
Read request data
For small JSON or form requests, request.body is the simplest option. Put a reasonable body limit on public services.
For larger inputs:
accept_streamaccepts a request without first collecting its full body;body_readerreads bounded chunks;multipartandnext_partiterate uploaded parts.
Validate content type and size before doing expensive work, and stop reading as soon as the application knows it will reject the request.
Stream a response
Use respond_stream when the response should not be held in memory all at once. Write bounded chunks and finish the response explicitly:
let stream = std.http_server.respond_stream(request, 200, headers)
std.http_server.write_stream(stream, first_chunk)
std.http_server.write_stream(stream, second_chunk)
std.http_server.finish_stream(stream)serve_file handles files and range requests. Prefer it over reading a large file into a string first.
Own the service loop
Use listen and accept when the application needs to decide how each connection is scheduled:
async fn serve_one(request: std.http_server.Request) -> int {
let response = handle(request)
std.http_server.respond(request, response)
0
}
async fn main() {
let listener = std.http_server.listen("127.0.0.1", 8080)
while true {
let request = std.http_server.accept(listener)
spawn(serve_one(request))
}
}Close the listener during shutdown so a waiting accept can finish. Add timeouts and request-size limits that match the service rather than relying on unbounded reads.
TLS and routing
TLS-enabled moon installations provide serve_tls and listen_tls. A reverse proxy is also a good place to terminate TLS when it already owns certificates and deployment routing.
For larger services, the moon_router package adds path parameters, middleware, typed body binding, response helpers, and a ready-made serve loop. The lower-level std.http_server API remains useful when the application wants full control.
A served request runs in its own arena, so whatever a handler allocates while answering is released when the response is done. See Memory for what that means for values a handler wants to keep.
Browse every request, response, listener, and streaming function in the std.http_server API reference.