This library implements a very simple, basic HTTP/1.1 server using blocking IOs and threads. Basic routing based is provided for convenience, so that several handlers can be registered.
It is possible to use a thread pool, see create's argument new_thread.
The echo example (see src/examples/echo.ml) demonstrates some of the features by declaring a few endpoints, including one for uploading files:
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)));
(* file upload *)
S.add_route_handler ~meth:`PUT server
S.Route.(exact "upload" @/ string_urlencoded @/ return)
(fun path req ->
try
let oc = open_out @@ "/tmp/" ^ path in
output_string oc req.S.Request.body;
flush oc;
S.Response.make_string (Ok "uploaded file")
with e ->
S.Response.fail ~code:500 "couldn't upload file: %s"
(Printexc.to_string e)
);
(* run the server *)
Printf.printf "listening on http://%s:%d\n%!" (S.addr server) (S.port server);
match S.run server with
| Ok () -> ()
| Error e -> raise e
It is then possible to query it using curl:
$ 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"}
Tiny buffer implementation
These buffers are used to avoid allocating too many byte arrays when processing streams and parsing requests.
A backend that provides IO operations, network operations, etc.
Sourceval create_from :
?enable_logging:bool ->?buf_size:int ->?head_middlewares:Head_middleware.t list->?middlewares:([ `Encoding | `Stage of int ] * Middleware.t) list->backend:(moduleIO_BACKEND)->unit ->t
Create a new webserver using provided backend.
The server will not do anything until run is called on it. Before starting the server, one can use add_path_handler and set_top_handler to specify how to handle incoming requests.
Port on which the server listens. Note that this might be different than the port initially given if the port was 0 (meaning that the OS picks a port for us).
Add a callback for every request. The callback can provide a stream transformer and a new request (with modified headers, typically). A possible use is to handle decompression by looking for a Transfer-Encoding header and returning a stream transformer that decompresses on the fly.
Add a callback for every request/response pair. Similarly to add_encode_response_cb the callback can return a new response, for example to compress it. The callback is given the query with only its headers, as well as the current response.
This handler is called with any request not accepted by any handler installed via add_path_handler. If no top handler is installed, unhandled paths will return a 404 not found
This used to take a string Request.t but it now takes a byte_stream Request.t since 0.14 . Use Request.read_body_full to read the body into a string if needed.
add_route_handler server Route.(exact "path" @/ string @/ int @/ return) f calls f "foo" 42 request when a request with path "path/foo/42/" is received.
Note that the handlers are called in the reverse order of their addition, so the last registered handler can override previously registered ones.
parametermeth
if provided, only accept requests with the given method. Typically one could react to `GET or `PUT.
parameteraccept
should return Ok() if the given request (before its body is read) should be accepted, Error (code,message) if it's to be rejected (e.g. because its content is too big, or for some permission error). See the http_of_dir program for an example of how to use accept to filter uploads that are too large before the upload even starts. The default always returns Ok(), i.e. it accepts all requests.
Similar to add_route_handler, but where the body of the request is a stream of bytes that has not been read yet. This is useful when one wants to stream the body directly into a parser, json decoder (such as Jsonm) or into a file.
Add a handler on an endpoint, that serves server-sent events.
The callback is given a generator that can be used to send events as it pleases. The connection is always closed by the client, and the accepted method is always GET. This will set the header "content-type" to "text/event-stream" automatically and reply with a 200 immediately. See server_sent_generator for more details.
This handler stays on the original thread (it is synchronous).
since 0.9
Upgrade handlers
These handlers upgrade the connection to another protocol.
Ask the server to stop. This might not have an immediate effect as run might currently be waiting on IO.
Sourceval run : ?after_init:(unit -> unit)->t->(unit, exn)result
Run the main loop of the server, listening on a socket described at the server's creation time, using new_thread to start a thread for each new client.
This returns Ok () if the server exits gracefully, or Error e if it exits with an error.
parameterafter_init
is called after the server starts listening. since 0.13 .
Sourceval run_exn : ?after_init:(unit -> unit)->t-> unit
run_exn s is like run s but re-raises an exception if the server exits with an error.
The server will not do anything until run is called on it. Before starting the server, one can use add_path_handler and set_top_handler to specify how to handle incoming requests.
parametermasksigpipe
if true, block the signal Sys.sigpipe which otherwise tends to kill client threads when they try to write on broken sockets. Default: true except when on Windows, which defaults to false.
parameterbuf_size
size for buffers (since 0.11)
parameternew_thread
a function used to spawn a new thread to handle a new client connection. By default it is Thread.create but one could use a thread pool instead. See for example this use of moonpool.
connection is closed if the socket does not do read or write for the amount of second. Default: 0.0 which means no timeout. timeout is not recommended when using proxy.
parameteraddr
address (IPv4 or IPv6) to listen on. Default "127.0.0.1".
parameterport
to listen on. Default 8080.
parametersock
an existing socket given to the server to listen on, e.g. by systemd on Linux (or launchd on macOS). If passed in, this socket will be used instead of the addr and port. If not passed in, those will be used. This parameter exists since 0.10.
parameterenable_logging
if true and Logs is installed, log requests. Default true. This parameter exists since 0.18. Does not affect debug-level logs.
parameterget_time_s
obtain the current timestamp in seconds. This parameter exists since 0.11.