Minimal HTTP server using good old threads + blocking IO, with a small request router.
Find a file
2022-03-01 16:24:40 -05:00
.github/workflows Cleanup GitHub Actions workflow 2022-01-04 13:54:53 +09:00
examples real fix in echo! 2021-12-15 20:19:42 -10:00
src prevent Tiny_httpd_dir.config from being built by hand; provide builder 2022-03-01 16:24:40 -05:00
tests fix tests on non-linux 2022-01-05 08:42:29 -05:00
.gitignore initial commit 2019-11-13 23:38:38 -06:00
CHANGES.md doc: update changes 2022-01-01 00:17:18 -05:00
dune-project avoid dune stanza for inline tests 2021-06-02 15:39:50 -04:00
echo.sh move echo from src/examples/ to examples/ 2021-12-11 10:02:24 -05:00
http_of_dir.sh wip: simple binary for serving a directory 2019-11-17 11:37:59 -06:00
Makefile test: fix production of data file 2021-12-11 13:20:18 -05:00
README.md Show small example of socket activation 2022-02-12 17:04:07 -05:00
tiny_httpd.opam prepare for 0.11 2022-01-01 00:17:18 -05:00
tiny_httpd_camlzip.opam prepare for 0.11 2022-01-01 00:17:18 -05:00

Tiny_httpd build

Minimal HTTP server using good old threads, with stream abstractions, simple routing, URL encoding/decoding, and optional compression with camlzip. It also supports server-sent events (w3c)

Free from all forms of ppx, async monads, etc. 🙃

Note: it can be useful to add the jemalloc opam package for long running server, as it does a good job at controlling memory usage.

The basic echo server from src/examples/echo.ml:


module S = Tiny_httpd

let () =
  let server = S.create () in
  (* say hello *)
  S.add_route_handler ~meth:`GET server
    S.Route.(exact "hello" @/ string @/ return)
    (fun name _req -> S.Response.make_string (Ok ("hello " ^name ^"!\n")));
  (* echo request *)
  S.add_route_handler server
    S.Route.(exact "echo" @/ return)
    (fun req -> S.Response.make_string (Ok (Format.asprintf "echo:@ %a@." S.Request.pp req)));
  Printf.printf "listening on http://%s:%d\n%!" (S.addr server) (S.port server);
  match S.run server with
  | Ok () -> ()
  | Error e -> raise e
$ dune exec src/examples/echo.exe &
listening on http://127.0.0.1:8080

# the path "hello/name" greets you.
$ curl -X GET http://localhost:8080/hello/quadrarotaphile
hello quadrarotaphile!

# the path "echo" just prints the request.
$ curl -X GET http://localhost:8080/echo --data "howdy y'all" 
echo:
{meth=GET;
 headers=Host: localhost:8080
         User-Agent: curl/7.66.0
         Accept: */*
         Content-Length: 10
         Content-Type: application/x-www-form-urlencoded;
 path="/echo"; body="howdy y'all"}

http_of_dir

Similar to python -m http.server, a simple program http_of_dir is provided. It serves files from the current directory.

$ http_of_dir . -p 8080 &
$ curl -X GET http://localhost:8080
...
<html list of current dir>
...

Socket activation

Since version 0.10, socket activation is supported indirectly, by allowing a socket to be explicitly passed in to the create function:

module S = Tiny_httpd

let not_found _ _ = S.Response.fail ~code:404 "Not Found\n"

let () =
  (* Module [Daemon] is from the [ocaml-systemd] package *)
  let server = match Daemon.listen_fds () with
    (* If no socket passed in, assume server was started explicitly i.e. without
       socket activation *)
    | [] -> S.create ()

    (* If a socket passed in e.g. by systemd, listen on that *)
    | sock :: _ -> S.create ~sock ()
  in
  S.add_route_handler server S.Route.rest_of_path not_found;
  Printf.printf "Listening on http://%s:%d\n%!" (S.addr server) (S.port server);
  match S.run server with
  | Ok () -> ()
  | Error e -> raise e

On Linux, this requires the ocaml-systemd package:

opam install ocaml-systemd

Tip: in the dune file, the package name should be systemd.

In case you're not familiar with socket activation, Lennart Poettering's blog post explains it well.

Why?

Why not? If you just want a super basic local server (perhaps for exposing data from a local demon, like Cups or Syncthing do), no need for a ton of dependencies or high scalability libraries.

Use cases might include:

  • serve content directly from a static blog generator;
  • provide a web UI to some tool (like CUPS and syncthing do);
  • implement a basic monitoring page for a service;
  • provide a simple json API for a service, on top of http;
  • use http_of_dir to serve odoc-generated docs or some assets directory.

Documentation

See https://c-cube.github.io/tiny_httpd

License

MIT.