mirror of
https://github.com/c-cube/tiny_httpd.git
synced 2025-12-07 03:35:34 -05:00
Compare commits
No commits in common. "main" and "v0.19" have entirely different histories.
9 changed files with 61 additions and 135 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 =
|
||||||
|
|
|
||||||
|
|
@ -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 =
|
||||||
[
|
[
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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. *)
|
|
||||||
|
|
||||||
(**/**)
|
(**/**)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue