Tiny_httpd
Tiny Http Server
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"}
These buffers are used to avoid allocating too many byte arrays when processing streams and parsing requests.
module Buf = Tiny_httpd_core.Buf
module IO = Tiny_httpd_core.IO
module Log = Tiny_httpd_core.Log
module Util = Tiny_httpd_core.Util
module Pool = Tiny_httpd_core.Pool
module Dir = Tiny_httpd_unix.Dir
module type VFS = Tiny_httpd_unix.Dir.VFS
module Html = Tiny_httpd_html
Alias to Tiny_httpd_html
module Request = Tiny_httpd_core.Request
module Response = Tiny_httpd_core.Response
module Response_code = Tiny_httpd_core.Response_code
module Route = Tiny_httpd_core.Route
module Headers = Tiny_httpd_core.Headers
module Meth = Tiny_httpd_core.Meth
module Server = Tiny_httpd_core.Server
Exception raised to exit request handlers with a code+error message
A middleware can be inserted in a handler to modify or observe its behavior.
module Middleware = Server.Middleware
module Head_middleware = Server.Head_middleware
A middleware that only considers the request's head+headers.
type t = Tiny_httpd_core.Server.t
A HTTP server. See create
for more details.
module type IO_BACKEND = Server.IO_BACKEND
A backend that provides IO operations, network operations, etc.
val create_from :
?buf_size:int ->
?middlewares:([ `Encoding | `Stage of int ] * Middleware.t) list ->
backend:(module IO_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.
val addr : t -> string
Address on which the server listens.
val is_ipv6 : t -> bool
is_ipv6 server
returns true
iff the address of the server is an IPv6 address.
val port : t -> int
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).
val active_connections : t -> int
Number of currently active connections.
val add_decode_request_cb :
t ->
(unit Tiny_httpd_core.Request.t ->
(unit Tiny_httpd_core.Request.t
* (Tiny_httpd_core.IO.Input.t ->
Tiny_httpd_core.IO.Input.t))
option) ->
unit
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.
val add_encode_response_cb :
t ->
(unit Tiny_httpd_core.Request.t ->
Tiny_httpd_core.Response.t ->
Tiny_httpd_core.Response.t option) ->
unit
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.
val add_middleware :
stage:[ `Encoding | `Stage of int ] ->
t ->
Middleware.t ->
unit
Add a middleware to every request/response pair.
val set_top_handler :
t ->
(Tiny_httpd_core.IO.Input.t Tiny_httpd_core.Request.t ->
Tiny_httpd_core.Response.t) ->
unit
Setup a handler called by default.
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.
val add_route_handler :
?accept:
(unit Tiny_httpd_core.Request.t ->
(unit, Tiny_httpd_core.Response_code.t * string) result) ->
?middlewares:Middleware.t list ->
?meth:Tiny_httpd_core.Meth.t ->
t ->
('a, string Tiny_httpd_core.Request.t -> Tiny_httpd_core.Response.t)
Tiny_httpd_core.Route.t ->
'a ->
unit
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.
val add_route_handler_stream :
?accept:
(unit Tiny_httpd_core.Request.t ->
(unit, Tiny_httpd_core.Response_code.t * string) result) ->
?middlewares:Middleware.t list ->
?meth:Tiny_httpd_core.Meth.t ->
t ->
('a,
Tiny_httpd_core.IO.Input.t Tiny_httpd_core.Request.t ->
Tiny_httpd_core.Response.t)
Tiny_httpd_core.Route.t ->
'a ->
unit
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.
EXPERIMENTAL: this API is not stable yet.
module type SERVER_SENT_GENERATOR = Server.SERVER_SENT_GENERATOR
A server-side function to generate of Server-sent events.
type server_sent_generator = (module SERVER_SENT_GENERATOR)
Server-sent event generator. This generates events that are forwarded to the client (e.g. the browser).
val add_route_server_sent_handler :
?accept:
(unit Tiny_httpd_core.Request.t ->
(unit, Tiny_httpd_core.Response_code.t * string) result) ->
?middlewares:Head_middleware.t list ->
t ->
('a, string Tiny_httpd_core.Request.t -> server_sent_generator -> unit)
Tiny_httpd_core.Route.t ->
'a ->
unit
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).
These handlers upgrade the connection to another protocol.
module type UPGRADE_HANDLER = Server.UPGRADE_HANDLER
Handler that upgrades to another protocol.
type upgrade_handler = (module UPGRADE_HANDLER)
val add_upgrade_handler :
?accept:
(unit Tiny_httpd_core.Request.t ->
(unit, Tiny_httpd_core.Response_code.t * string) result) ->
?middlewares:Head_middleware.t list ->
t ->
('a, upgrade_handler) Tiny_httpd_core.Route.t ->
'a ->
unit
val running : t -> bool
Is the server running?
val stop : t -> unit
Ask the server to stop. This might not have an immediate effect as run
might currently be waiting on IO.
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.
val 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.
val create :
?masksigpipe:bool ->
?max_connections:int ->
?timeout:float ->
?buf_size:int ->
?get_time_s:(unit -> float) ->
?new_thread:((unit -> unit) -> unit) ->
?addr:string ->
?port:int ->
?sock:Unix.file_descr ->
?middlewares:([ `Encoding | `Stage of int ] * Middleware.t) list ->
unit ->
t
Create a new webserver using UNIX abstractions.
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.