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

Setting Up a Basic HTTP Server

Goal: Learn how to create and start a simple HTTP server with Chronos.

Source code: chapter1/src/dashboard.nim

First, let's initialize a new binary project with Nimble. Switch to your preferred project directory in your terminal and run:

$ nimble init dashboard

When prompted, choose binary for the package type.

Now, open the generated dashboard.nimble file and add chronos to the dependencies:

# Dependencies

requires "nim >= 2.0.0"
requires "chronos"

Finally, open src/dashboard.nim and replace the code in it with this (we'll go through each line in a moment):

import chronos/apps/http/httpserver

proc handler(
    reqfence: RequestFence
): Future[HttpResponseRef] {.async: (raises: [CancelledError]).} =
  if reqfence.isErr():
    return defaultResponse()

  let request = reqfence.get()

  try:
    await request.respond(Http200, "Hello, Chronos!")
  except HttpWriteError:
    defaultResponse()


proc main() {.async: (raises: [TransportAddressError, CancelledError]).} =
  let
    address = initTAddress("127.0.0.1:8080")
    server = HttpServerRef.new(address, handler).valueOr:
      echo "Unable to start HTTP server: " & error
      return

  server.start()
  echo "HTTP server running on http://127.0.0.1:8080"

  try:
    await server.join()
  finally:
    await server.stop()
    await server.closeWait()


when isMainModule:
  waitFor main()

To execute the project, run this command from the dashboard directory:

$ nimble run

You should see the following message in your terminal:

HTTP server running on http://127.0.0.1:8080

Now, open your web browser and go to 127.0.0.1:8080. You should see "Hello, Chronos!".

Line-by-Line Explanation

import chronos/apps/http/httpserver

httpserver module implements the HTTP server capabilities, i.e. listening for incoming connections and responding to HTTP requests.

proc handler(
    reqfence: RequestFence
): Future[HttpResponseRef] {.async: (raises: [CancelledError]).} =
  if reqfence.isErr():
    return defaultResponse()

  let request = reqfence.get()

  try:
    await request.respond(Http200, "Hello, Chronos!")
  except HttpWriteError:
    defaultResponse()

We define a handler function that will be called for every incoming request.

Note that this function takes a RequestFence as an argument. RequestFence is a Result type that can contain either a valid HttpRequestRef or an error. This allows Chronos to notify us if something went wrong during request parsing.

Info

Result comes from results library. It's somewhat similar to Nim's built-in Options type but more powerful. Chronos uses it all around the place whenever a function can return a result or an error.

The function is annotated with the async pragma and raises: [CancelledError] (CancelledError) according to Chronos's checked exceptions.

Inside the handler, we first check if the request was received correctly. If not, we return a defaultResponse(), which is simply an empty response.

If the request is valid, we use the respond method to send a simple string back to the client with an HTTP 200 OK status.

We wrap the respond call in a try-except block to handle potential network errors (HttpWriteError). Note that we let CancelledError propagate to the caller instead of catching it.

proc main() {.async: (raises: [TransportAddressError, CancelledError]).} =
  let
    address = initTAddress("127.0.0.1:8080")
    server = HttpServerRef.new(address, handler).valueOr:
      echo "Unable to start HTTP server: " & error
      return

  server.start()
  echo "HTTP server running on http://127.0.0.1:8080"

  try:
    await server.join()
  finally:
    await server.stop()
    await server.closeWait()

In the main function, we:

  1. Define the address and port to listen on (127.0.0.1:8080).
  2. Create an instance of the server using HttpServerRef.new.
  3. Start the server with server.start().
  4. Use server.join() to wait until the server is stopped (which, in this case, will be never, until we manually terminate the program with Ctrl-C).
  5. In the finally block, we ensure the server is stopped and its resources are released correctly.

Info

valueOr is a helper template from the results package that returns the value of a Result or executes a given code block if it is an error.

when isMainModule:
  waitFor main()

Finally, we use waitFor to start our async main routine.