mirror of
https://github.com/c-cube/tiny_httpd.git
synced 2025-12-06 11:15:35 -05:00
add more doc
This commit is contained in:
parent
3048bfcc82
commit
95248a132c
1 changed files with 160 additions and 15 deletions
|
|
@ -1,12 +1,34 @@
|
|||
|
||||
(** {1 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 buffer input stream, with a view into the current buffer (or refill if empty),
|
||||
and a function to consume [n] bytes *)
|
||||
(** 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. *)
|
||||
|
||||
(** {2 Tiny buffer implementation} *)
|
||||
(** {2 Tiny buffer implementation}
|
||||
|
||||
These buffers are used to avoid allocating too many byte arrays when
|
||||
processing streams and parsing requests.
|
||||
*)
|
||||
module Buf_ : sig
|
||||
type t
|
||||
val size : t -> int
|
||||
|
|
@ -15,19 +37,36 @@ module Buf_ : sig
|
|||
val contents : t -> string
|
||||
end
|
||||
|
||||
(** {2 Generic stream of data} *)
|
||||
(** {2 Generic stream of data}
|
||||
|
||||
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. *)
|
||||
module Stream_ : sig
|
||||
type t = stream
|
||||
|
||||
val close : t -> unit
|
||||
|
||||
val of_chan : in_channel -> t
|
||||
(** Make a buffered stream from the given channel. *)
|
||||
|
||||
val of_chan_close_noerr : in_channel -> t
|
||||
(** Same as {!of_chan} but the [close] method will never fail. *)
|
||||
|
||||
val of_bytes : ?i:int -> ?len:int -> bytes -> t
|
||||
(** A stream that just returns the slice of bytes starting from [i]
|
||||
and of length [len]. *)
|
||||
|
||||
val with_file : string -> (t -> 'a) -> 'a
|
||||
(** Open a file with given name, and obtain an input stream *)
|
||||
(** Open a file with given name, and obtain an input stream
|
||||
on its content. When the function returns, the stream (and file) are closed. *)
|
||||
|
||||
val read_line : ?buf:Buf_.t -> t -> string
|
||||
(** Read a line from the stream.
|
||||
@param buf a buffer to (re)use. Its content will be cleared. *)
|
||||
|
||||
val read_all : ?buf:Buf_.t -> t -> string
|
||||
(** Read the whole stream into a string.
|
||||
@param buf a buffer to (re)use. Its content will be cleared. *)
|
||||
end
|
||||
|
||||
module Meth : sig
|
||||
|
|
@ -38,6 +77,10 @@ module Meth : sig
|
|||
| `HEAD
|
||||
| `DELETE
|
||||
]
|
||||
(** A HTTP method.
|
||||
For now we only handle a subset of these.
|
||||
|
||||
See https://tools.ietf.org/html/rfc7231#section-4 *)
|
||||
|
||||
val pp : Format.formatter -> t -> unit
|
||||
val to_string : t -> string
|
||||
|
|
@ -45,13 +88,32 @@ end
|
|||
|
||||
module Headers : sig
|
||||
type t = (string * string) list
|
||||
(** The header files of a request or response.
|
||||
|
||||
Neither the key nor the value can contain ['\r'] or ['\n'].
|
||||
See https://tools.ietf.org/html/rfc7230#section-3.2 *)
|
||||
|
||||
val get : ?f:(string->string) -> string -> t -> string option
|
||||
(** [get k headers] looks for the header field with key [k].
|
||||
@param f if provided, will transform the value before it is returned. *)
|
||||
|
||||
val set : string -> string -> t -> t
|
||||
(** [set k v headers] sets the key [k] to value [v].
|
||||
It erases any previous entry for [k] *)
|
||||
|
||||
val remove : string -> t -> t
|
||||
(** Remove the key from the headers, if present. *)
|
||||
|
||||
val contains : string -> t -> bool
|
||||
(** Is there a header with the given key? *)
|
||||
|
||||
val pp : Format.formatter -> t -> unit
|
||||
(** Pretty print the headers. *)
|
||||
end
|
||||
|
||||
(** {2 HTTP request}
|
||||
|
||||
A request sent by a client. *)
|
||||
module Request : sig
|
||||
type 'body t = {
|
||||
meth: Meth.t;
|
||||
|
|
@ -59,63 +121,110 @@ module Request : sig
|
|||
path: string;
|
||||
body: 'body;
|
||||
}
|
||||
(** A request with method, path, headers, and a body.
|
||||
|
||||
The body is polymorphic because the request goes through
|
||||
several transformations. First it has no body, as only the request
|
||||
and headers are read; then it has a stream body; then the body might be
|
||||
entirely read as a string via {!read_body_full}. *)
|
||||
|
||||
val pp : Format.formatter -> string t -> unit
|
||||
(** Pretty print the request and its body *)
|
||||
|
||||
val pp_ : Format.formatter -> _ t -> unit
|
||||
(** Pretty print the request without its body *)
|
||||
|
||||
val headers : _ t -> Headers.t
|
||||
|
||||
val get_header : ?f:(string->string) -> _ t -> string -> string option
|
||||
|
||||
val get_header_int : _ t -> string -> int option
|
||||
|
||||
val set_header : 'a t -> string -> string -> 'a t
|
||||
|
||||
val meth : _ t -> Meth.t
|
||||
|
||||
val path : _ t -> string
|
||||
|
||||
val body : 'b t -> 'b
|
||||
|
||||
val read_body_full : stream t -> string t
|
||||
(** Read the whole body into a string. Potentially blocking. *)
|
||||
end
|
||||
|
||||
(** {2 Response code} *)
|
||||
module Response_code : sig
|
||||
type t = int
|
||||
(** A standard HTTP code.
|
||||
|
||||
https://tools.ietf.org/html/rfc7231#section-6 *)
|
||||
|
||||
val ok : t
|
||||
(** The code [200] *)
|
||||
|
||||
val not_found : t
|
||||
(** The code [404] *)
|
||||
|
||||
val descr : t -> string
|
||||
(** A description of some of the error codes.
|
||||
NOTE: this is not complete (yet). *)
|
||||
end
|
||||
|
||||
(** {2 Response}
|
||||
|
||||
A response sent back to a client. *)
|
||||
module Response : sig
|
||||
type body = [`String of string | `Stream of stream]
|
||||
(** Body of a response, either as a simple string,
|
||||
or a stream of bytes. *)
|
||||
|
||||
type t = {
|
||||
code: Response_code.t;
|
||||
headers: Headers.t;
|
||||
body: body;
|
||||
code: Response_code.t; (** HTTP response code. See {!Response_code}. *)
|
||||
headers: Headers.t; (** Headers of the reply. Some will be set by [Tiny_httpd] automatically. *)
|
||||
body: body; (** Body of the response. Can be empty. *)
|
||||
}
|
||||
(** A response. *)
|
||||
|
||||
val make_raw :
|
||||
?headers:Headers.t ->
|
||||
code:Response_code.t ->
|
||||
string ->
|
||||
t
|
||||
(** Make a response from its raw components, with a string body.
|
||||
Use [""] to not send a body at all. *)
|
||||
|
||||
val make_raw_stream :
|
||||
?headers:Headers.t ->
|
||||
code:Response_code.t ->
|
||||
stream ->
|
||||
t
|
||||
(** Same as {!make_raw} but with a stream body. The body will be sent with
|
||||
the chunked transfer-encoding. *)
|
||||
|
||||
val make :
|
||||
?headers:Headers.t ->
|
||||
(body, Response_code.t * string) result -> t
|
||||
(** [make r] turns a result into a response.
|
||||
|
||||
- [make (Ok body)] replies with [200] and the body.
|
||||
- [make (Error (code,msg))] replies with the given error code
|
||||
and message as body.
|
||||
*)
|
||||
|
||||
val make_string :
|
||||
?headers:Headers.t ->
|
||||
(string, Response_code.t * string) result -> t
|
||||
(** Same as {!make} but with a string body. *)
|
||||
|
||||
val make_stream :
|
||||
?headers:Headers.t ->
|
||||
(stream, Response_code.t * string) result -> t
|
||||
(** Same as {!make} but with a stream body. *)
|
||||
|
||||
val fail : ?headers:Headers.t -> code:int ->
|
||||
('a, unit, string, t) format4 -> 'a
|
||||
(** Make the current request fail with the given code and message.
|
||||
Example: [fail ~code:404 "oh noes, %s not found" "waldo"]
|
||||
Example: [fail ~code:404 "oh noes, %s not found" "waldo"].
|
||||
*)
|
||||
|
||||
val fail_raise : code:int -> ('a, unit, string, 'b) format4 -> 'a
|
||||
|
|
@ -125,9 +234,11 @@ module Response : sig
|
|||
*)
|
||||
|
||||
val pp : Format.formatter -> t -> unit
|
||||
(** Pretty print the response. *)
|
||||
end
|
||||
|
||||
type t
|
||||
(** A HTTP server. See {!create} for more details. *)
|
||||
|
||||
val create :
|
||||
?masksigpipe:bool ->
|
||||
|
|
@ -136,10 +247,28 @@ val create :
|
|||
?port:int ->
|
||||
unit ->
|
||||
t
|
||||
(** TODO: document *)
|
||||
(** 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.
|
||||
|
||||
@param 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].
|
||||
|
||||
@param 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.
|
||||
|
||||
@param addr the address (IPv4) to listen on. Default ["127.0.0.1"].
|
||||
@param 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 ->
|
||||
|
|
@ -147,6 +276,8 @@ val add_decode_request_cb :
|
|||
(** 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:
|
||||
|
|
@ -160,7 +291,10 @@ val add_encode_response_cb:
|
|||
|
||||
val set_top_handler : t -> (string Request.t -> Response.t) -> unit
|
||||
(** Setup a handler called by default.
|
||||
If not installed, unhandled paths will return a 404 not found. *)
|
||||
|
||||
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) ->
|
||||
|
|
@ -172,14 +306,22 @@ val add_path_handler :
|
|||
(** [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.
|
||||
@param meth if provided, only accept requests with the given method
|
||||
@param accept should return [true] if the given request (before its body
|
||||
is read) should be accepted, [false] if it's to be rejected (e.g. because
|
||||
|
||||
Note that the handlers are called in the reverse order of their addition,
|
||||
so the last registered handler can override previously registered ones.
|
||||
|
||||
@param meth if provided, only accept requests with the given method.
|
||||
Typically one could react to [`GET] or [`PUT].
|
||||
@param 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
|
||||
|
|
@ -189,7 +331,10 @@ val stop : t -> unit
|
|||
val run : 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. *)
|
||||
start a thread for each new client.
|
||||
|
||||
This returns [Ok ()] if the server exits gracefully, or [Error e] if
|
||||
it exits with an error. *)
|
||||
|
||||
(**/**)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue