From 76757038f46b9022afbcf7bb3d93c78c30280ddc Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Mon, 18 Nov 2019 22:08:45 -0600 Subject: [PATCH] update doc --- 0.1/tiny_httpd/Tiny_httpd/index.html | 40 +++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/0.1/tiny_httpd/Tiny_httpd/index.html b/0.1/tiny_httpd/Tiny_httpd/index.html index 5a468e7d..c98ad014 100644 --- a/0.1/tiny_httpd/Tiny_httpd/index.html +++ b/0.1/tiny_httpd/Tiny_httpd/index.html @@ -1,2 +1,40 @@ -Tiny_httpd (tiny_httpd.Tiny_httpd)

Module Tiny_httpd

Tiny Http Server

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.

type stream = {
is_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.

is_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.

is_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 Buf_ for more details.

module Buf_ : sig ... end
module Stream_ : sig ... end
module Meth : sig ... end
module Headers : sig ... end
module Request : sig ... end
module Response_code : sig ... end
module Response : sig ... end
type t

A HTTP server. See create for more details.

val create : ?⁠masksigpipe:bool -> ?⁠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.

parameter masksigpipe

if true, block the signal Sys.sigpipe which otherwise tends to kill client threads when they try to write on broken sockets. Default: true.

parameter new_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.

parameter addr

the address (IPv4) to listen on. Default "127.0.0.1".

parameter port

to listen on. Default 8080.

val addr : t -> string

Address on which the server listen.

val port : t -> int

Port on which the server listen.

val add_decode_request_cb : t -> (unit Request.t -> (unit Request.t * (stream -> 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) Pervasives.result) -> ?⁠meth:Meth.t -> t -> ('a, Scanf.Scanning.in_channel, 'b'c -> string Request.t -> Response.t'a -> 'd'd) Pervasives.format6 -> 'c -> unit

add_path_handler server "/some/path/%s@/%d/" f calls f request "foo" 42 () 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.

parameter meth

if provided, only accept requests with the given method. Typically one could react to `GET or `PUT.

parameter accept

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.

val stop : t -> unit

Ask the server to stop. This might not have an immediate effect as run might currently be waiting on IO.

val run : t -> (unit, exn) Pervasives.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.

\ No newline at end of file +Tiny_httpd (tiny_httpd.Tiny_httpd)

Module Tiny_httpd

Tiny Http Server

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:

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 ->
+        debug_ (fun k->k "start upload %S\n%!" path);
+        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"}
type stream = {
is_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.

is_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.

is_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 Buf_ for more details.

module Buf_ : sig ... end
module Stream_ : sig ... end
module Meth : sig ... end
module Headers : sig ... end
module Request : sig ... end
module Response_code : sig ... end
module Response : sig ... end
type t

A HTTP server. See create for more details.

val create : ?⁠masksigpipe:bool -> ?⁠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.

parameter masksigpipe

if true, block the signal Sys.sigpipe which otherwise tends to kill client threads when they try to write on broken sockets. Default: true.

parameter new_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.

parameter addr

the address (IPv4) to listen on. Default "127.0.0.1".

parameter port

to listen on. Default 8080.

val addr : t -> string

Address on which the server listen.

val port : t -> int

Port on which the server listen.

val add_decode_request_cb : t -> (unit Request.t -> (unit Request.t * (stream -> 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) Pervasives.result) -> ?⁠meth:Meth.t -> t -> ('a, Scanf.Scanning.in_channel, 'b'c -> string Request.t -> Response.t'a -> 'd'd) Pervasives.format6 -> 'c -> unit

add_path_handler server "/some/path/%s@/%d/" f calls f request "foo" 42 () 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.

parameter meth

if provided, only accept requests with the given method. Typically one could react to `GET or `PUT.

parameter accept

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.

val stop : t -> unit

Ask the server to stop. This might not have an immediate effect as run might currently be waiting on IO.

val run : t -> (unit, exn) Pervasives.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.

\ No newline at end of file