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):
#ANCHOR: import import chronos/apps/http/httpserver #ANCHOR_END: import #ANCHOR: handler 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() #ANCHOR_END: handler #ANCHOR: main 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() #ANCHOR_END: main #ANCHOR: run when isMainModule: waitFor main() #ANCHOR_END: run
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 httpserver: RequestFence as an argument. httpserver: RequestFence is a Result type that can contain either a valid httpserver: HttpRequestRef or an error. This allows Chronos to notify us if something went wrong during request parsing.
The function is annotated with the asyncmacro: async(untyped) pragma and raises: [CancelledError] (futures: CancelledError) according to Chronos's Errors and exceptions: Checked exceptions.
Inside the handler, we first check if the request was received correctly. If not, we return a httpserver: defaultResponse(), which is simply an empty response.
If the request is valid, we use the httpserver: respond(HttpRequestRef, HttpCode, ByteChar) 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 (httpcommon: HttpWriteError). Note that we let futures: 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:
- Define the address and port to listen on (127.0.0.1:8080).
- Create an instance of the server using new(typedesc[HttpServerRef], TransportAddress, HttpProcessCallback2, set[HttpServerFlags], set[ServerFlags], string, int, int, int, int, int, openArray[HttpServerMiddlewareRef]).
- Start the server with httpserver: start(HttpServerRef).
- Use httpserver: join(HttpServerRef) to wait until the server is stopped (which, in this case, will be never, until we manually terminate the program with Ctrl-C).
- In the finally block, we ensure the server is stopped and its resources are released correctly.
when isMainModule: waitFor main()
Finally, we use asyncfutures: waitFor(Future[void]) to start our async main routine.