add example for Writer.t response body

This commit is contained in:
Simon Cruanes 2023-07-18 12:50:15 -04:00
parent 6137c20801
commit 355cc4d004
No known key found for this signature in database
GPG key ID: EBFFF6F283F3A2B4
4 changed files with 96 additions and 0 deletions

View file

@ -105,6 +105,32 @@ it allows downloading the files, and listing directories.
If a directory contains `index.html` then this will be served
instead of listing the content.
## Steaming response body
Tiny_httpd provides multiple ways of returning a body in a response.
The response body type is:
```ocaml
type body =
[ `String of string
| `Stream of byte_stream
| `Writer of Tiny_httpd_io.Writer.t
| `Void ]
```
The simplest way is to return, say, `` `String "hello" ``. The response
will have a set content-length header and its body is just the string.
Some responses don't have a body at all, which is where `` `Void `` is useful.
The `` `Stream _ `` case is more advanced and really only intended for experts.
The `` `Writer w `` is new, and is intended as an easy way to write the
body in a streaming fashion. See 'examples/writer.ml' to see a full example.
Typically the idea is to create the body with `Tiny_httpd_io.Writer.make ~write ()`
where `write` will be called with an output channel (the connection to the client),
and can write whatever it wants to this channel. Once the `write` function returns
the body has been fully sent and the next request can be processed.
## Socket activation
Since version 0.10, socket activation is supported indirectly, by allowing a

View file

@ -21,6 +21,12 @@
(libraries tiny_httpd tiny_httpd_camlzip
tiny_httpd_eio eio eio_posix))
(executable
(name writer)
(flags :standard -warn-error -a+8)
(modules writer)
(libraries tiny_httpd))
(rule
(targets test_output.txt)
(deps

62
examples/writer.ml Normal file
View file

@ -0,0 +1,62 @@
module H = Tiny_httpd
let serve_zeroes server : unit =
H.add_route_handler server H.(Route.(exact "zeroes" @/ int @/ return))
@@ fun n _req ->
(* stream [n] zeroes *)
let write (oc : H.IO.Out_channel.t) : unit =
let buf = Bytes.make 1 '0' in
for _i = 1 to n do
H.IO.Out_channel.output oc buf 0 1
done
in
let writer = H.IO.Writer.make ~write () in
H.Response.make_writer @@ Ok writer
let serve_file server : unit =
H.add_route_handler server H.(Route.(exact "file" @/ string @/ return))
@@ fun file _req ->
if Sys.file_exists file then (
(* stream the content of the file *)
let write oc =
let buf = Bytes.create 4096 in
let ic = open_in file in
Fun.protect ~finally:(fun () -> close_in_noerr ic) @@ fun () ->
while
let n = input ic buf 0 (Bytes.length buf) in
if n > 0 then H.IO.Out_channel.output oc buf 0 n;
n > 0
do
()
done
in
let writer = H.IO.Writer.make ~write () in
H.Response.make_writer @@ Ok writer
) else
H.Response.fail ~code:404 "file not found"
let () =
let port = ref 8085 in
Arg.parse [ "-p", Arg.Set_int port, " port" ] ignore "";
let server = H.create ~port:!port () in
Printf.printf "listen on http://localhost:%d/\n%!" !port;
serve_file server;
serve_zeroes server;
H.add_route_handler server H.Route.return (fun _req ->
let body =
H.Html.(
div []
[
p [] [ txt "routes" ];
ul []
[
li []
[ a [ A.href "/zeroes/1000" ] [ txt "get 1000 zeroes" ] ];
li [] [ a [ A.href "/file/f_13M" ] [ txt "read file" ] ];
];
])
|> H.Html.to_string_top
in
H.Response.make_string @@ Ok body);
H.run_exn server

2
writer.sh Executable file
View file

@ -0,0 +1,2 @@
#!/bin/sh
exec dune exec --display=quiet -- examples/writer.exe $@