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:
module S = Tiny_httpd
-
-let () =
- let server = S.create () in
-
- (* say hello *)
- S.add_route_handler ~meth:`GET server
- S.Route.(exact "hello" @/ string @/ return)
- (fun name _req -> S.Response.make_string (Ok ("hello " ^name ^"!\n")));
-
- (* echo request *)
- S.add_route_handler server
- S.Route.(exact "echo" @/ return)
- (fun req -> S.Response.make_string
- (Ok (Format.asprintf "echo:@ %a@." S.Request.pp req)));
-
- (* file upload *)
- S.add_route_handler ~meth:`PUT server
- S.Route.(exact "upload" @/ string_urlencoded @/ return)
- (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)
- );
-
- (* run the server *)
- 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"}
Tiny buffer implementation
These buffers are used to avoid allocating too many byte arrays when processing streams and parsing requests.
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.
Main Server Type
Methods
Headers are metadata associated with a request or response.
Requests
Requests are sent by a client, e.g. a web browser or cURL.
Response Codes
Responses
Responses are what a http server, such as Tiny_httpd, send back to the client to answer a Request.t
Main Server type
A HTTP server. See create for more details.
val create : ?masksigpipe:bool -> ?max_connections:int -> ?timeout:float ->
-?buf_size:int -> ?get_time_s:(unit -> float) -> ?new_thread:((unit -> unit) -> unit) ->
-?addr:string -> ?port:int -> ?sock:Unix.file_descr ->
-?middlewares:([ `Encoding | `Stage of int ] * Middleware.t) list -> unit -> tCreate 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.
Address on which the server listens.
is_ipv6 server returns true iff the address of the server is an IPv6 address.
Port on which the server listens.
val active_connections : t -> intNumber of active connections
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.
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 query with only its headers, as well as the current response.
val add_middleware : stage:[ `Encoding | `Stage of int ] -> t -> Middleware.t -> unitAdd a middleware to every request/response pair.
Request handlers
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.
add_route_handler server Route.(exact "path" @/ string @/ int @/ return) f calls f "foo" 42 request when a request with path "path/foo/42/" is received.
Note that the handlers are called in the reverse order of their addition, so the last registered handler can override previously registered ones.
Similar to add_route_handler, but where the body of the request is a stream of bytes that has not been read yet. This is useful when one wants to stream the body directly into a parser, json decoder (such as Jsonm) or into a file.
Server-sent events
EXPERIMENTAL: this API is not stable yet.
A server-side function to generate of Server-sent events.
Server-sent event generator
Add a handler on an endpoint, that serves server-sent events.
The callback is given a generator that can be used to send events as it pleases. The connection is always closed by the client, and the accepted method is always GET. This will set the header "content-type" to "text/event-stream" automatically and reply with a 200 immediately. See server_sent_generator for more details.
This handler stays on the original thread (it is synchronous).
Run the server
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) Stdlib.resultRun 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.
Utils
Static directory serving
\ No newline at end of file
diff --git a/0.12/tiny_httpd/Tiny_httpd_buf/.dummy b/0.12/tiny_httpd/Tiny_httpd_buf/.dummy
deleted file mode 100644
index e69de29b..00000000
diff --git a/0.12/tiny_httpd/Tiny_httpd_buf/index.html b/0.12/tiny_httpd/Tiny_httpd_buf/index.html
deleted file mode 100644
index badc7892..00000000
--- a/0.12/tiny_httpd/Tiny_httpd_buf/index.html
+++ /dev/null
@@ -1,2 +0,0 @@
-
-Middlewares
A middleware can be inserted in a handler to modify or observe its behavior.
Handlers are functions returning a response to a request. The response can be delayed, hence the use of a continuation as the resp parameter.
A middleware is a handler transformation.
It takes the existing handler h, and returns a new one which, given a query, modify it or log it before passing it to h, or fail. It can also log or modify or drop the response.
Trivial middleware that does nothing.
\ No newline at end of file
diff --git a/0.12/tiny_httpd/Tiny_httpd_server/Request/index.html b/0.12/tiny_httpd/Tiny_httpd_server/Request/index.html
deleted file mode 100644
index 699b6e2a..00000000
--- a/0.12/tiny_httpd/Tiny_httpd_server/Request/index.html
+++ /dev/null
@@ -1,2 +0,0 @@
-
-Methods
module Meth : sig ... endHeaders are metadata associated with a request or response.
Requests
Requests are sent by a client, e.g. a web browser or cURL.
Response Codes
Responses
Responses are what a http server, such as Tiny_httpd, send back to the client to answer a Request.t
module Route : sig ... endMain Server type
A HTTP server. See create for more details.
val create : ?masksigpipe:bool -> ?max_connections:int -> ?timeout:float ->
-?buf_size:int -> ?get_time_s:(unit -> float) -> ?new_thread:((unit -> unit) -> unit) ->
-?addr:string -> ?port:int -> ?sock:Unix.file_descr ->
-?middlewares:([ `Encoding | `Stage of int ] * Middleware.t) list -> unit -> tCreate 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.
Address on which the server listens.
is_ipv6 server returns true iff the address of the server is an IPv6 address.
Port on which the server listens.
val active_connections : t -> intNumber of active connections
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.
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 query with only its headers, as well as the current response.
val add_middleware : stage:[ `Encoding | `Stage of int ] -> t -> Middleware.t -> unitAdd a middleware to every request/response pair.
Request handlers
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.
add_route_handler server Route.(exact "path" @/ string @/ int @/ return) f calls f "foo" 42 request when a request with path "path/foo/42/" is received.
Note that the handlers are called in the reverse order of their addition, so the last registered handler can override previously registered ones.
Similar to add_route_handler, but where the body of the request is a stream of bytes that has not been read yet. This is useful when one wants to stream the body directly into a parser, json decoder (such as Jsonm) or into a file.
Server-sent events
EXPERIMENTAL: this API is not stable yet.
A server-side function to generate of Server-sent events.
Server-sent event generator
Add a handler on an endpoint, that serves server-sent events.
The callback is given a generator that can be used to send events as it pleases. The connection is always closed by the client, and the accepted method is always GET. This will set the header "content-type" to "text/event-stream" automatically and reply with a 200 immediately. See server_sent_generator for more details.
This handler stays on the original thread (it is synchronous).
Run the server
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) Stdlib.resultRun 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
diff --git a/0.12/tiny_httpd/Tiny_httpd_server/module-type-SERVER_SENT_GENERATOR/index.html b/0.12/tiny_httpd/Tiny_httpd_server/module-type-SERVER_SENT_GENERATOR/index.html
deleted file mode 100644
index 572b867a..00000000
--- a/0.12/tiny_httpd/Tiny_httpd_server/module-type-SERVER_SENT_GENERATOR/index.html
+++ /dev/null
@@ -1,3 +0,0 @@
-
-Library tiny_httpd
This library exposes the following toplevel modules:
\ No newline at end of file
diff --git a/0.12/tiny_httpd_camlzip/Tiny_httpd_camlzip/.dummy b/0.12/tiny_httpd_camlzip/Tiny_httpd_camlzip/.dummy
deleted file mode 100644
index e69de29b..00000000
diff --git a/0.12/tiny_httpd_camlzip/Tiny_httpd_camlzip/index.html b/0.12/tiny_httpd_camlzip/Tiny_httpd_camlzip/index.html
deleted file mode 100644
index 2f89312f..00000000
--- a/0.12/tiny_httpd_camlzip/Tiny_httpd_camlzip/index.html
+++ /dev/null
@@ -1,2 +0,0 @@
-
-Library tiny_httpd_camlzip
The entry point of this library is the module: Tiny_httpd_camlzip.
\ No newline at end of file
diff --git a/0.2/highlight.pack.js b/0.2/highlight.pack.js
deleted file mode 100644
index 2e55d491..00000000
--- a/0.2/highlight.pack.js
+++ /dev/null
@@ -1,2 +0,0 @@
-/*! highlight.js v9.15.8 | BSD3 License | git.io/hljslicense */
-!function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"!=typeof exports?e(exports):n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs}))}(function(a){var f=[],u=Object.keys,N={},c={},n=/^(no-?highlight|plain|text)$/i,s=/\blang(?:uage)?-([\w-]+)\b/i,t=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,r={case_insensitive:"cI",lexemes:"l",contains:"c",keywords:"k",subLanguage:"sL",className:"cN",begin:"b",beginKeywords:"bK",end:"e",endsWithParent:"eW",illegal:"i",excludeBegin:"eB",excludeEnd:"eE",returnBegin:"rB",returnEnd:"rE",relevance:"r",variants:"v",IDENT_RE:"IR",UNDERSCORE_IDENT_RE:"UIR",NUMBER_RE:"NR",C_NUMBER_RE:"CNR",BINARY_NUMBER_RE:"BNR",RE_STARTERS_RE:"RSR",BACKSLASH_ESCAPE:"BE",APOS_STRING_MODE:"ASM",QUOTE_STRING_MODE:"QSM",PHRASAL_WORDS_MODE:"PWM",C_LINE_COMMENT_MODE:"CLCM",C_BLOCK_COMMENT_MODE:"CBCM",HASH_COMMENT_MODE:"HCM",NUMBER_MODE:"NM",C_NUMBER_MODE:"CNM",BINARY_NUMBER_MODE:"BNM",CSS_NUMBER_MODE:"CSSNM",REGEXP_MODE:"RM",TITLE_MODE:"TM",UNDERSCORE_TITLE_MODE:"UTM",COMMENT:"C",beginRe:"bR",endRe:"eR",illegalRe:"iR",lexemesRe:"lR",terminators:"t",terminator_end:"tE"},b="",h={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0};function _(e){return e.replace(/&/g,"&").replace(//g,">")}function E(e){return e.nodeName.toLowerCase()}function v(e,n){var t=e&&e.exec(n);return t&&0===t.index}function l(e){return n.test(e)}function g(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach(function(e){for(n in e)t[n]=e[n]}),t}function R(e){var a=[];return function e(n,t){for(var r=n.firstChild;r;r=r.nextSibling)3===r.nodeType?t+=r.nodeValue.length:1===r.nodeType&&(a.push({event:"start",offset:t,node:r}),t=e(r,t),E(r).match(/br|hr|img|input/)||a.push({event:"stop",offset:t,node:r}));return t}(e,0),a}function i(e){if(r&&!e.langApiRestored){for(var n in e.langApiRestored=!0,r)e[n]&&(e[r[n]]=e[n]);(e.c||[]).concat(e.v||[]).forEach(i)}}function m(o){function s(e){return e&&e.source||e}function c(e,n){return new RegExp(s(e),"m"+(o.cI?"i":"")+(n?"g":""))}!function n(t,e){if(!t.compiled){if(t.compiled=!0,t.k=t.k||t.bK,t.k){function r(t,e){o.cI&&(e=e.toLowerCase()),e.split(" ").forEach(function(e){var n=e.split("|");a[n[0]]=[t,n[1]?Number(n[1]):1]})}var a={};"string"==typeof t.k?r("keyword",t.k):u(t.k).forEach(function(e){r(e,t.k[e])}),t.k=a}t.lR=c(t.l||/\w+/,!0),e&&(t.bK&&(t.b="\\b("+t.bK.split(" ").join("|")+")\\b"),t.b||(t.b=/\B|\b/),t.bR=c(t.b),t.endSameAsBegin&&(t.e=t.b),t.e||t.eW||(t.e=/\B|\b/),t.e&&(t.eR=c(t.e)),t.tE=s(t.e)||"",t.eW&&e.tE&&(t.tE+=(t.e?"|":"")+e.tE)),t.i&&(t.iR=c(t.i)),null==t.r&&(t.r=1),t.c||(t.c=[]),t.c=Array.prototype.concat.apply([],t.c.map(function(e){return function(n){return n.v&&!n.cached_variants&&(n.cached_variants=n.v.map(function(e){return g(n,{v:null},e)})),n.cached_variants||n.eW&&[g(n)]||[n]}("self"===e?t:e)})),t.c.forEach(function(e){n(e,t)}),t.starts&&n(t.starts,e);var i=t.c.map(function(e){return e.bK?"\\.?(?:"+e.b+")\\.?":e.b}).concat([t.tE,t.i]).map(s).filter(Boolean);t.t=i.length?c(function(e,n){for(var t=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./,r=0,a="",i=0;i