/Ocamlapi

Path-based http request routing in Ocaml.

Primary LanguageOCaml

Ocamlapi

Ocamlapi is an Ocaml library for path-based routing of HTTP requests.

Full documentation is available here.

It is built on top of Cohttp.

Libraries

The core of Ocamlapi is parameterized on your choice of Http library.

Ocamlapi has 4 separately installable libraries:

  • ocamlapi: Core functors for the library.
  • ocamlapi_async: An implementation of ocamlapi using the Cohttp-async backend.
  • ocamlapi_lwt_unix: An implmentation of ocamlapi using the Cohttp-lwt-unix backend.

Each library is installable through opam:

Eg. to install ocamlapi_async, run opam install ocamlapi_async.

Getting Started

Ocamlapi allows the user to build routers, which bind callbacks to URLs. When a request is received by the server, a router is used to dispatch the request to the appropriate callback.

For quick examples to get started, consult the examples directory.

URL templates

A URL template specifies a set of URLs to be matched against.

A URL template consists of a list of path segments delimited by forward slashes. A path segment can either be static or dynamic. A static path segment matches against a single string. A dynamic path segment matches against any string up to the next forward slash in the path, and is written as <variable name>.

A few examples of URL templates:

/api/version This matches against the literal string /api/version

/users/<userId> This matches against strings such as /users/sam. It does not match against strings such as /users/sam/history.

/users/<userId>/profile This matches against strings such as /users/sam/profile.

Routes

A route consists of a URL template and a list of tuples. Each tuple in the list is of the form (HTTP method, callback). This means that the route will match any request where the path matches the route's URL template, and the request's HTTP method appears in the list.

The callback associated with the HTTP method then gets called on the request.

Callbacks

A callback is a function of type ?⁠vars:string Core.String.Table.t ‑> request -> body -> (response * body) io

The first argument to a callback is an optional map of strings to strings. This map's keys are the names of the dynamic path segments in the URL template that was matched. The corresponding values are the values that were extracted from the request's path.

For example, if a route is declared as "/user/<userId>", [ `GET, some_callback] and a GET request is made to the path "/user/sam", the map given to the callback will have a single key, "userId", and its value will be "sam".

The second argument is the request that was made to the server. The third argument is the request's body.

Finally, a callback returns an HTTP response and its body asynchronously, hence the return type of (response * body) io.

The callback contains whatever business logic necessary to power the response. For example, within a callback, you can make database calls, call a template rendering libary, etc.

Routers

A router is the data structure that actually dispatches the request to the appropriate callback.

Creating a router

A router has 3 main components: a list of routes, a fallback function, and an error handling function.

The fallback function is a callback that gets called when no route matches the given request. The error handler is a function that gets called if a callback throws an exception while processing a request. Finally, the list of routes defines what routes a router will respond to.

Routing a request

The dispatch function takes a router and a request, and routes the request to the appropriate callback.

An example

We will use Ocamlapi_async for this introduction.

Here is an example of a server that supports a single GET operation on the path: /<name>/greet:

open Async
open Core
open Cohttp_async

let exn_handler ?vars:_ _ =
        Server.respond_string ~status:(`Code 500) "Internal server error"

(* Create a route *)
let greeting_route =
    "/<name>/greet",
    [`GET, fun ?vars:(vars=String.Table.create ()) _request _body ->
                String.Table.find_exn vars "name"
                |> Printf.sprintf "Hello, %s!"
                |> Server.respond_string ]

(* Create the router *)
let r = Ocamlapi_async.create_exn ~exn_handler:exn_handler [ greeting_route ]

(* When the server recieves a request, dispatch it to the callback using the router *)
let handler ~body:b _sock req =
    Ocamlapi_async.dispatch r req b

let start_server port () =
    eprintf "Listening for HTTP on port %d\n" port;
    Cohttp_async.Server.create
                        ~on_handler_error:`Ignore
                        (Async.Tcp.Where_to_listen.of_port port)
                        handler
                        >>= fun _ -> Deferred.never ()

let () =
    let module Command = Async_extra.Command in
        Command.async_spec
                ~summary:"Start a hello world Async server"
                Command.Spec.(
                        empty +>
                        flag "-p" (optional_with_default 8080 int)
                                ~doc:"int Source port to listen on"
                ) start_server
        |> Command.run

For documentation around the server component of this example, consult the Cohttp project.