Library
Module
Module type
Parameter
Class
Class type
This library implements a very simple, basic HTTP/1.1 server using blocking IOs and threads. Basic routing based on Scanf
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_path_handler ~meth:`GET server
"/hello/%s@/" (fun name _req ->
S.Response.make_string (Ok ("hello " ^name ^"!\n")));
(* echo request *)
S.add_path_handler server
"/echo" (fun req -> S.Response.make_string
(Ok (Format.asprintf "echo:@ %a@." S.Request.pp req)));
S.add_path_handler ~meth:`PUT server
"/upload/%s" (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)
);
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_ : sig ... end
Streams are used to represent a series of bytes that can arrive progressively. For example, an uploaded file will be sent as a series of chunks.
type byte_stream = {
bs_fill_buf : unit -> bytes * int * int;
See the current slice of the internal buffer as bytes, i, len
, where the slice is bytes[i] .. [bytes[i+len-1]]
. Can block to refill the buffer if there is currently no content. If len=0
then there is no more data.
bs_consume : int -> unit;
Consume n bytes from the buffer. This should only be called with n <= len
after a call to is_fill_buf
that returns a slice of length len
.
bs_close : unit -> unit;
Close the stream.
*)}
A buffered stream, with a view into the current buffer (or refill if empty), and a function to consume n
bytes. See Byte_stream
for more details.
module Byte_stream : sig ... end
module Meth : sig ... end
module Headers : sig ... end
module Request : sig ... end
module Response_code : sig ... end
module Response : sig ... end
A HTTP server. See create
for more details.
val create :
?masksigpipe:bool ->
?max_connections:int ->
?new_thread:((unit -> unit) -> unit) ->
?addr:string ->
?port:int ->
unit ->
t
Create a new webserver.
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 port : t -> int
Port on which the server listens.
val add_decode_request_cb :
t ->
(unit Request.t -> (unit Request.t * (byte_stream -> byte_stream)) 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 ->
(string Request.t -> Response.t -> 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 fully parsed query as well as the current response.
val set_top_handler : t -> (string Request.t -> 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.
val add_path_handler :
?accept:(unit Request.t -> (unit, Response_code.t * string) result) ->
?meth:Meth.t ->
t ->
('a,
Scanf.Scanning.in_channel,
'b,
'c ->
string Request.t ->
Response.t,
'a ->
'd,
'd)
format6 ->
'c ->
unit
add_path_handler server "/some/path/%s@/%d/" f
calls f "foo" 42 request
when a request with path "some/path/foo/42/" is received.
This uses Scanf
's splitting, which has some gotchas (in particular, "%s"
is eager, so it's generally necessary to delimit its scope with a "@/"
delimiter. The "@" before a character indicates it's a separator.
Note that the handlers are called in the reverse order of their addition, so the last registered handler can override previously registered ones.