Web service howto

This manual gets you started to connect your service to an HTTP gateway.

Define your service

In this example we use the pre-canned Webs.Req.echo service which writes back requests and their bodies as 404. We use Webs_cli.quick_serve function, it kills a bit of boilerplate and setups a basic command line interface. The function uses the HTTP/1.1 Webs_httpc gateway connector which makes it for convenient local testing.

cat - > min.ml <<EOF
open Webs

let service = Http.Req.echo
let main () = Webs_cli.quick_serve ~name:"min" service
let () = if !Sys.interactive then () else main ()

Compile the service:

ocamlfind ocamlopt -linkpkg -thread -g -package webs,webs.cli -o min min.ml

Now run the service and check it works correctly. By default Webs_cli.quick_serve bind localhost on the port 8000 so try:

./min --service-path /myservice/ &
curl -i http://localhost:8000/myservice/

You should get a response like:

HTTP/1.1 404 Not Found
content-type: text/plain;charset=utf-8
content-length: 192
connection: close

(body-length 0)
(accept "*/*")
(host "localhost:8000")
(user-agent "curl/7.64.1")
(method GET)
(path "")
(query )
(service-path "myservice" "")
(version HTTP/1.1)
(request-target /myservice/)

Connect the gateway

If you can't find instructions for your web server below you should be able to apply those of Nginx mutatis mutandis.


We assume you are running Nginx to serve the https://example.org web site and that you want to bind your service using https://example.org/myservice/ as the root for requests.

Edit the configuration file of your web site in /etc/nginx/sites-available/example.org and add the following location block:

location /myservice/ {
   proxy_http_version 1.1;
   proxy_pass http://localhost:8000;

   # If you need websockets
   proxy_set_header Upgrade $http_upgrade;
   proxy_set_header Connection $http_connection;

Now reload the web server configuration. One of the following two should do.

systemctl reload nginx   # If you are using systemd
nginx -s reload          # Otherwise

Your service must now be connected to the interwebs over HTTPS. Try:

curl -i https://example.org/myservice/hello

to see if you get a response. If not you may want to dig into nginx's error logs, for example in /var/logs/nginx or journalctl -u nginx.service.

There are quite a few things that can be tweaked in that proxy_pass location block, for example to add custom request headers for your service, see the nginx reverse proxy manual for details.

Service path

TODO add a little discussion about --service-path and Req.service_path.

Serving files

File serving over HTTP should correctly handle:

You can use Webs_unix.send_file to serve files with your service. This takes care of these points and transmits the file with the sendfile(2) system call if available. The advantage of this approach is that it keeps your gateway configuration simple.

Alternatively most gateways let backend services handoff requests back to the gateway by writing special headers in the response. Use the function Webs_kit.Gateway.send_file for this. How to use it exactly depends on your gateway. The advantage of this approach is that your service doesn't depend on the Unix module (you may care) and it allows to take advantage of your gateway's load balancing capabilities.

These two alternatives are detailed on a simple example below.

Via the service

In this example any GET request of the form /assets/* is looked up as the /my/files/* file and directly served by the service.

open Webs
open Webs_kit
let ( let* ) = Result.bind

let root = "/my/files"
let service r =
  Http.Resp.result @@ match Http.Req.path r with
  | "assets" as pre :: _ ->
      let* `GET = Http.Req.allow Http.Meth.[get] r in
      let* file = Http.Req.to_absolute_filepath ~strip:[pre] ~root r in
      Webs_unix.send_file r file
  | _ ->
      Http.Resp.not_found_404 ()

let main () = Webs_cli.quick_serve ~name:"files_unix" service
let () = if !Sys.interactive then () else main ()

Via the gateway

In this example the service runs with Nginx on /myservice and any GET request on the service of the form /assets/* is served by the gateway as the /my/files/* file.

We start by configuring Nginx as follows:

location /myservice/ {
   proxy_http_version 1.1;
   proxy_pass http://localhost:8000/; # final slash is important

location /myservice-files/assets
  alias /my/files;

With this configuration internal redirects of the form /myservice-files/assets/* serve the file /my/files/*.

Now our Webs service just captures /assets/* requests and internally redirects them to /myservice-files/assets/* by using Webs_kit.Gateway.send_file with the Nginx specific Webs_kit.Gateway.x_accel_redirect header.

open Webs
open Webs_kit
let ( let* ) = Result.bind

let root = "/myservice-files"
let service r =
  Http.Resp.result @@ match Http.Req.path r with
  | "assets" as pre :: _ ->
      let* `GET = Http.Req.Allow.(meths [get] r) in
      let* file = Http.Req.to_absolute_filepath ~strip:[pre] ~root r in
      Gateway.send_file ~header:Gateway.x_accel_redirect r file
  | _ ->
      Ok (Http.Resp.v Http.not_found_404)

let main () = Webs_cli.quick_serve ~name:"files_gateway" service
let () = if !Sys.interactive then () else main ()

Note that if your Nginx gateway is configured to do so this properly handles etags and range requests.


Explain a bit of the caching stuff. In particular if your webs log is full of HTML page asset requests (even 302), something is likely wrong.