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 = {
|
type stream = {
|
||||||
is_fill_buf: unit -> (bytes * int * int);
|
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;
|
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;
|
is_close: unit -> unit;
|
||||||
|
(** Close the stream. *)
|
||||||
}
|
}
|
||||||
(** A buffer input stream, with a view into the current buffer (or refill if empty),
|
(** A buffered stream, with a view into the current buffer (or refill if empty),
|
||||||
and a function to consume [n] bytes *)
|
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
|
module Buf_ : sig
|
||||||
type t
|
type t
|
||||||
val size : t -> int
|
val size : t -> int
|
||||||
|
|
@ -15,19 +37,36 @@ module Buf_ : sig
|
||||||
val contents : t -> string
|
val contents : t -> string
|
||||||
end
|
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
|
module Stream_ : sig
|
||||||
type t = stream
|
type t = stream
|
||||||
|
|
||||||
val close : t -> unit
|
val close : t -> unit
|
||||||
|
|
||||||
val of_chan : in_channel -> t
|
val of_chan : in_channel -> t
|
||||||
|
(** Make a buffered stream from the given channel. *)
|
||||||
|
|
||||||
val of_chan_close_noerr : in_channel -> t
|
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
|
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
|
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
|
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
|
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
|
end
|
||||||
|
|
||||||
module Meth : sig
|
module Meth : sig
|
||||||
|
|
@ -38,6 +77,10 @@ module Meth : sig
|
||||||
| `HEAD
|
| `HEAD
|
||||||
| `DELETE
|
| `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 pp : Format.formatter -> t -> unit
|
||||||
val to_string : t -> string
|
val to_string : t -> string
|
||||||
|
|
@ -45,13 +88,32 @@ end
|
||||||
|
|
||||||
module Headers : sig
|
module Headers : sig
|
||||||
type t = (string * string) list
|
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
|
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
|
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
|
val remove : string -> t -> t
|
||||||
|
(** Remove the key from the headers, if present. *)
|
||||||
|
|
||||||
val contains : string -> t -> bool
|
val contains : string -> t -> bool
|
||||||
|
(** Is there a header with the given key? *)
|
||||||
|
|
||||||
val pp : Format.formatter -> t -> unit
|
val pp : Format.formatter -> t -> unit
|
||||||
|
(** Pretty print the headers. *)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
(** {2 HTTP request}
|
||||||
|
|
||||||
|
A request sent by a client. *)
|
||||||
module Request : sig
|
module Request : sig
|
||||||
type 'body t = {
|
type 'body t = {
|
||||||
meth: Meth.t;
|
meth: Meth.t;
|
||||||
|
|
@ -59,63 +121,110 @@ module Request : sig
|
||||||
path: string;
|
path: string;
|
||||||
body: 'body;
|
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
|
val pp : Format.formatter -> string t -> unit
|
||||||
|
(** Pretty print the request and its body *)
|
||||||
|
|
||||||
val pp_ : Format.formatter -> _ t -> unit
|
val pp_ : Format.formatter -> _ t -> unit
|
||||||
|
(** Pretty print the request without its body *)
|
||||||
|
|
||||||
val headers : _ t -> Headers.t
|
val headers : _ t -> Headers.t
|
||||||
|
|
||||||
val get_header : ?f:(string->string) -> _ t -> string -> string option
|
val get_header : ?f:(string->string) -> _ t -> string -> string option
|
||||||
|
|
||||||
val get_header_int : _ t -> string -> int option
|
val get_header_int : _ t -> string -> int option
|
||||||
|
|
||||||
val set_header : 'a t -> string -> string -> 'a t
|
val set_header : 'a t -> string -> string -> 'a t
|
||||||
|
|
||||||
val meth : _ t -> Meth.t
|
val meth : _ t -> Meth.t
|
||||||
|
|
||||||
val path : _ t -> string
|
val path : _ t -> string
|
||||||
|
|
||||||
val body : 'b t -> 'b
|
val body : 'b t -> 'b
|
||||||
|
|
||||||
val read_body_full : stream t -> string t
|
val read_body_full : stream t -> string t
|
||||||
|
(** Read the whole body into a string. Potentially blocking. *)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
(** {2 Response code} *)
|
||||||
module Response_code : sig
|
module Response_code : sig
|
||||||
type t = int
|
type t = int
|
||||||
|
(** A standard HTTP code.
|
||||||
|
|
||||||
|
https://tools.ietf.org/html/rfc7231#section-6 *)
|
||||||
|
|
||||||
val ok : t
|
val ok : t
|
||||||
|
(** The code [200] *)
|
||||||
|
|
||||||
val not_found : t
|
val not_found : t
|
||||||
|
(** The code [404] *)
|
||||||
|
|
||||||
val descr : t -> string
|
val descr : t -> string
|
||||||
|
(** A description of some of the error codes.
|
||||||
|
NOTE: this is not complete (yet). *)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
(** {2 Response}
|
||||||
|
|
||||||
|
A response sent back to a client. *)
|
||||||
module Response : sig
|
module Response : sig
|
||||||
type body = [`String of string | `Stream of stream]
|
type body = [`String of string | `Stream of stream]
|
||||||
|
(** Body of a response, either as a simple string,
|
||||||
|
or a stream of bytes. *)
|
||||||
|
|
||||||
type t = {
|
type t = {
|
||||||
code: Response_code.t;
|
code: Response_code.t; (** HTTP response code. See {!Response_code}. *)
|
||||||
headers: Headers.t;
|
headers: Headers.t; (** Headers of the reply. Some will be set by [Tiny_httpd] automatically. *)
|
||||||
body: body;
|
body: body; (** Body of the response. Can be empty. *)
|
||||||
}
|
}
|
||||||
|
(** A response. *)
|
||||||
|
|
||||||
val make_raw :
|
val make_raw :
|
||||||
?headers:Headers.t ->
|
?headers:Headers.t ->
|
||||||
code:Response_code.t ->
|
code:Response_code.t ->
|
||||||
string ->
|
string ->
|
||||||
t
|
t
|
||||||
|
(** Make a response from its raw components, with a string body.
|
||||||
|
Use [""] to not send a body at all. *)
|
||||||
|
|
||||||
val make_raw_stream :
|
val make_raw_stream :
|
||||||
?headers:Headers.t ->
|
?headers:Headers.t ->
|
||||||
code:Response_code.t ->
|
code:Response_code.t ->
|
||||||
stream ->
|
stream ->
|
||||||
t
|
t
|
||||||
|
(** Same as {!make_raw} but with a stream body. The body will be sent with
|
||||||
|
the chunked transfer-encoding. *)
|
||||||
|
|
||||||
val make :
|
val make :
|
||||||
?headers:Headers.t ->
|
?headers:Headers.t ->
|
||||||
(body, Response_code.t * string) result -> 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 :
|
val make_string :
|
||||||
?headers:Headers.t ->
|
?headers:Headers.t ->
|
||||||
(string, Response_code.t * string) result -> t
|
(string, Response_code.t * string) result -> t
|
||||||
|
(** Same as {!make} but with a string body. *)
|
||||||
|
|
||||||
val make_stream :
|
val make_stream :
|
||||||
?headers:Headers.t ->
|
?headers:Headers.t ->
|
||||||
(stream, Response_code.t * string) result -> t
|
(stream, Response_code.t * string) result -> t
|
||||||
|
(** Same as {!make} but with a stream body. *)
|
||||||
|
|
||||||
val fail : ?headers:Headers.t -> code:int ->
|
val fail : ?headers:Headers.t -> code:int ->
|
||||||
('a, unit, string, t) format4 -> 'a
|
('a, unit, string, t) format4 -> 'a
|
||||||
(** Make the current request fail with the given code and message.
|
(** 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
|
val fail_raise : code:int -> ('a, unit, string, 'b) format4 -> 'a
|
||||||
|
|
@ -125,9 +234,11 @@ module Response : sig
|
||||||
*)
|
*)
|
||||||
|
|
||||||
val pp : Format.formatter -> t -> unit
|
val pp : Format.formatter -> t -> unit
|
||||||
|
(** Pretty print the response. *)
|
||||||
end
|
end
|
||||||
|
|
||||||
type t
|
type t
|
||||||
|
(** A HTTP server. See {!create} for more details. *)
|
||||||
|
|
||||||
val create :
|
val create :
|
||||||
?masksigpipe:bool ->
|
?masksigpipe:bool ->
|
||||||
|
|
@ -136,10 +247,28 @@ val create :
|
||||||
?port:int ->
|
?port:int ->
|
||||||
unit ->
|
unit ->
|
||||||
t
|
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
|
val addr : t -> string
|
||||||
|
(** Address on which the server listen. *)
|
||||||
|
|
||||||
val port : t -> int
|
val port : t -> int
|
||||||
|
(** Port on which the server listen. *)
|
||||||
|
|
||||||
val add_decode_request_cb :
|
val add_decode_request_cb :
|
||||||
t ->
|
t ->
|
||||||
|
|
@ -147,6 +276,8 @@ val add_decode_request_cb :
|
||||||
(** Add a callback for every request.
|
(** Add a callback for every request.
|
||||||
The callback can provide a stream transformer and a new request (with
|
The callback can provide a stream transformer and a new request (with
|
||||||
modified headers, typically).
|
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:
|
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
|
val set_top_handler : t -> (string Request.t -> Response.t) -> unit
|
||||||
(** Setup a handler called by default.
|
(** 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 :
|
val add_path_handler :
|
||||||
?accept:(unit Request.t -> (unit, Response_code.t * string) result) ->
|
?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]
|
(** [add_path_handler server "/some/path/%s@/%d/" f]
|
||||||
calls [f request "foo" 42 ()] when a request with path "some/path/foo/42/"
|
calls [f request "foo" 42 ()] when a request with path "some/path/foo/42/"
|
||||||
is received.
|
is received.
|
||||||
|
|
||||||
This uses {!Scanf}'s splitting, which has some gotchas (in particular,
|
This uses {!Scanf}'s splitting, which has some gotchas (in particular,
|
||||||
["%s"] is eager, so it's generally necessary to delimit its
|
["%s"] is eager, so it's generally necessary to delimit its
|
||||||
scope with a ["@/"] delimiter. The "@" before a character indicates it's
|
scope with a ["@/"] delimiter. The "@" before a character indicates it's
|
||||||
a separator.
|
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
|
Note that the handlers are called in the reverse order of their addition,
|
||||||
is read) should be accepted, [false] if it's to be rejected (e.g. because
|
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).
|
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
|
val stop : t -> unit
|
||||||
|
|
@ -189,7 +331,10 @@ val stop : t -> unit
|
||||||
val run : t -> (unit, exn) result
|
val run : t -> (unit, exn) result
|
||||||
(** Run the main loop of the server, listening on a socket
|
(** Run the main loop of the server, listening on a socket
|
||||||
described at the server's creation time, using [new_thread] to
|
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