Compare commits

..

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

9 changed files with 61 additions and 135 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

@ -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

@ -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);