Skip to content

Commit

Permalink
improve docs and opam, tidy up for 0.1; rename stream
Browse files Browse the repository at this point in the history
  • Loading branch information
c-cube committed Nov 19, 2019
1 parent 8bfcf36 commit 20275c1
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 63 deletions.
93 changes: 47 additions & 46 deletions src/Tiny_httpd.ml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
type stream = {
is_fill_buf: unit -> (bytes * int * int);
is_consume: int -> unit;
is_close: unit -> unit;
type byte_stream = {
bs_fill_buf: unit -> (bytes * int * int);
bs_consume: int -> unit;
bs_close: unit -> unit;
}
(** A buffer input stream, with a view into the current buffer (or refill if empty),
and a function to consume [n] bytes *)
Expand Down Expand Up @@ -54,23 +54,23 @@ module Buf_ = struct
x
end

module Stream_ = struct
type t = stream
module Byte_stream = struct
type t = byte_stream

let close self = self.is_close()
let close self = self.bs_close()

let of_chan_ ~close ic : t =
let i = ref 0 in
let len = ref 0 in
let buf = Bytes.make 4096 ' ' in
{ is_fill_buf=(fun () ->
{ bs_fill_buf=(fun () ->
if !i >= !len then (
i := 0;
len := input ic buf 0 (Bytes.length buf);
);
buf, !i,!len - !i);
is_consume=(fun n -> i := !i + n);
is_close=(fun () -> close ic)
bs_consume=(fun n -> i := !i + n);
bs_close=(fun () -> close ic)
}

let of_chan = of_chan_ ~close:close_in
Expand All @@ -85,9 +85,9 @@ module Stream_ = struct
)
in
let i = ref i in
{ is_fill_buf=(fun () -> s, !i, !len);
is_close=(fun () -> ());
is_consume=(fun n -> i := !i + n; len := !len - n);
{ bs_fill_buf=(fun () -> s, !i, !len);
bs_close=(fun () -> ());
bs_consume=(fun n -> i := !i + n; len := !len - n);
}

let with_file file f =
Expand All @@ -102,10 +102,10 @@ module Stream_ = struct

(* Read as much as possible into [buf]. *)
let read_into_buf (self:t) (buf:Buf_.t) : int =
let s, i, len = self.is_fill_buf () in
let s, i, len = self.bs_fill_buf () in
if len > 0 then (
Buf_.add_bytes buf s i len;
self.is_consume len;
self.bs_consume len;
);
len

Expand All @@ -125,11 +125,11 @@ module Stream_ = struct
let offset = ref 0 in
while !offset < n do
let n_read =
let s, i, len = self.is_fill_buf () in
let s, i, len = self.bs_fill_buf () in
let n_read = min len (n- !offset) in
Bytes.blit s i bytes !offset n_read;
offset := !offset + n_read;
self.is_consume n_read;
self.bs_consume n_read;
n_read
in
if n_read=0 then too_short();
Expand All @@ -140,7 +140,7 @@ module Stream_ = struct
Buf_.clear buf;
let continue = ref true in
while !continue do
let s, i, len = self.is_fill_buf () in
let s, i, len = self.bs_fill_buf () in
if len=0 then continue := false;
let j = ref i in
while !j < i+len && Bytes.get s !j <> '\n' do
Expand All @@ -149,11 +149,11 @@ module Stream_ = struct
if !j-i < len then (
assert (Bytes.get s !j = '\n');
Buf_.add_bytes buf s i (!j-i); (* without \n *)
self.is_consume (!j-i+1); (* remove \n *)
self.bs_consume (!j-i+1); (* remove \n *)
continue := false
) else (
Buf_.add_bytes buf s i len;
self.is_consume len;
self.bs_consume len;
)
done

Expand Down Expand Up @@ -237,9 +237,9 @@ module Headers = struct
let pp_pair out (k,v) = Format.fprintf out "@[<h>%s: %s@]" k v in
Format.fprintf out "@[<v>%a@]" (Format.pp_print_list pp_pair) l

let parse_ ~buf (is:stream) : t =
let parse_ ~buf (bs:byte_stream) : t =
let rec loop acc =
let line = Stream_.read_line ~buf is in
let line = Byte_stream.read_line ~buf bs in
_debug (fun k->k "parsed header line %S" line);
if line = "\r" then (
acc
Expand Down Expand Up @@ -283,16 +283,16 @@ module Request = struct
(Meth.to_string self.meth) self.host Headers.pp self.headers
self.path self.body

let read_body_exact (is:stream) (n:int) : string =
let read_body_exact (bs:byte_stream) (n:int) : string =
let bytes = Bytes.make n ' ' in
Stream_.read_exactly_ is bytes n
Byte_stream.read_exactly_ bs bytes n
~too_short:(fun () -> bad_reqf 400 "body is too short");
Bytes.unsafe_to_string bytes

(* decode a "chunked" stream into a normal stream *)
let read_stream_chunked_ ?(buf=Buf_.create()) (is:stream) : stream =
let read_stream_chunked_ ?(buf=Buf_.create()) (bs:byte_stream) : byte_stream =
let read_next_chunk_len () : int =
let line = Stream_.read_line ~buf is in
let line = Byte_stream.read_line ~buf bs in
(* parse chunk length, ignore extensions *)
let chunk_size = (
if String.trim line = "" then 0
Expand All @@ -307,7 +307,7 @@ module Request = struct
let offset = ref 0 in
let len = ref 0 in
let chunk_size = ref 0 in
{ is_fill_buf=
{ bs_fill_buf=
(fun () ->
(* do we need to refill? *)
if !offset >= !len then (
Expand All @@ -319,9 +319,9 @@ module Request = struct
if !chunk_size > 0 then (
(* read the whole chunk, or [Bytes.length bytes] of it *)
let to_read = min !chunk_size (Bytes.length bytes) in
Stream_.read_exactly_
Byte_stream.read_exactly_
~too_short:(fun () -> bad_reqf 400 "chunk is too short")
is bytes to_read;
bs bytes to_read;
len := to_read;
chunk_size := !chunk_size - to_read;
) else (
Expand All @@ -330,17 +330,17 @@ module Request = struct
);
bytes, !offset, !len
);
is_consume=(fun n -> offset := !offset + n);
is_close=(fun () -> Stream_.close is);
bs_consume=(fun n -> offset := !offset + n);
bs_close=(fun () -> Byte_stream.close bs);
}

let read_body_chunked ~tr_stream ~buf ~size:max_size (is:stream) : string =
let read_body_chunked ~tr_stream ~buf ~size:max_size (bs:byte_stream) : string =
_debug (fun k->k "read body with chunked encoding (max-size: %d)" max_size);
let is = tr_stream @@ read_stream_chunked_ ~buf is in
let is = tr_stream @@ read_stream_chunked_ ~buf bs in
let buf_res = Buf_.create() in (* store the accumulated chunks *)
(* TODO: extract this as a function [read_all_up_to ~max_size is]? *)
let rec read_chunks () =
let n = Stream_.read_into_buf is buf_res in
let n = Byte_stream.read_into_buf is buf_res in
if n = 0 then (
Buf_.contents buf_res (* done *)
) else (
Expand All @@ -356,16 +356,16 @@ module Request = struct
read_chunks()

(* parse request, but not body (yet) *)
let parse_req_start ~buf (is:stream) : unit t option resp_result =
let parse_req_start ~buf (bs:byte_stream) : unit t option resp_result =
try
let line = Stream_.read_line ~buf is in
let line = Byte_stream.read_line ~buf bs in
let meth, path =
try Scanf.sscanf line "%s %s HTTP/1.1\r" (fun x y->x,y)
with _ -> raise (Bad_req (400, "Invalid request line"))
in
let meth = Meth.of_string meth in
_debug (fun k->k "got meth: %s, path %S" (Meth.to_string meth) path);
let headers = Headers.parse_ ~buf is in
let headers = Headers.parse_ ~buf bs in
let host =
try List.assoc "Host" headers
with Not_found -> bad_reqf 400 "No 'Host' header in request"
Expand All @@ -379,7 +379,7 @@ module Request = struct

(* parse body, given the headers.
@param tr_stream a transformation of the input stream. *)
let parse_body_ ~tr_stream ~buf (req:stream t) : string t resp_result =
let parse_body_ ~tr_stream ~buf (req:byte_stream t) : string t resp_result =
try
let size =
match List.assoc "Content-Length" req.headers |> int_of_string with
Expand All @@ -401,17 +401,17 @@ module Request = struct
| e ->
Error (400, Printexc.to_string e)

let read_body_full (self:stream t) : string t =
let read_body_full (self:byte_stream t) : string t =
try
let body = Stream_.read_all self.body in
let body = Byte_stream.read_all self.body in
{ self with body }
with
| Bad_req _ as e -> raise e
| e -> bad_reqf 500 "failed to read body: %s" (Printexc.to_string e)
end

module Response = struct
type body = [`String of string | `Stream of stream]
type body = [`String of string | `Stream of byte_stream]
type t = {
code: Response_code.t;
headers: Headers.t;
Expand Down Expand Up @@ -457,14 +457,14 @@ module Response = struct
self.code Headers.pp self.headers pp_body self.body

(* print a stream as a series of chunks *)
let output_stream_chunked_ (oc:out_channel) (str:stream) : unit =
let output_stream_chunked_ (oc:out_channel) (str:byte_stream) : unit =
let continue = ref true in
while !continue do
(* next chunk *)
let s, i, len = str.is_fill_buf () in
let s, i, len = str.bs_fill_buf () in
Printf.fprintf oc "%x\r\n" len;
output oc s i len;
str.is_consume len;
str.bs_consume len;
if len = 0 then (
continue := false;
);
Expand Down Expand Up @@ -523,7 +523,8 @@ type t = {
masksigpipe: bool;
mutable handler: (string Request.t -> Response.t);
mutable path_handlers : (unit Request.t -> cb_path_handler resp_result option) list;
mutable cb_decode_req: (unit Request.t -> (unit Request.t * (stream -> stream)) option) list;
mutable cb_decode_req:
(unit Request.t -> (unit Request.t * (byte_stream -> byte_stream)) option) list;
mutable cb_encode_resp: (string Request.t -> Response.t -> Response.t option) list;
mutable running: bool;
}
Expand Down Expand Up @@ -583,7 +584,7 @@ let handle_client_ (self:t) (client_sock:Unix.file_descr) : unit =
let ic = Unix.in_channel_of_descr client_sock in
let oc = Unix.out_channel_of_descr client_sock in
let buf = Buf_.create() in
let is = Stream_.of_chan ic in
let is = Byte_stream.of_chan ic in
let continue = ref true in
while !continue && self.running do
_debug (fun k->k "read next request");
Expand Down
30 changes: 16 additions & 14 deletions src/Tiny_httpd.mli
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@
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_path_handler ~meth:`GET server
"/hello/%s@/" (fun name _req -> S.Response.make_string (Ok ("hello " ^name ^"!\n")));
"/hello/%s@/" (fun name _req ->
S.Response.make_string (Ok ("hello " ^name ^"!\n")));
(* echo request *)
S.add_path_handler server
"/echo" (fun req -> S.Response.make_string
Expand Down Expand Up @@ -84,24 +86,24 @@ end
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. *)

type stream = {
is_fill_buf: unit -> (bytes * int * int);
type byte_stream = {
bs_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;
bs_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;
bs_close: unit -> unit;
(** Close the stream. *)
}
(** A buffered stream, with a view into the current buffer (or refill if empty),
and a function to consume [n] bytes.
See {!Stream_} for more details. *)
See {!Byte_stream} for more details. *)

module Stream_ : sig
type t = stream
module Byte_stream : sig
type t = byte_stream

val close : t -> unit

Expand Down Expand Up @@ -218,7 +220,7 @@ module Request : sig
val body : 'b t -> 'b
(** Request body, possibly empty. *)

val read_body_full : stream t -> string t
val read_body_full : byte_stream t -> string t
(** Read the whole body into a string. Potentially blocking. *)
end

Expand All @@ -244,7 +246,7 @@ end
(** {2 Response} *)

module Response : sig
type body = [`String of string | `Stream of stream]
type body = [`String of string | `Stream of byte_stream]
(** Body of a response, either as a simple string,
or a stream of bytes. *)

Expand All @@ -266,7 +268,7 @@ module Response : sig
val make_raw_stream :
?headers:Headers.t ->
code:Response_code.t ->
stream ->
byte_stream ->
t
(** Same as {!make_raw} but with a stream body. The body will be sent with
the chunked transfer-encoding. *)
Expand All @@ -288,7 +290,7 @@ module Response : sig

val make_stream :
?headers:Headers.t ->
(stream, Response_code.t * string) result -> t
(byte_stream, Response_code.t * string) result -> t
(** Same as {!make} but with a stream body. *)

val fail : ?headers:Headers.t -> code:int ->
Expand Down Expand Up @@ -346,7 +348,7 @@ val port : t -> int

val add_decode_request_cb :
t ->
(unit Request.t -> (unit Request.t * (stream -> stream)) option) -> unit
(unit Request.t -> (unit Request.t * (byte_stream -> byte_stream)) option) -> unit
(** Add a callback for every request.
The callback can provide a stream transformer and a new request (with
modified headers, typically).
Expand Down Expand Up @@ -378,7 +380,7 @@ val add_path_handler :
'b, 'c -> string Request.t -> Response.t, 'a -> 'd, 'd) format6 ->
'c -> unit
(** [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 "foo" 42 request] when a request with path "some/path/foo/42/"
is received.
This uses {!Scanf}'s splitting, which has some gotchas (in particular,
Expand Down
2 changes: 1 addition & 1 deletion src/bin/dune
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
(public_name http_of_dir)
(package tiny_httpd)
(flags :standard -warn-error -3)
(libraries tiny_httpd str))
(libraries tiny_httpd))
2 changes: 1 addition & 1 deletion src/bin/http_of_dir.ml
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ let serve ~config (dir:string) : _ result =
let ic = open_in full_path in
S.Response.make_raw_stream
~headers:["Etag", Lazy.force mtime]
~code:200 (S.Stream_.of_chan ic)
~code:200 (S.Byte_stream.of_chan ic)
with e ->
S.Response.fail ~code:500 "error while reading file: %s" (Printexc.to_string e)
));
Expand Down
3 changes: 2 additions & 1 deletion tiny_httpd.opam
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ version: "0.1"
authors: ["Simon Cruanes"]
maintainer: "[email protected]"
license: "MIT"
description: "Minimal HTTP server using good old threads"
synopsis: "Minimal HTTP server using good old threads"
build: [
["dune" "build" "@install" "-p" name "-j" jobs]
["dune" "build" "@doc" "-p" name] {with-doc}
Expand All @@ -20,3 +20,4 @@ homepage: "https://github.com/c-cube/tiny_httpd/"
doc: "https://c-cube.github.io/tiny_httpd/"
bug-reports: "https://github.com/c-cube/tiny_httpd/issues"
dev-repo: "git+https://github.com/c-cube/tiny_httpd.git"
post-messages: "tiny http server, with blocking IOs. Also ships with a `http_of_dir` program."

0 comments on commit 20275c1

Please sign in to comment.