HTTP server
August 12, 2026 · View on GitHub
proto/httpserver.nim is a non-blocking HTTP/1.1 server combining the TCP layer
and the parser. It is deliberately router-less: you provide one
callback and route however you like (plain if/case, or a framework such as
Supranim on top).
Runnable examples: examples/httpserver.nim,
examples/static_server.nim.
Creating a server
let server = newHttpServer(loop) # share an existing loop
let server = newHttpServer() # create its own loop
populate: bool = true prewarms the connection/parser/response pools by
default. populatePools(server, poolSize = 256) re-prewarms explicitly.
The handler
proc handler(req: HttpRequest, res: HttpResponse) {.gcsafe.} =
case req.getMethod():
of HttpGet:
if req.getPath() == "/":
res.status(Http200).header("Content-Type", "text/html; charset=utf-8")
.send("<h1>Hello</h1>")
else:
res.sendError(Http404, "not found")
else:
res.sendError(Http405)
OnRequestCallback = proc(req: HttpRequest, res: HttpResponse) {.gcsafe.}
The callback is {.gcsafe.} so it can run on multiple threads
(see concurrency).
Starting / listening
server.start(handler, Port(9000)) # listen on 0.0.0.0:9000
server.listen("127.0.0.1", 9000) # explicit address (HTTP)
server.listenUnix("/tmp/app.sock", 0o660) # UDS, POSIX
server.stop()
server.close()
getLoop(server) returns the server's loop; ensureTcpServer lazily creates
the underlying TcpServer; addConnection(fd) injects an accepted fd.
Response builder
status/header/close are fluent (they return HttpResponse):
res.status(Http200) # set status code
.header("Content-Type", "application/json")
.header("X-Frame-Options", "DENY")
.send("""{"ok": true}""") # send body (string or seq[byte])
res.send() # empty body
res.close() # mark connection close
res.sendError(Http404, "custom message") # canned error page
res.getConn() → the underlying Connection; res.getClientIp() → client IP;
res.markSent() marks the response sent without sending.
Responses are served with a Date header (RFC 7231), pipelined requests are
processed in order, and keep-alive is on by default
(setKeepAliveTimeout(ms), default DefaultKeepAliveMs = 5000).
Streaming a response body
res.send writes the buffer; for large bodies use the zero-copy file APIs or
write chunks directly:
discard res.getConn().send("partial bytes")
Serving files
res.sendFile(path, req, ...)— one-shot zero-copy downloadres.streamFile(path, req, chunkSize)— chunked streaming, keep-alive, RangeserveFile(res, req, path, fsRoot, ...)— full conditional/Range semanticsserveStatic(res, req, urlPrefix, fsRoot, indexFiles)— static site serving
See static files for the full matrix.
Server configuration
HttpServer public fields (see security for the recommended
production values):
| Field | Purpose |
|---|---|
handler | the OnRequestCallback |
sslCtx | TLS context → HTTPS (see TLS) |
maxBodySize | max request body (0 = use maxStreamBodySize) |
maxStreamBodySize | hard cap for streamed bodies |
maxFileSize | max single uploaded file part |
maxFieldSize | max single text field |
maxConnections | concurrent connection cap |
maxPipelineDepth | pipelined requests per connection |
readTimeoutMs | slowloris / partial-request timeout |
wsIdleTimeoutMs | WebSocket idle timeout |
timeoutSweepMs | timeout sweep interval |
Constants
DefaultKeepAliveMs = 5000, MaxWsPoolSize = 2048,
DefaultChunkSize = 1 MiB (1_048_576).
API reference
Full signatures: HTTP server API. Related: requests, static files, multipart, rate limiting.