mirror of
https://github.com/c-cube/tiny_httpd.git
synced 2025-12-06 03:05:29 -05:00
there's already a buffer nearby, we can use it to write streams/writer bodies to the output channel
309 lines
8.3 KiB
OCaml
309 lines
8.3 KiB
OCaml
module Buf = Tiny_httpd_buf
|
|
module IO = Tiny_httpd_io
|
|
|
|
let spf = Printf.sprintf
|
|
|
|
type hidden = unit
|
|
|
|
type t = {
|
|
mutable bs: bytes;
|
|
mutable off: int;
|
|
mutable len: int;
|
|
fill_buf: unit -> unit;
|
|
consume: int -> unit;
|
|
close: unit -> unit;
|
|
_rest: hidden;
|
|
}
|
|
|
|
let[@inline] close self = self.close ()
|
|
|
|
let empty =
|
|
{
|
|
bs = Bytes.empty;
|
|
off = 0;
|
|
len = 0;
|
|
fill_buf = ignore;
|
|
consume = ignore;
|
|
close = ignore;
|
|
_rest = ();
|
|
}
|
|
|
|
let make ?(bs = Bytes.create @@ (16 * 1024)) ?(close = ignore) ~consume ~fill ()
|
|
: t =
|
|
let rec self =
|
|
{
|
|
bs;
|
|
off = 0;
|
|
len = 0;
|
|
close = (fun () -> close self);
|
|
fill_buf = (fun () -> if self.len = 0 then fill self);
|
|
consume =
|
|
(fun n ->
|
|
assert (n <= self.len);
|
|
consume self n);
|
|
_rest = ();
|
|
}
|
|
in
|
|
self
|
|
|
|
let of_input ?(buf_size = 16 * 1024) (ic : IO.In_channel.t) : t =
|
|
make ~bs:(Bytes.create buf_size)
|
|
~close:(fun _ -> IO.In_channel.close ic)
|
|
~consume:(fun self n ->
|
|
self.off <- self.off + n;
|
|
self.len <- self.len - n)
|
|
~fill:(fun self ->
|
|
if self.off >= self.len then (
|
|
self.off <- 0;
|
|
self.len <- IO.In_channel.input ic self.bs 0 (Bytes.length self.bs)
|
|
))
|
|
()
|
|
|
|
let of_chan_ ?buf_size ic ~close_noerr : t =
|
|
let inc = IO.In_channel.of_in_channel ~close_noerr ic in
|
|
of_input ?buf_size inc
|
|
|
|
let of_chan ?buf_size ic = of_chan_ ?buf_size ic ~close_noerr:false
|
|
let of_chan_close_noerr ?buf_size ic = of_chan_ ?buf_size ic ~close_noerr:true
|
|
|
|
let of_fd_ ?buf_size ~close_noerr ic : t =
|
|
let inc = IO.In_channel.of_unix_fd ~close_noerr ic in
|
|
of_input ?buf_size inc
|
|
|
|
let of_fd ?buf_size fd : t = of_fd_ ?buf_size ~close_noerr:false fd
|
|
let of_fd_close_noerr ?buf_size fd : t = of_fd_ ?buf_size ~close_noerr:true fd
|
|
|
|
let rec iter f (self : t) : unit =
|
|
self.fill_buf ();
|
|
if self.len = 0 then
|
|
self.close ()
|
|
else (
|
|
f self.bs self.off self.len;
|
|
self.consume self.len;
|
|
(iter [@tailcall]) f self
|
|
)
|
|
|
|
let to_chan (oc : out_channel) (self : t) = iter (output oc) self
|
|
|
|
let to_chan' (oc : IO.Out_channel.t) (self : t) =
|
|
iter (IO.Out_channel.output oc) self
|
|
|
|
let to_writer (self : t) : Tiny_httpd_io.Writer.t =
|
|
{ write = (fun oc -> to_chan' oc self) }
|
|
|
|
let of_bytes ?(i = 0) ?len (bs : bytes) : t =
|
|
(* invariant: !i+!len is constant *)
|
|
let len =
|
|
match len with
|
|
| Some n ->
|
|
if n > Bytes.length bs - i then invalid_arg "Byte_stream.of_bytes";
|
|
n
|
|
| None -> Bytes.length bs - i
|
|
in
|
|
let self =
|
|
make ~bs ~fill:ignore
|
|
~close:(fun self -> self.len <- 0)
|
|
~consume:(fun self n ->
|
|
assert (n >= 0 && n <= self.len);
|
|
self.off <- n + self.off;
|
|
self.len <- self.len - n)
|
|
()
|
|
in
|
|
self.off <- i;
|
|
self.len <- len;
|
|
self
|
|
|
|
let of_string s : t = of_bytes (Bytes.unsafe_of_string s)
|
|
|
|
let with_file ?buf_size file f =
|
|
let ic = Unix.(openfile file [ O_RDONLY ] 0) in
|
|
try
|
|
let x = f (of_fd ?buf_size ic) in
|
|
Unix.close ic;
|
|
x
|
|
with e ->
|
|
Unix.close ic;
|
|
raise e
|
|
|
|
let read_all ?(buf = Buf.create ()) (self : t) : string =
|
|
let continue = ref true in
|
|
while !continue do
|
|
self.fill_buf ();
|
|
if self.len > 0 then (
|
|
Buf.add_bytes buf self.bs self.off self.len;
|
|
self.consume self.len
|
|
);
|
|
assert (self.len >= 0);
|
|
if self.len = 0 then continue := false
|
|
done;
|
|
Buf.contents_and_clear buf
|
|
|
|
(* put [n] bytes from the input into bytes *)
|
|
let read_exactly_ ~too_short (self : t) (bytes : bytes) (n : int) : unit =
|
|
assert (Bytes.length bytes >= n);
|
|
let offset = ref 0 in
|
|
while !offset < n do
|
|
self.fill_buf ();
|
|
let n_read = min self.len (n - !offset) in
|
|
Bytes.blit self.bs self.off bytes !offset n_read;
|
|
offset := !offset + n_read;
|
|
self.consume n_read;
|
|
if n_read = 0 then too_short ()
|
|
done
|
|
|
|
(* read a line into the buffer, after clearing it. *)
|
|
let read_line_into (self : t) ~buf : unit =
|
|
Buf.clear buf;
|
|
let continue = ref true in
|
|
while !continue do
|
|
self.fill_buf ();
|
|
if self.len = 0 then (
|
|
continue := false;
|
|
if Buf.size buf = 0 then raise End_of_file
|
|
);
|
|
let j = ref self.off in
|
|
while !j < self.off + self.len && Bytes.get self.bs !j <> '\n' do
|
|
incr j
|
|
done;
|
|
if !j - self.off < self.len then (
|
|
assert (Bytes.get self.bs !j = '\n');
|
|
Buf.add_bytes buf self.bs self.off (!j - self.off);
|
|
(* without \n *)
|
|
self.consume (!j - self.off + 1);
|
|
(* remove \n *)
|
|
continue := false
|
|
) else (
|
|
Buf.add_bytes buf self.bs self.off self.len;
|
|
self.consume self.len
|
|
)
|
|
done
|
|
|
|
(* new stream with maximum size [max_size].
|
|
@param close_rec if true, closing this will also close the input stream
|
|
@param too_big called with read size if the max size is reached *)
|
|
let limit_size_to ~close_rec ~max_size ~too_big (arg : t) : t =
|
|
let size = ref 0 in
|
|
let continue = ref true in
|
|
make ~bs:Bytes.empty
|
|
~close:(fun _ -> if close_rec then arg.close ())
|
|
~fill:(fun res ->
|
|
if res.len = 0 && !continue then (
|
|
arg.fill_buf ();
|
|
res.bs <- arg.bs;
|
|
res.off <- arg.off;
|
|
res.len <- arg.len
|
|
) else (
|
|
arg.bs <- Bytes.empty;
|
|
arg.off <- 0;
|
|
arg.len <- 0
|
|
))
|
|
~consume:(fun res n ->
|
|
size := !size + n;
|
|
if !size > max_size then (
|
|
continue := false;
|
|
too_big !size
|
|
) else (
|
|
arg.consume n;
|
|
res.off <- res.off + n;
|
|
res.len <- res.len - n
|
|
))
|
|
()
|
|
|
|
(* read exactly [size] bytes from the stream *)
|
|
let read_exactly ~close_rec ~size ~too_short (arg : t) : t =
|
|
if size = 0 then
|
|
empty
|
|
else (
|
|
let size = ref size in
|
|
make ~bs:Bytes.empty
|
|
~fill:(fun res ->
|
|
(* must not block on [arg] if we're done *)
|
|
if !size = 0 then (
|
|
res.bs <- Bytes.empty;
|
|
res.off <- 0;
|
|
res.len <- 0
|
|
) else (
|
|
arg.fill_buf ();
|
|
res.bs <- arg.bs;
|
|
res.off <- arg.off;
|
|
let len = min arg.len !size in
|
|
if len = 0 && !size > 0 then too_short !size;
|
|
res.len <- len
|
|
))
|
|
~close:(fun _res ->
|
|
(* close underlying stream if [close_rec] *)
|
|
if close_rec then arg.close ();
|
|
size := 0)
|
|
~consume:(fun res n ->
|
|
let n = min n !size in
|
|
size := !size - n;
|
|
arg.consume n;
|
|
res.off <- res.off + n;
|
|
res.len <- res.len - n)
|
|
()
|
|
)
|
|
|
|
let read_line ?(buf = Buf.create ()) self : string =
|
|
read_line_into self ~buf;
|
|
Buf.contents buf
|
|
|
|
let read_chunked ?(buf = Buf.create ()) ~fail (bs : t) : t =
|
|
let first = ref true in
|
|
let read_next_chunk_len () : int =
|
|
if !first then
|
|
first := false
|
|
else (
|
|
let line = read_line ~buf bs in
|
|
if String.trim line <> "" then raise (fail "expected crlf between chunks")
|
|
);
|
|
let line = read_line ~buf bs in
|
|
(* parse chunk length, ignore extensions *)
|
|
let chunk_size =
|
|
if String.trim line = "" then
|
|
0
|
|
else (
|
|
try Scanf.sscanf line "%x %s@\r" (fun n _ext -> n)
|
|
with _ ->
|
|
raise (fail (spf "cannot read chunk size from line %S" line))
|
|
)
|
|
in
|
|
chunk_size
|
|
in
|
|
let refill = ref true in
|
|
let chunk_size = ref 0 in
|
|
make
|
|
~bs:(Bytes.create (16 * 4096))
|
|
~fill:(fun self ->
|
|
(* do we need to refill? *)
|
|
if self.off >= self.len then (
|
|
if !chunk_size = 0 && !refill then chunk_size := read_next_chunk_len ();
|
|
self.off <- 0;
|
|
self.len <- 0;
|
|
if !chunk_size > 0 then (
|
|
(* read the whole chunk, or [Bytes.length bytes] of it *)
|
|
let to_read = min !chunk_size (Bytes.length self.bs) in
|
|
read_exactly_
|
|
~too_short:(fun () -> raise (fail "chunk is too short"))
|
|
bs self.bs to_read;
|
|
self.len <- to_read;
|
|
chunk_size := !chunk_size - to_read
|
|
) else
|
|
refill := false (* stream is finished *)
|
|
))
|
|
~consume:(fun self n ->
|
|
self.off <- self.off + n;
|
|
self.len <- self.len - n)
|
|
~close:(fun self ->
|
|
(* close this overlay, do not close underlying stream *)
|
|
self.len <- 0;
|
|
refill := false)
|
|
()
|
|
|
|
let output_chunked' ?buf (oc : IO.Out_channel.t) (self : t) : unit =
|
|
let oc' = IO.Out_channel.chunk_encoding ?buf oc ~close_rec:false in
|
|
to_chan' oc' self;
|
|
IO.Out_channel.close oc'
|
|
|
|
(* print a stream as a series of chunks *)
|
|
let output_chunked ?buf (oc : out_channel) (self : t) : unit =
|
|
output_chunked' ?buf (IO.Out_channel.of_out_channel oc) self
|