Page
Library
Module
Module type
Parameter
Class
Class type
Source
Tiny_httpd
SourceThis 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.
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.
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 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.
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.
Ask the server to stop. This might not have an immediate effect as run
might currently be waiting on IO.