Compare commits

..

No commits in common. "main" and "v0.18" have entirely different histories.
main ... v0.18

21 changed files with 88 additions and 239 deletions

View file

@ -1,4 +1,4 @@
version = 0.27.0 version = 0.26.2
profile=conventional profile=conventional
margin=80 margin=80
if-then-else=k-r if-then-else=k-r

View file

@ -1,23 +1,4 @@
## 0.19
- feat(headers): `set` will not reallocate whole list if not needed
- feat(headers): use case insensitive comparison
- fix(response): do not override "content-length" in raw response
- feat pool: expose `acquire/release` for advanced uses
## 0.18
- feat: add ?head_middlewares to `create`
- add content-type header for prometheus endpoint
- new flag ?enable_logging to disable regular logs (not debug)
- new sublibrary to deal with multipart-form-data
- feat response: add `pp_with`; have `pp` hide set-cookie headers
- fix percent encoding for < 0x10 chars
- Processing to fix incompatible -O and gcc flags
- fix: make check for 'Connection: Upgrade' header case-insensitive
## 0.17 ## 0.17
- add optional middlewares to tiny_httpd_ws - add optional middlewares to tiny_httpd_ws

View file

@ -4,7 +4,7 @@
(authors c-cube) (authors c-cube)
(maintainers c-cube) (maintainers c-cube)
(version 0.19) (version 0.18)
(source (github c-cube/tiny_httpd)) (source (github c-cube/tiny_httpd))
(homepage https://github.com/c-cube/tiny_httpd/) (homepage https://github.com/c-cube/tiny_httpd/)
(license MIT) (license MIT)

View file

@ -26,6 +26,11 @@ let atomic_before_412 =
let atomic_after_412 = {|include Atomic|} let atomic_after_412 = {|include Atomic|}
let write_file file s =
let oc = open_out file in
output_string oc s;
close_out oc
let () = let () =
let version = Scanf.sscanf Sys.ocaml_version "%d.%d.%s" (fun x y _ -> x, y) in let version = Scanf.sscanf Sys.ocaml_version "%d.%d.%s" (fun x y _ -> x, y) in
print_endline print_endline

View file

@ -4,46 +4,24 @@ type t = (string * string) list
let empty = [] let empty = []
(* [Char.lowercase_ascii] but easier to inline *)
let[@inline] lower_char_ = function
| 'A' .. 'Z' as c -> Char.unsafe_chr (Char.code c + 32)
| c -> c
(** Are these two header names equal? This is case insensitive *)
let equal_name_ (s1 : string) (s2 : string) : bool =
String.length s1 = String.length s2
&&
try
for i = 0 to String.length s1 - 1 do
let c1 = String.unsafe_get s1 i |> lower_char_ in
let c2 = String.unsafe_get s2 i |> lower_char_ in
if c1 <> c2 then raise_notrace Exit
done;
true
with Exit -> false
let contains name headers = let contains name headers =
List.exists (fun (n, _) -> equal_name_ name n) headers let name' = String.lowercase_ascii name in
List.exists (fun (n, _) -> name' = n) headers
let rec get_exn ?(f = fun x -> x) x h = let get_exn ?(f = fun x -> x) x h =
match h with let x' = String.lowercase_ascii x in
| [] -> raise Not_found List.assoc x' h |> f
| (k, v) :: _ when equal_name_ x k -> f v
| _ :: tl -> get_exn ~f x tl
let get ?(f = fun x -> x) x h = let get ?(f = fun x -> x) x h =
try Some (get_exn ~f x h) with Not_found -> None try Some (get_exn ~f x h) with Not_found -> None
let remove x h = List.filter (fun (k, _) -> not (equal_name_ k x)) h let remove x h =
let x' = String.lowercase_ascii x in
List.filter (fun (k, _) -> k <> x') h
let set x y h = let set x y h =
let h = let x' = String.lowercase_ascii x in
if contains x h then (x', y) :: List.filter (fun (k, _) -> k <> x') h
remove x h
else
h
in
(x, y) :: h
let pp out l = let pp out l =
let pp_pair out (k, v) = Format.fprintf out "@[<h>%s: %s@]" k v in let pp_pair out (k, v) = Format.fprintf out "@[<h>%s: %s@]" k v in
@ -98,6 +76,6 @@ let parse_ ~(buf : Buf.t) (bs : IO.Input.t) : t =
| Error msg -> | Error msg ->
bad_reqf 400 "invalid header line: %s\nline is: %S" msg line bad_reqf 400 "invalid header line: %s\nline is: %S" msg line
in in
loop ((k, v) :: acc) loop ((String.lowercase_ascii k, v) :: acc)
in in
loop [] loop []

View file

@ -32,9 +32,8 @@ val contains : string -> t -> bool
val pp : Format.formatter -> t -> unit val pp : Format.formatter -> t -> unit
(** Pretty print the headers. *) (** Pretty print the headers. *)
(**/*)
val parse_ : buf:Buf.t -> IO.Input.t -> t val parse_ : buf:Buf.t -> IO.Input.t -> t
val parse_line_ : string -> (string * string, string) result (**/*)
val parse_line_ : string -> (string * string, string) result
(**/*) (**/*)

View file

@ -12,20 +12,20 @@ type 'a t = {
let create ?(clear = ignore) ~mk_item ?(max_size = 512) () : _ t = let create ?(clear = ignore) ~mk_item ?(max_size = 512) () : _ t =
{ mk_item; clear; max_size; items = A.make Nil } { mk_item; clear; max_size; items = A.make Nil }
let rec acquire self = let rec acquire_ self =
match A.get self.items with match A.get self.items with
| Nil -> self.mk_item () | Nil -> self.mk_item ()
| Cons (_, x, tl) as l -> | Cons (_, x, tl) as l ->
if A.compare_and_set self.items l tl then if A.compare_and_set self.items l tl then
x x
else else
acquire self acquire_ self
let[@inline] size_ = function let[@inline] size_ = function
| Cons (sz, _, _) -> sz | Cons (sz, _, _) -> sz
| Nil -> 0 | Nil -> 0
let release self x : unit = let release_ self x : unit =
let rec loop () = let rec loop () =
match A.get self.items with match A.get self.items with
| Cons (sz, _, _) when sz >= self.max_size -> | Cons (sz, _, _) when sz >= self.max_size ->
@ -40,17 +40,12 @@ let release self x : unit =
loop () loop ()
let with_resource (self : _ t) f = let with_resource (self : _ t) f =
let x = acquire self in let x = acquire_ self in
try try
let res = f x in let res = f x in
release self x; release_ self x;
res res
with e -> with e ->
let bt = Printexc.get_raw_backtrace () in let bt = Printexc.get_raw_backtrace () in
release self x; release_ self x;
Printexc.raise_with_backtrace e bt Printexc.raise_with_backtrace e bt
module Raw = struct
let release = release
let acquire = acquire
end

View file

@ -23,12 +23,3 @@ val with_resource : 'a t -> ('a -> 'b) -> 'b
(** [with_resource pool f] runs [f x] with [x] a resource; (** [with_resource pool f] runs [f x] with [x] a resource;
when [f] fails or returns, [x] is returned to the pool for when [f] fails or returns, [x] is returned to the pool for
future reuse. *) future reuse. *)
(** Low level control over the pool.
This is easier to get wrong (e.g. releasing the same resource twice)
so use with caution.
@since 0.18 *)
module Raw : sig
val acquire : 'a t -> 'a
val release : 'a t -> 'a -> unit
end

View file

@ -15,11 +15,7 @@ let set_code code self = { self with code }
let make_raw ?(headers = []) ~code body : t = let make_raw ?(headers = []) ~code body : t =
(* add content length to response *) (* add content length to response *)
let headers = let headers =
if Headers.contains "content-length" headers then Headers.set "Content-Length" (string_of_int (String.length body)) headers
(* do not override user-provided headers (e.g. in HEAD), see #92 *)
headers
else
Headers.set "Content-Length" (string_of_int (String.length body)) headers
in in
{ code; headers; body = `String body } { code; headers; body = `String body }

View file

@ -73,9 +73,9 @@ let rec pp_ : type a b. Buffer.t -> (a, b) t -> unit =
| Rest { url_encoded } -> | Rest { url_encoded } ->
bpf out "<rest_of_url%s>" bpf out "<rest_of_url%s>"
(if url_encoded then (if url_encoded then
"_urlencoded" "_urlencoded"
else else
"") "")
| Compose (Exact s, tl) -> bpf out "%s/%a" s pp_ tl | Compose (Exact s, tl) -> bpf out "%s/%a" s pp_ tl
| Compose (Int, tl) -> bpf out "<int>/%a" pp_ tl | Compose (Int, tl) -> bpf out "<int>/%a" pp_ tl
| Compose (String, tl) -> bpf out "<str>/%a" pp_ tl | Compose (String, tl) -> bpf out "<str>/%a" pp_ tl
@ -91,34 +91,3 @@ module Private_ = struct
end end
let pp out x = Format.pp_print_string out (to_string x) let pp out x = Format.pp_print_string out (to_string x)
let rec to_url_rec : type b. Buffer.t -> (b, string) t -> b =
fun buf route ->
match route with
| Fire -> Buffer.contents buf
| Rest { url_encoded = _ } ->
fun str ->
Buffer.add_string buf str;
Buffer.contents buf
| Compose (comp, rest) ->
(match comp with
| Exact s ->
Buffer.add_string buf s;
Buffer.add_char buf '/';
to_url_rec buf rest
| Int ->
fun i ->
Printf.bprintf buf "%d/" i;
to_url_rec buf rest
| String ->
fun s ->
Printf.bprintf buf "%s/" s;
to_url_rec buf rest
| String_urlencoded ->
fun s ->
Printf.bprintf buf "%s/" (Util.percent_encode s);
to_url_rec buf rest)
let to_url (h : ('a, string) t) : 'a =
let buf = Buffer.create 16 in
to_url_rec buf h

View file

@ -1,8 +1,8 @@
(** Routing (** Routing
Basic type-safe routing of handlers based on URL paths. This is optional, it Basic type-safe routing of handlers based on URL paths. This is optional,
is possible to only define the root handler with something like it is possible to only define the root handler with something like
{{:https://github.com/anuragsoni/routes/} Routes}. {{: https://github.com/anuragsoni/routes/} Routes}.
@since 0.6 *) @since 0.6 *)
type ('a, 'b) comp type ('a, 'b) comp
@ -27,35 +27,31 @@ val return : ('a, 'a) t
(** Matches the empty path. *) (** Matches the empty path. *)
val rest_of_path : (string -> 'a, 'a) t val rest_of_path : (string -> 'a, 'a) t
(** Matches a string, even containing ['/']. This will match the entirety of the (** Matches a string, even containing ['/']. This will match
remaining route. the entirety of the remaining route.
@since 0.7 *) @since 0.7 *)
val rest_of_path_urlencoded : (string -> 'a, 'a) t val rest_of_path_urlencoded : (string -> 'a, 'a) t
(** Matches a string, even containing ['/'], and URL-decode it (piecewise). This (** Matches a string, even containing ['/'], and URL-decode it (piecewise).
will match the entirety of the remaining route. This will match the entirety of the remaining route.
@since 0.7 *) @since 0.7 *)
val ( @/ ) : ('a, 'b) comp -> ('b, 'c) t -> ('a, 'c) t val ( @/ ) : ('a, 'b) comp -> ('b, 'c) t -> ('a, 'c) t
(** [comp / route] matches ["foo/bar/…"] iff [comp] matches ["foo"], and [route] (** [comp / route] matches ["foo/bar/…"] iff [comp] matches ["foo"],
matches ["bar/…"]. *) and [route] matches ["bar/…"]. *)
val exact_path : string -> ('a, 'b) t -> ('a, 'b) t val exact_path : string -> ('a, 'b) t -> ('a, 'b) t
(** [exact_path "foo/bar/..." r] is equivalent to (** [exact_path "foo/bar/..." r] is equivalent to
[exact "foo" @/ exact "bar" @/ ... @/ r] [exact "foo" @/ exact "bar" @/ ... @/ r]
@since 0.11 **) @since 0.11 **)
val pp : Format.formatter -> _ t -> unit val pp : Format.formatter -> _ t -> unit
(** Print the route. (** Print the route.
@since 0.7 *) @since 0.7 *)
val to_string : _ t -> string val to_string : _ t -> string
(** Print the route. (** Print the route.
@since 0.7 *) @since 0.7 *)
val to_url : ('a, string) t -> 'a
(** [to_url route args] takes a route, and turns it into a URL path.
@since NEXT_RELEASE *)
module Private_ : sig module Private_ : sig
val eval : string list -> ('a, 'b) t -> 'a -> 'b option val eval : string list -> ('a, 'b) t -> 'a -> 'b option

View file

@ -15,6 +15,7 @@ module Head_middleware = struct
type t = { handle: 'a. 'a Request.t -> 'a Request.t } type t = { handle: 'a. 'a Request.t -> 'a Request.t }
let trivial = { handle = Fun.id } let trivial = { handle = Fun.id }
let[@inline] apply (self : t) req = self.handle req
let[@inline] apply' req (self : t) = self.handle req let[@inline] apply' req (self : t) = self.handle req
let to_middleware (self : t) : Middleware.t = let to_middleware (self : t) : Middleware.t =

View file

@ -1,6 +1,7 @@
(* adapted from https://github.com/sindresorhus/html-tags (MIT licensed) *) (* adapted from https://github.com/sindresorhus/html-tags (MIT licensed) *)
let pf = Printf.printf let pf = Printf.printf
let spf = Printf.sprintf
let void = let void =
[ [

View file

@ -1,26 +1,5 @@
open Common_ws_ open Common_ws_
module With_lock = struct
type t = { with_lock: 'a. (unit -> 'a) -> 'a }
type builder = unit -> t
let default_builder : builder =
fun () ->
let mutex = Mutex.create () in
{
with_lock =
(fun f ->
Mutex.lock mutex;
try
let x = f () in
Mutex.unlock mutex;
x
with e ->
Mutex.unlock mutex;
raise e);
}
end
type handler = unit Request.t -> IO.Input.t -> IO.Output.t -> unit type handler = unit Request.t -> IO.Input.t -> IO.Output.t -> unit
module Frame_type = struct module Frame_type = struct
@ -73,10 +52,10 @@ module Writer = struct
mutable offset: int; (** number of bytes already in [buf] *) mutable offset: int; (** number of bytes already in [buf] *)
oc: IO.Output.t; oc: IO.Output.t;
mutable closed: bool; mutable closed: bool;
mutex: With_lock.t; mutex: Mutex.t;
} }
let create ?(buf_size = 16 * 1024) ~with_lock ~oc () : t = let create ?(buf_size = 16 * 1024) ~oc () : t =
{ {
header = Header.create (); header = Header.create ();
header_buf = Bytes.create 16; header_buf = Bytes.create 16;
@ -84,9 +63,19 @@ module Writer = struct
offset = 0; offset = 0;
oc; oc;
closed = false; closed = false;
mutex = with_lock; mutex = Mutex.create ();
} }
let[@inline] with_mutex_ (self : t) f =
Mutex.lock self.mutex;
try
let x = f () in
Mutex.unlock self.mutex;
x
with e ->
Mutex.unlock self.mutex;
raise e
let[@inline] close self = self.closed <- true let[@inline] close self = self.closed <- true
let int_of_bool : bool -> int = Obj.magic let int_of_bool : bool -> int = Obj.magic
@ -153,7 +142,7 @@ module Writer = struct
if self.offset = Bytes.length self.buf then really_output_buf_ self if self.offset = Bytes.length self.buf then really_output_buf_ self
let send_pong (self : t) : unit = let send_pong (self : t) : unit =
let@ () = self.mutex.with_lock in let@ () = with_mutex_ self in
self.header.fin <- true; self.header.fin <- true;
self.header.ty <- Frame_type.pong; self.header.ty <- Frame_type.pong;
self.header.payload_len <- 0; self.header.payload_len <- 0;
@ -162,7 +151,7 @@ module Writer = struct
write_header_ self write_header_ self
let output_char (self : t) c : unit = let output_char (self : t) c : unit =
let@ () = self.mutex.with_lock in let@ () = with_mutex_ self in
let cap = Bytes.length self.buf - self.offset in let cap = Bytes.length self.buf - self.offset in
(* make room for [c] *) (* make room for [c] *)
if cap = 0 then really_output_buf_ self; if cap = 0 then really_output_buf_ self;
@ -172,7 +161,7 @@ module Writer = struct
if cap = 1 then really_output_buf_ self if cap = 1 then really_output_buf_ self
let output (self : t) buf i len : unit = let output (self : t) buf i len : unit =
let@ () = self.mutex.with_lock in let@ () = with_mutex_ self in
let i = ref i in let i = ref i in
let len = ref len in let len = ref len in
while !len > 0 do while !len > 0 do
@ -190,7 +179,7 @@ module Writer = struct
flush_if_full self flush_if_full self
let flush self : unit = let flush self : unit =
let@ () = self.mutex.with_lock in let@ () = with_mutex_ self in
flush_ self flush_ self
end end
@ -198,8 +187,8 @@ module Reader = struct
type state = type state =
| Begin (** At the beginning of a frame *) | Begin (** At the beginning of a frame *)
| Reading_frame of { mutable remaining_bytes: int; mutable num_read: int } | Reading_frame of { mutable remaining_bytes: int; mutable num_read: int }
(** Currently reading the payload of a frame with [remaining_bytes] left (** Currently reading the payload of a frame with [remaining_bytes]
to read from the underlying [ic] *) left to read from the underlying [ic] *)
| Close | Close
type t = { type t = {
@ -277,7 +266,7 @@ module Reader = struct
external apply_masking_ : external apply_masking_ :
key:bytes -> key_offset:int -> buf:bytes -> int -> int -> unit key:bytes -> key_offset:int -> buf:bytes -> int -> int -> unit
= "tiny_httpd_ws_apply_masking" = "tiny_httpd_ws_apply_masking"
[@@noalloc] [@@noalloc]
(** Apply masking to the parsed data *) (** Apply masking to the parsed data *)
let[@inline] apply_masking ~mask_key ~mask_offset (buf : bytes) off len : unit let[@inline] apply_masking ~mask_key ~mask_offset (buf : bytes) off len : unit
@ -401,8 +390,8 @@ module Reader = struct
) )
end end
let upgrade ?(with_lock = With_lock.default_builder ()) ic oc : _ * _ = let upgrade ic oc : _ * _ =
let writer = Writer.create ~with_lock ~oc () in let writer = Writer.create ~oc () in
let reader = Reader.create ~ic ~writer () in let reader = Reader.create ~ic ~writer () in
let ws_ic : IO.Input.t = let ws_ic : IO.Input.t =
object object
@ -425,11 +414,9 @@ let upgrade ?(with_lock = With_lock.default_builder ()) ic oc : _ * _ =
in in
ws_ic, ws_oc ws_ic, ws_oc
(** Turn a regular connection handler (provided by the user) into a websocket (** Turn a regular connection handler (provided by the user) into a websocket upgrade handler *)
upgrade handler *)
module Make_upgrade_handler (X : sig module Make_upgrade_handler (X : sig
val accept_ws_protocol : string -> bool val accept_ws_protocol : string -> bool
val with_lock : With_lock.builder
val handler : handler val handler : handler
end) : Server.UPGRADE_HANDLER with type handshake_state = unit Request.t = end) : Server.UPGRADE_HANDLER with type handshake_state = unit Request.t =
struct struct
@ -474,8 +461,7 @@ struct
try Ok (handshake_ req) with Bad_req s -> Error s try Ok (handshake_ req) with Bad_req s -> Error s
let handle_connection req ic oc = let handle_connection req ic oc =
let with_lock = X.with_lock () in let ws_ic, ws_oc = upgrade ic oc in
let ws_ic, ws_oc = upgrade ~with_lock ic oc in
try X.handler req ws_ic ws_oc try X.handler req ws_ic ws_oc
with Close_connection -> with Close_connection ->
Log.debug (fun k -> k "websocket: requested to close the connection"); Log.debug (fun k -> k "websocket: requested to close the connection");
@ -483,11 +469,9 @@ struct
end end
let add_route_handler ?accept ?(accept_ws_protocol = fun _ -> true) ?middlewares let add_route_handler ?accept ?(accept_ws_protocol = fun _ -> true) ?middlewares
?(with_lock = With_lock.default_builder) (server : Server.t) route (server : Server.t) route (f : handler) : unit =
(f : handler) : unit =
let module M = Make_upgrade_handler (struct let module M = Make_upgrade_handler (struct
let handler = f let handler = f
let with_lock = with_lock
let accept_ws_protocol = accept_ws_protocol let accept_ws_protocol = accept_ws_protocol
end) in end) in
let up : Server.upgrade_handler = (module M) in let up : Server.upgrade_handler = (module M) in

View file

@ -1,60 +1,30 @@
(** Websockets for Tiny_httpd. (** Websockets for Tiny_httpd.
This sub-library ([tiny_httpd.ws]) exports a small implementation for a This sub-library ([tiny_httpd.ws]) exports a small implementation
websocket server. It has no additional dependencies. *) for a websocket server. It has no additional dependencies.
*)
(** Synchronization primitive used to allow both the reader to reply to "ping",
and the handler to send messages, without stepping on each other's toes.
@since NEXT_RELEASE *)
module With_lock : sig
type t = { with_lock: 'a. (unit -> 'a) -> 'a }
(** A primitive to run the callback in a critical section where others cannot
run at the same time.
The default is a mutex, but that works poorly with thread pools so it's
possible to use a semaphore or a cooperative mutex instead. *)
type builder = unit -> t
val default_builder : builder
(** Lock using [Mutex]. *)
end
type handler = unit Request.t -> IO.Input.t -> IO.Output.t -> unit type handler = unit Request.t -> IO.Input.t -> IO.Output.t -> unit
(** Websocket handler *) (** Websocket handler *)
val upgrade : val upgrade : IO.Input.t -> IO.Output.t -> IO.Input.t * IO.Output.t
?with_lock:With_lock.t -> (** Upgrade a byte stream to the websocket framing protocol. *)
IO.Input.t ->
IO.Output.t ->
IO.Input.t * IO.Output.t
(** Upgrade a byte stream to the websocket framing protocol.
@param with_lock
if provided, use this to prevent reader and writer to compete on sending
frames. since NEXT_RELEASE. *)
exception Close_connection exception Close_connection
(** Exception that can be raised from IOs inside the handler, when the (** Exception that can be raised from IOs inside the handler,
connection is closed from underneath. *) when the connection is closed from underneath. *)
val add_route_handler : val add_route_handler :
?accept:(unit Request.t -> (unit, int * string) result) -> ?accept:(unit Request.t -> (unit, int * string) result) ->
?accept_ws_protocol:(string -> bool) -> ?accept_ws_protocol:(string -> bool) ->
?middlewares:Server.Head_middleware.t list -> ?middlewares:Server.Head_middleware.t list ->
?with_lock:With_lock.builder ->
Server.t -> Server.t ->
(Server.upgrade_handler, Server.upgrade_handler) Route.t -> (Server.upgrade_handler, Server.upgrade_handler) Route.t ->
handler -> handler ->
unit unit
(** Add a route handler for a websocket endpoint. (** Add a route handler for a websocket endpoint.
@param accept_ws_protocol @param accept_ws_protocol decides whether this endpoint accepts the websocket protocol
decides whether this endpoint accepts the websocket protocol sent by the sent by the client. Default accepts everything. *)
client. Default accepts everything.
@param with_lock
if provided, use this to synchronize writes between the frame reader
(replies "pong" to "ping") and the handler emitting writes. since
NEXT_RELEASE. *)
(**/**) (**/**)

View file

@ -8,7 +8,7 @@ CAMLprim value tiny_httpd_ws_apply_masking(value _mask_key, value _mask_offset,
CAMLparam5(_mask_key, _mask_offset, _buf, _offset, _len); CAMLparam5(_mask_key, _mask_offset, _buf, _offset, _len);
char const *mask_key = String_val(_mask_key); char const *mask_key = String_val(_mask_key);
unsigned char *buf = Bytes_val(_buf); char *buf = Bytes_val(_buf);
intnat mask_offset = Int_val(_mask_offset); intnat mask_offset = Int_val(_mask_offset);
intnat offset = Int_val(_offset); intnat offset = Int_val(_offset);
intnat len = Int_val(_len); intnat len = Int_val(_len);

View file

@ -2,8 +2,8 @@ listening on http://127.0.0.1:8085
echo: echo:
{meth=GET; host=localhost:8085; {meth=GET; host=localhost:8085;
headers=[user-agent: test headers=[user-agent: test
Accept: */* accept: */*
Host: localhost:8085]; host: localhost:8085];
path="/echo/?a=b&c=d"; body=""; path_components=["echo"]; path="/echo/?a=b&c=d"; body=""; path_components=["echo"];
query=["c","d";"a","b"]} query=["c","d";"a","b"]}
(query: "c" = "d";"a" = "b") (query: "c" = "d";"a" = "b")

View file

@ -1,4 +1,4 @@
(tests (tests
(names t_util t_buf t_server t_io t_response) (names t_util t_buf t_server t_io)
(package tiny_httpd) (package tiny_httpd)
(libraries tiny_httpd.core qcheck-core qcheck-core.runner test_util)) (libraries tiny_httpd.core qcheck-core qcheck-core.runner test_util))

View file

@ -1,17 +0,0 @@
open Test_util
open Tiny_httpd_core
module U = Util
let () =
let res =
Response.make_raw ~code:200 ~headers:[ "content-length", "42" ] ""
in
let h = Headers.get_exn "content-length" res.headers in
assert_eq "42" h
let () =
let res =
Response.make_raw ~code:200 ~headers:[ "Content-Length", "42" ] ""
in
let h = Headers.get_exn "content-length" res.headers in
assert_eq "42" h

View file

@ -1,6 +1,6 @@
# This file is generated by dune, edit dune-project instead # This file is generated by dune, edit dune-project instead
opam-version: "2.0" opam-version: "2.0"
version: "0.19" version: "0.18"
synopsis: "Minimal HTTP server using threads" synopsis: "Minimal HTTP server using threads"
maintainer: ["c-cube"] maintainer: ["c-cube"]
authors: ["c-cube"] authors: ["c-cube"]

View file

@ -1,6 +1,6 @@
# This file is generated by dune, edit dune-project instead # This file is generated by dune, edit dune-project instead
opam-version: "2.0" opam-version: "2.0"
version: "0.19" version: "0.18"
synopsis: "Interface to camlzip for tiny_httpd" synopsis: "Interface to camlzip for tiny_httpd"
maintainer: ["c-cube"] maintainer: ["c-cube"]
authors: ["c-cube"] authors: ["c-cube"]