From 20275c164f18a18cf3727e9f0a8adb82259a8826 Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Mon, 18 Nov 2019 23:06:38 -0600 Subject: [PATCH] improve docs and opam, tidy up for 0.1; rename stream --- src/Tiny_httpd.ml | 93 +++++++++++++++++++++--------------------- src/Tiny_httpd.mli | 30 +++++++------- src/bin/dune | 2 +- src/bin/http_of_dir.ml | 2 +- tiny_httpd.opam | 3 +- 5 files changed, 67 insertions(+), 63 deletions(-) diff --git a/src/Tiny_httpd.ml b/src/Tiny_httpd.ml index eb424949..15125286 100644 --- a/src/Tiny_httpd.ml +++ b/src/Tiny_httpd.ml @@ -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 *) @@ -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 @@ -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 = @@ -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 @@ -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(); @@ -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 @@ -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 @@ -237,9 +237,9 @@ module Headers = struct let pp_pair out (k,v) = Format.fprintf out "@[%s: %s@]" k v in Format.fprintf out "@[%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 @@ -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 @@ -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 ( @@ -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 ( @@ -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 ( @@ -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" @@ -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 @@ -401,9 +401,9 @@ 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 @@ -411,7 +411,7 @@ module Request = struct 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; @@ -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; ); @@ -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; } @@ -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"); diff --git a/src/Tiny_httpd.mli b/src/Tiny_httpd.mli index 318728a6..dc67ef19 100644 --- a/src/Tiny_httpd.mli +++ b/src/Tiny_httpd.mli @@ -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 @@ -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 @@ -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 @@ -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. *) @@ -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. *) @@ -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 -> @@ -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). @@ -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, diff --git a/src/bin/dune b/src/bin/dune index ab8a97e0..31303a70 100644 --- a/src/bin/dune +++ b/src/bin/dune @@ -4,4 +4,4 @@ (public_name http_of_dir) (package tiny_httpd) (flags :standard -warn-error -3) - (libraries tiny_httpd str)) + (libraries tiny_httpd)) diff --git a/src/bin/http_of_dir.ml b/src/bin/http_of_dir.ml index 66236a24..3281ec83 100644 --- a/src/bin/http_of_dir.ml +++ b/src/bin/http_of_dir.ml @@ -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) )); diff --git a/tiny_httpd.opam b/tiny_httpd.opam index f4115dfc..4d9d5f5f 100644 --- a/tiny_httpd.opam +++ b/tiny_httpd.opam @@ -3,7 +3,7 @@ version: "0.1" authors: ["Simon Cruanes"] maintainer: "simon.cruanes.2007@m4x.org" 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} @@ -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."