mirror of
https://github.com/c-cube/tiny_httpd.git
synced 2025-12-06 11:15:35 -05:00
52 lines
No EOL
24 KiB
HTML
52 lines
No EOL
24 KiB
HTML
<!DOCTYPE html>
|
||
<html xmlns="http://www.w3.org/1999/xhtml"><head><title>Tiny_httpd (tiny_httpd.Tiny_httpd)</title><link rel="stylesheet" href="../../odoc.css"/><meta charset="utf-8"/><meta name="generator" content="odoc 1.5.2"/><meta name="viewport" content="width=device-width,initial-scale=1.0"/><script src="../../highlight.pack.js"></script><script>hljs.initHighlightingOnLoad();</script></head><body><div class="content"><header><nav><a href="../index.html">Up</a> – <a href="../index.html">tiny_httpd</a> » Tiny_httpd</nav><h1>Module <code>Tiny_httpd</code></h1><h2 id="tiny-http-server"><a href="#tiny-http-server" class="anchor"></a>Tiny Http Server</h2><p>This library implements a very simple, basic HTTP/1.1 server using blocking IOs and threads. Basic routing based on <code>Scanf</code> is provided for convenience, so that several handlers can be registered.</p><p>It is possible to use a thread pool, see <a href="index.html#val-create"><code>create</code></a>'s argument <code>new_thread</code>.</p><p>The <code>echo</code> example (see <code>src/examples/echo.ml</code>) demonstrates some of the features by declaring a few endpoints, including one for uploading files:</p><pre><code class="ml">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</code></pre><p>It is then possible to query it using curl:</p><pre><code class="ml">$ 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"}</code></pre><nav class="toc"><ul><li><a href="#tiny-buffer-implementation">Tiny buffer implementation</a></li><li><a href="#generic-stream-of-data">Generic stream of data</a></li><li><a href="#methods">Methods</a></li><li><a href="#headers">Headers</a></li><li><a href="#requests">Requests</a></li><li><a href="#response-codes">Response Codes</a></li><li><a href="#responses">Responses</a></li><li><a href="#main-server-type">Main Server type</a></li><li><a href="#request-handlers">Request handlers</a></li><li><a href="#server-sent-events">Server-sent events</a></li><li><a href="#run-the-server">Run the server</a></li></ul></nav></header><section><header><h3 id="tiny-buffer-implementation"><a href="#tiny-buffer-implementation" class="anchor"></a>Tiny buffer implementation</h3><p>These buffers are used to avoid allocating too many byte arrays when processing streams and parsing requests.</p></header><div class="spec module" id="module-Buf_"><a href="#module-Buf_" class="anchor"></a><code><span class="keyword">module</span> <a href="Buf_/index.html">Buf_</a> : <span class="keyword">sig</span> ... <span class="keyword">end</span></code></div></section><section><header><h3 id="generic-stream-of-data"><a href="#generic-stream-of-data" class="anchor"></a>Generic stream of data</h3><p>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.</p></header><dl><dt class="spec type" id="type-byte_stream"><a href="#type-byte_stream" class="anchor"></a><code><span class="keyword">type</span> byte_stream</code><code> = </code><code>{</code><table class="record"><tr id="type-byte_stream.bs_fill_buf" class="anchored"><td class="def field"><a href="#type-byte_stream.bs_fill_buf" class="anchor"></a><code>bs_fill_buf : unit <span>-></span> bytes * int * int;</code></td><td class="doc"><p>See the current slice of the internal buffer as <code>bytes, i, len</code>, where the slice is <code>bytes[i] .. [bytes[i+len-1]]</code>. Can block to refill the buffer if there is currently no content. If <code>len=0</code> then there is no more data.</p></td></tr><tr id="type-byte_stream.bs_consume" class="anchored"><td class="def field"><a href="#type-byte_stream.bs_consume" class="anchor"></a><code>bs_consume : int <span>-></span> unit;</code></td><td class="doc"><p>Consume n bytes from the buffer. This should only be called with <code>n <= len</code> after a call to <code>is_fill_buf</code> that returns a slice of length <code>len</code>.</p></td></tr><tr id="type-byte_stream.bs_close" class="anchored"><td class="def field"><a href="#type-byte_stream.bs_close" class="anchor"></a><code>bs_close : unit <span>-></span> unit;</code></td><td class="doc"><p>Close the stream.</p></td></tr></table><code>}</code></dt><dd><p>A buffered stream, with a view into the current buffer (or refill if empty), and a function to consume <code>n</code> bytes. See <a href="Byte_stream/index.html"><code>Byte_stream</code></a> for more details.</p></dd></dl><div class="spec module" id="module-Byte_stream"><a href="#module-Byte_stream" class="anchor"></a><code><span class="keyword">module</span> <a href="Byte_stream/index.html">Byte_stream</a> : <span class="keyword">sig</span> ... <span class="keyword">end</span></code></div></section><section><header><h3 id="methods"><a href="#methods" class="anchor"></a>Methods</h3></header><div class="spec module" id="module-Meth"><a href="#module-Meth" class="anchor"></a><code><span class="keyword">module</span> <a href="Meth/index.html">Meth</a> : <span class="keyword">sig</span> ... <span class="keyword">end</span></code></div></section><section><header><h3 id="headers"><a href="#headers" class="anchor"></a>Headers</h3><p>Headers are metadata associated with a request or response.</p></header><div class="spec module" id="module-Headers"><a href="#module-Headers" class="anchor"></a><code><span class="keyword">module</span> <a href="Headers/index.html">Headers</a> : <span class="keyword">sig</span> ... <span class="keyword">end</span></code></div></section><section><header><h3 id="requests"><a href="#requests" class="anchor"></a>Requests</h3><p>Requests are sent by a client, e.g. a web browser or cURL.</p></header><div class="spec module" id="module-Request"><a href="#module-Request" class="anchor"></a><code><span class="keyword">module</span> <a href="Request/index.html">Request</a> : <span class="keyword">sig</span> ... <span class="keyword">end</span></code></div></section><section><header><h3 id="response-codes"><a href="#response-codes" class="anchor"></a>Response Codes</h3></header><div class="spec module" id="module-Response_code"><a href="#module-Response_code" class="anchor"></a><code><span class="keyword">module</span> <a href="Response_code/index.html">Response_code</a> : <span class="keyword">sig</span> ... <span class="keyword">end</span></code></div></section><section><header><h3 id="responses"><a href="#responses" class="anchor"></a>Responses</h3><p>Responses are what a http server, such as <a href="index.html"><code>Tiny_httpd</code></a>, send back to the client to answer a <a href="Request/index.html#type-t"><code>Request.t</code></a></p></header><div class="spec module" id="module-Response"><a href="#module-Response" class="anchor"></a><code><span class="keyword">module</span> <a href="Response/index.html">Response</a> : <span class="keyword">sig</span> ... <span class="keyword">end</span></code></div><dl><dt class="spec module" id="module-Route"><a href="#module-Route" class="anchor"></a><code><span class="keyword">module</span> <a href="Route/index.html">Route</a> : <span class="keyword">sig</span> ... <span class="keyword">end</span></code></dt><dd></dd></dl></section><section><header><h3 id="main-server-type"><a href="#main-server-type" class="anchor"></a>Main Server type</h3></header><dl><dt class="spec type" id="type-t"><a href="#type-t" class="anchor"></a><code><span class="keyword">type</span> t</code></dt><dd><p>A HTTP server. See <a href="index.html#val-create"><code>create</code></a> for more details.</p></dd></dl><dl><dt class="spec value" id="val-create"><a href="#val-create" class="anchor"></a><code><span class="keyword">val</span> create : <span>?⁠masksigpipe:bool</span> <span>-></span> <span>?⁠max_connections:int</span> <span>-></span> <span>?⁠new_thread:<span>(<span>(unit <span>-></span> unit)</span> <span>-></span> unit)</span></span> <span>-></span> <span>?⁠addr:string</span> <span>-></span> <span>?⁠port:int</span> <span>-></span> unit <span>-></span> <a href="index.html#type-t">t</a></code></dt><dd><p>Create a new webserver.</p><p>The server will not do anything until <a href="index.html#val-run"><code>run</code></a> is called on it. Before starting the server, one can use <a href="index.html#val-add_path_handler"><code>add_path_handler</code></a> and <a href="index.html#val-set_top_handler"><code>set_top_handler</code></a> to specify how to handle incoming requests.</p><dl><dt>parameter masksigpipe</dt><dd><p>if true, block the signal <span class="xref-unresolved" title="unresolved reference to "Sys.sigpipe""><code>Sys</code>.sigpipe</span> which otherwise tends to kill client threads when they try to write on broken sockets. Default: <code>true</code>.</p></dd></dl><dl><dt>parameter new_thread</dt><dd><p>a function used to spawn a new thread to handle a new client connection. By default it is <span class="xref-unresolved" title="unresolved reference to "Thread.create""><code>Thread</code>.create</span> but one could use a thread pool instead.</p></dd></dl><dl><dt>parameter max_connections</dt><dd><p>maximum number of simultaneous connections.</p></dd></dl><dl><dt>parameter addr</dt><dd><p>address (IPv4 or IPv6) to listen on. Default <code>"127.0.0.1"</code>.</p></dd></dl><dl><dt>parameter port</dt><dd><p>to listen on. Default <code>8080</code>.</p></dd></dl></dd></dl><dl><dt class="spec value" id="val-addr"><a href="#val-addr" class="anchor"></a><code><span class="keyword">val</span> addr : <a href="index.html#type-t">t</a> <span>-></span> string</code></dt><dd><p>Address on which the server listens.</p></dd></dl><dl><dt class="spec value" id="val-is_ipv6"><a href="#val-is_ipv6" class="anchor"></a><code><span class="keyword">val</span> is_ipv6 : <a href="index.html#type-t">t</a> <span>-></span> bool</code></dt><dd><p><code>is_ipv6 server</code> returns <code>true</code> iff the address of the server is an IPv6 address.</p><dl><dt>since</dt><dd>0.3</dd></dl></dd></dl><dl><dt class="spec value" id="val-port"><a href="#val-port" class="anchor"></a><code><span class="keyword">val</span> port : <a href="index.html#type-t">t</a> <span>-></span> int</code></dt><dd><p>Port on which the server listens.</p></dd></dl><dl><dt class="spec value" id="val-add_decode_request_cb"><a href="#val-add_decode_request_cb" class="anchor"></a><code><span class="keyword">val</span> add_decode_request_cb : <a href="index.html#type-t">t</a> <span>-></span> <span>(<span>unit <a href="Request/index.html#type-t">Request.t</a></span> <span>-></span> <span><span>(<span>unit <a href="Request/index.html#type-t">Request.t</a></span> * <span>(<a href="index.html#type-byte_stream">byte_stream</a> <span>-></span> <a href="index.html#type-byte_stream">byte_stream</a>)</span>)</span> option</span>)</span> <span>-></span> unit</code></dt><dd><p>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 <code>Transfer-Encoding</code> header and returning a stream transformer that decompresses on the fly.</p></dd></dl><dl><dt class="spec value" id="val-add_encode_response_cb"><a href="#val-add_encode_response_cb" class="anchor"></a><code><span class="keyword">val</span> add_encode_response_cb : <a href="index.html#type-t">t</a> <span>-></span> <span>(<span>unit <a href="Request/index.html#type-t">Request.t</a></span> <span>-></span> <a href="Response/index.html#type-t">Response.t</a> <span>-></span> <span><a href="Response/index.html#type-t">Response.t</a> option</span>)</span> <span>-></span> unit</code></dt><dd><p>Add a callback for every request/response pair. Similarly to <a href="index.html#val-add_encode_response_cb"><code>add_encode_response_cb</code></a> 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.</p></dd></dl></section><section><header><h3 id="request-handlers"><a href="#request-handlers" class="anchor"></a>Request handlers</h3></header><dl><dt class="spec value" id="val-set_top_handler"><a href="#val-set_top_handler" class="anchor"></a><code><span class="keyword">val</span> set_top_handler : <a href="index.html#type-t">t</a> <span>-></span> <span>(<span>string <a href="Request/index.html#type-t">Request.t</a></span> <span>-></span> <a href="Response/index.html#type-t">Response.t</a>)</span> <span>-></span> unit</code></dt><dd><p>Setup a handler called by default.</p><p>This handler is called with any request not accepted by any handler installed via <a href="index.html#val-add_path_handler"><code>add_path_handler</code></a>. If no top handler is installed, unhandled paths will return a <code>404</code> not found.</p></dd></dl><dl><dt class="spec value" id="val-add_route_handler"><a href="#val-add_route_handler" class="anchor"></a><code><span class="keyword">val</span> add_route_handler : <span>?⁠accept:<span>(<span>unit <a href="Request/index.html#type-t">Request.t</a></span> <span>-></span> <span><span>(unit, <a href="Response_code/index.html#type-t">Response_code.t</a> * string)</span> Stdlib.result</span>)</span></span> <span>-></span> <span>?⁠meth:<a href="Meth/index.html#type-t">Meth.t</a></span> <span>-></span> <a href="index.html#type-t">t</a> <span>-></span> <span><span>(<span class="type-var">'a</span>, <span>string <a href="Request/index.html#type-t">Request.t</a></span> <span>-></span> <a href="Response/index.html#type-t">Response.t</a>)</span> <a href="Route/index.html#type-t">Route.t</a></span> <span>-></span> <span class="type-var">'a</span> <span>-></span> unit</code></dt><dd><p><code>add_route_handler server Route.(exact "path" @/ string @/ int @/ return) f</code> calls <code>f "foo" 42 request</code> when a <code>request</code> with path "path/foo/42/" is received.</p><p>Note that the handlers are called in the reverse order of their addition, so the last registered handler can override previously registered ones.</p><dl><dt>parameter meth</dt><dd><p>if provided, only accept requests with the given method. Typically one could react to <code>`GET</code> or <code>`PUT</code>.</p></dd></dl><dl><dt>parameter accept</dt><dd><p>should return <code>Ok()</code> if the given request (before its body is read) should be accepted, <code>Error (code,message)</code> if it's to be rejected (e.g. because its content is too big, or for some permission error). See the <code>http_of_dir</code> program for an example of how to use <code>accept</code> to filter uploads that are too large before the upload even starts. The default always returns <code>Ok()</code>, i.e. it accepts all requests.</p></dd></dl><dl><dt>since</dt><dd>0.6</dd></dl></dd></dl><dl><dt class="spec value" id="val-add_route_handler_stream"><a href="#val-add_route_handler_stream" class="anchor"></a><code><span class="keyword">val</span> add_route_handler_stream : <span>?⁠accept:<span>(<span>unit <a href="Request/index.html#type-t">Request.t</a></span> <span>-></span> <span><span>(unit, <a href="Response_code/index.html#type-t">Response_code.t</a> * string)</span> Stdlib.result</span>)</span></span> <span>-></span> <span>?⁠meth:<a href="Meth/index.html#type-t">Meth.t</a></span> <span>-></span> <a href="index.html#type-t">t</a> <span>-></span> <span><span>(<span class="type-var">'a</span>, <span><a href="index.html#type-byte_stream">byte_stream</a> <a href="Request/index.html#type-t">Request.t</a></span> <span>-></span> <a href="Response/index.html#type-t">Response.t</a>)</span> <a href="Route/index.html#type-t">Route.t</a></span> <span>-></span> <span class="type-var">'a</span> <span>-></span> unit</code></dt><dd><p>Similar to <a href="index.html#val-add_route_handler"><code>add_route_handler</code></a>, 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 <code>Jsonm</code>) or into a file.</p><dl><dt>since</dt><dd>0.6</dd></dl></dd></dl><dl><dt class="spec value" id="val-add_path_handler"><a href="#val-add_path_handler" class="anchor"></a><code><span class="keyword">val</span> add_path_handler : <span>?⁠accept:<span>(<span>unit <a href="Request/index.html#type-t">Request.t</a></span> <span>-></span> <span><span>(unit, <a href="Response_code/index.html#type-t">Response_code.t</a> * string)</span> Stdlib.result</span>)</span></span> <span>-></span> <span>?⁠meth:<a href="Meth/index.html#type-t">Meth.t</a></span> <span>-></span> <a href="index.html#type-t">t</a> <span>-></span> <span><span>(<span class="type-var">'a</span>, Stdlib.Scanf.Scanning.in_channel, <span class="type-var">'b</span>, <span class="type-var">'c</span> <span>-></span> <span>string <a href="Request/index.html#type-t">Request.t</a></span> <span>-></span> <a href="Response/index.html#type-t">Response.t</a>, <span class="type-var">'a</span> <span>-></span> <span class="type-var">'d</span>, <span class="type-var">'d</span>)</span> Stdlib.format6</span> <span>-></span> <span class="type-var">'c</span> <span>-></span> unit</code></dt><dd><p>Similar to <a href="index.html#val-add_route_handler"><code>add_route_handler</code></a> but based on scanf.</p><p>This uses <code>Scanf</code>'s splitting, which has some gotchas (in particular, <code>"%s"</code> is eager, so it's generally necessary to delimit its scope with a <code>"@/"</code> delimiter. The "@" before a character indicates it's a separator.</p><dl><dt>deprecated</dt><dd><p>use <a href="index.html#val-add_route_handler"><code>add_route_handler</code></a> instead.</p></dd></dl></dd></dl><dl><dt class="spec value" id="val-add_path_handler_stream"><a href="#val-add_path_handler_stream" class="anchor"></a><code><span class="keyword">val</span> add_path_handler_stream : <span>?⁠accept:<span>(<span>unit <a href="Request/index.html#type-t">Request.t</a></span> <span>-></span> <span><span>(unit, <a href="Response_code/index.html#type-t">Response_code.t</a> * string)</span> Stdlib.result</span>)</span></span> <span>-></span> <span>?⁠meth:<a href="Meth/index.html#type-t">Meth.t</a></span> <span>-></span> <a href="index.html#type-t">t</a> <span>-></span> <span><span>(<span class="type-var">'a</span>, Stdlib.Scanf.Scanning.in_channel, <span class="type-var">'b</span>, <span class="type-var">'c</span> <span>-></span> <span><a href="index.html#type-byte_stream">byte_stream</a> <a href="Request/index.html#type-t">Request.t</a></span> <span>-></span> <a href="Response/index.html#type-t">Response.t</a>, <span class="type-var">'a</span> <span>-></span> <span class="type-var">'d</span>, <span class="type-var">'d</span>)</span> Stdlib.format6</span> <span>-></span> <span class="type-var">'c</span> <span>-></span> unit</code></dt><dd><p>Similar to <a href="index.html#val-add_path_handler"><code>add_path_handler</code></a>, 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 <code>Jsonm</code>) or into a file.</p><dl><dt>since</dt><dd>0.3</dd></dl></dd></dl></section><section><header><h3 id="server-sent-events"><a href="#server-sent-events" class="anchor"></a>Server-sent events</h3><p><b>EXPERIMENTAL</b>: this API is not stable yet.</p></header><dl><dt class="spec module-type" id="module-type-SERVER_SENT_GENERATOR"><a href="#module-type-SERVER_SENT_GENERATOR" class="anchor"></a><code><span class="keyword">module</span> <span class="keyword">type</span> <a href="module-type-SERVER_SENT_GENERATOR/index.html">SERVER_SENT_GENERATOR</a> = <span class="keyword">sig</span> ... <span class="keyword">end</span></code></dt><dd><p>A server-side function to generate of Server-sent events.</p></dd></dl><dl><dt class="spec type" id="type-server_sent_generator"><a href="#type-server_sent_generator" class="anchor"></a><code><span class="keyword">type</span> server_sent_generator</code><code> = <span>(<span class="keyword">module</span> <a href="module-type-SERVER_SENT_GENERATOR/index.html">SERVER_SENT_GENERATOR</a>)</span></code></dt><dd><p>Server-sent event generator</p><dl><dt>since</dt><dd>0.9</dd></dl></dd></dl><dl><dt class="spec value" id="val-add_route_server_sent_handler"><a href="#val-add_route_server_sent_handler" class="anchor"></a><code><span class="keyword">val</span> add_route_server_sent_handler : <span>?⁠accept:<span>(<span>unit <a href="Request/index.html#type-t">Request.t</a></span> <span>-></span> <span><span>(unit, <a href="Response_code/index.html#type-t">Response_code.t</a> * string)</span> Stdlib.result</span>)</span></span> <span>-></span> <a href="index.html#type-t">t</a> <span>-></span> <span><span>(<span class="type-var">'a</span>, <span>string <a href="Request/index.html#type-t">Request.t</a></span> <span>-></span> <a href="index.html#type-server_sent_generator">server_sent_generator</a> <span>-></span> unit)</span> <a href="Route/index.html#type-t">Route.t</a></span> <span>-></span> <span class="type-var">'a</span> <span>-></span> unit</code></dt><dd><p>Add a handler on an endpoint, that serves server-sent events.</p><p>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 <code>GET</code>. This will set the header "content-type" to "text/event-stream" automatically and reply with a 200 immediately. See <a href="index.html#type-server_sent_generator"><code>server_sent_generator</code></a> for more details.</p><p>This handler stays on the original thread (it is synchronous).</p><dl><dt>since</dt><dd>0.9</dd></dl></dd></dl></section><section><header><h3 id="run-the-server"><a href="#run-the-server" class="anchor"></a>Run the server</h3></header><dl><dt class="spec value" id="val-stop"><a href="#val-stop" class="anchor"></a><code><span class="keyword">val</span> stop : <a href="index.html#type-t">t</a> <span>-></span> unit</code></dt><dd><p>Ask the server to stop. This might not have an immediate effect as <a href="index.html#val-run"><code>run</code></a> might currently be waiting on IO.</p></dd></dl><dl><dt class="spec value" id="val-run"><a href="#val-run" class="anchor"></a><code><span class="keyword">val</span> run : <a href="index.html#type-t">t</a> <span>-></span> <span><span>(unit, exn)</span> Stdlib.result</span></code></dt><dd><p>Run the main loop of the server, listening on a socket described at the server's creation time, using <code>new_thread</code> to start a thread for each new client.</p><p>This returns <code>Ok ()</code> if the server exits gracefully, or <code>Error e</code> if it exits with an error.</p></dd></dl></section></div></body></html> |