Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Server

Once you have a router, you serve it with a RestServerRef. The server owns a Chronos HTTP server, dispatches incoming requests through the router, and turns the RestApiResponse your handlers return into real HTTP responses.

Creating a server

RestServerRef.new returns a Result, so unwrap it with get() (or handle the error). At minimum it needs a router and an address to bind to.

let server = RestServerRef.new(router, address).get()

Common options

new accepts many keyword arguments; the most useful ones are:

ArgumentDefaultDescription
serverIdentPrestoIdentvalue of the Server response header
serverFlags{NotifyDisconnect}Chronos HttpServerFlags
socketFlags{ReuseAddr}listening socket flags
maxConnections-1 (unlimited)connection cap
bufferSize4096per-connection buffer
httpHeadersTimeout10.secondsheader read timeout
maxHeadersSize8192max request header bytes
maxRequestBodySize1_048_576max request body bytes
requestErrorHandlernilcustom error handler (see below)
errorTypecstringerror type of the returned Result

The errorType parameter lets you choose whether construction failures are reported as cstring (the default) or string:

let res = RestServerRef.new(router, address, errorType = string)
if res.isErr():
  echo "failed to start: ", res.error()

Lifecycle

server.start()              # begin accepting connections
echo server.state           # Running | Stopped | Closed
echo server.localAddress()  # actual bound address (useful with port 0)
await server.stop()         # stop accepting new connections
await server.drop()         # drop pending connections
await server.closeWait()    # stop and release all resources

The relevant procedures are start, stop, drop, closeWait, join, state and localAddress.

A typical test or short-lived program brackets its work with start and closeWait:

server.start()
try:
  # … issue requests …
  discard
finally:
  await server.closeWait()

Handling errors

Some failures happen before or around your handler: a malformed request, an unknown route, or an undecodable body. By default Presto answers these with a bare status code (400, 404, …). Supply a requestErrorHandler to customize them.

The handler receives a RestRequestError describing what went wrong and the HttpRequestRef, and returns an HttpResponseRef.

proc onError(kind: RestRequestError,
             request: HttpRequestRef): Future[HttpResponseRef] {.
    async: (raises: [CancelledError]).} =
  try:
    case kind
    of RestRequestError.Invalid:
      await request.respond(Http400, "invalid request")
    of RestRequestError.NotFound:
      await request.respond(Http404, "no such endpoint")
    of RestRequestError.InvalidContentBody:
      await request.respond(Http400, "bad body")
    of RestRequestError.InvalidContentType:
      await request.respond(Http400, "bad content-type")
    of RestRequestError.Unexpected:
      defaultResponse()
  except HttpError:
    defaultResponse()
let server = RestServerRef.new(
  router, initTAddress("127.0.0.1:8080"), requestErrorHandler = onError).get()

The RestRequestError cases are:

  • Invalid — the request line or path could not be parsed.
  • NotFound — no route matched.
  • InvalidContentBody — the body could not be read.
  • InvalidContentType — a body was sent without a usable Content-Type.
  • Unexpected — a catch-all.

defaultResponse() returns Presto's built-in response and is handy as a fallback.

Note

The error handler covers framework errors. Errors your own handler wants to report should be returned as RestApiResponse.error(...) instead (see Building responses).

Serving over HTTPS

For TLS, use SecureRestServerRef from presto/secureserver. It behaves exactly like RestServerRef—same routing, same responses—but additionally takes a TLS private key and certificate.

let
  key = TLSPrivateKey.init(myKeyPem)
  cert = TLSCertificate.init(myCertPem)
  server = SecureRestServerRef.new(
    router, initTAddress("127.0.0.1:8443"), key, cert).get()

server.start()

SecureRestServerRef supports the same lifecycle procedures (start, stop, drop, closeWait, join, state, localAddress) and the same errorType option as the plain server, plus a secureFlags: set[TLSFlags] argument for TLS-specific behavior.