Add middleware

This commit is contained in:
Calascibetta Romain 2025-02-09 18:56:08 +01:00
parent 93e39d449d
commit 392bb3bd88
6 changed files with 139 additions and 21 deletions

View file

@ -20,6 +20,7 @@ end
module C = Vif_c module C = Vif_c
module D = Vif_d module D = Vif_d
module S = Vif_s module S = Vif_s
module M = Vif_m
module Ds = struct module Ds = struct
type 'value t = type 'value t =
@ -44,6 +45,31 @@ module Ds = struct
Vif_d.Hmap.iter fn m Vif_d.Hmap.iter fn m
end end
module Ms = struct
type 'cfg t = [] : 'cfg t | ( :: ) : ('cfg, 'a) Vif_m.t * 'cfg t -> 'cfg t
type ('cfg, 'v) fn = ('cfg, 'v) Vif_m.fn
let make = Vif_m.make
type ('value, 'a, 'c) ctx = {
server: Vif_s.t
; request: Vif_request0.t
; target: string
; user's_value: 'value
}
let rec run : type v. v t -> (v, 'a, 'c) ctx -> Vif_m.Hmap.t -> Vif_m.Hmap.t =
fun lst ctx env ->
match lst with
| [] -> env
| Middleware (fn, key) :: r -> begin
match fn ctx.request ctx.target ctx.server ctx.user's_value with
| Some value -> run r ctx (Vif_m.Hmap.add key value env)
| None -> run r ctx env
| exception _exn -> run r ctx env
end
end
module Content_type = Vif_content_type module Content_type = Vif_content_type
module Stream = Stream module Stream = Stream
module Method = Vif_method module Method = Vif_method
@ -75,7 +101,7 @@ let method_of_request server =
| `V1 reqd -> ((H1.Reqd.request reqd).H1.Request.meth :> H2.Method.t) | `V1 reqd -> ((H1.Reqd.request reqd).H1.Request.meth :> H2.Method.t)
| `V2 reqd -> ((H2.Reqd.request reqd).H2.Request.meth :> H2.Method.t) | `V2 reqd -> ((H2.Reqd.request reqd).H2.Request.meth :> H2.Method.t)
let request server = let request ~env server =
let extract : type c a. let extract : type c a.
Vif_method.t option Vif_method.t option
-> (c, a) Vif_content_type.t -> (c, a) Vif_content_type.t
@ -84,47 +110,47 @@ let request server =
let meth' = method_of_request server in let meth' = method_of_request server in
match (meth, meth', c) with match (meth, meth', c) with
| None, _, (Vif_content_type.Any as encoding) -> | None, _, (Vif_content_type.Any as encoding) ->
Some (Vif_request.of_reqd ~encoding server.S.reqd) Some (Vif_request.of_reqd ~encoding ~env server.S.reqd)
| Some a, b, (Vif_content_type.Any as encoding) -> | Some a, b, (Vif_content_type.Any as encoding) ->
if a = b then Some (Vif_request.of_reqd ~encoding server.S.reqd) if a = b then Some (Vif_request.of_reqd ~encoding ~env server.S.reqd)
else None else None
| None, _, (Null as encoding) -> | None, _, (Null as encoding) ->
Some (Vif_request.of_reqd ~encoding server.S.reqd) Some (Vif_request.of_reqd ~encoding ~env server.S.reqd)
| Some a, b, (Null as encoding) -> | Some a, b, (Null as encoding) ->
if a = b then Some (Vif_request.of_reqd ~encoding server.S.reqd) if a = b then Some (Vif_request.of_reqd ~encoding ~env server.S.reqd)
else None else None
| None, _, (Json_encoding _ as encoding) -> | None, _, (Json_encoding _ as encoding) ->
let c = content_type server in let c = content_type server in
let application_json = Result.map is_application_json c in let application_json = Result.map is_application_json c in
let application_json = Result.value ~default:false application_json in let application_json = Result.value ~default:false application_json in
if application_json then if application_json then
Some (Vif_request.of_reqd ~encoding server.S.reqd) Some (Vif_request.of_reqd ~encoding ~env server.S.reqd)
else None else None
| Some a, b, (Json_encoding _ as encoding) -> | Some a, b, (Json_encoding _ as encoding) ->
let c = content_type server in let c = content_type server in
let application_json = Result.map is_application_json c in let application_json = Result.map is_application_json c in
let application_json = Result.value ~default:false application_json in let application_json = Result.value ~default:false application_json in
if application_json && a = b then if application_json && a = b then
Some (Vif_request.of_reqd ~encoding server.S.reqd) Some (Vif_request.of_reqd ~encoding ~env server.S.reqd)
else None else None
| None, _, (Json as encoding) -> | None, _, (Json as encoding) ->
let c = content_type server in let c = content_type server in
let application_json = Result.map is_application_json c in let application_json = Result.map is_application_json c in
let application_json = Result.value ~default:false application_json in let application_json = Result.value ~default:false application_json in
if application_json then if application_json then
Some (Vif_request.of_reqd ~encoding server.S.reqd) Some (Vif_request.of_reqd ~encoding ~env server.S.reqd)
else None else None
| Some a, b, (Json as encoding) -> | Some a, b, (Json as encoding) ->
let c = content_type server in let c = content_type server in
let application_json = Result.map is_application_json c in let application_json = Result.map is_application_json c in
let application_json = Result.value ~default:false application_json in let application_json = Result.value ~default:false application_json in
if application_json && a = b then if application_json && a = b then
Some (Vif_request.of_reqd ~encoding server.S.reqd) Some (Vif_request.of_reqd ~encoding ~env server.S.reqd)
else None else None
in in
{ Vif_r.extract } { Vif_r.extract }
let handler ~default routes devices user's_value = let handler ~default ~middlewares routes devices user's_value =
(); ();
fun socket reqd -> fun socket reqd ->
let target = let target =
@ -133,7 +159,11 @@ let handler ~default routes devices user's_value =
| `V2 reqd -> (H2.Reqd.request reqd).H2.Request.target | `V2 reqd -> (H2.Reqd.request reqd).H2.Request.target
in in
let server = { Vif_s.reqd; socket; devices } in let server = { Vif_s.reqd; socket; devices } in
let request = request server in let ctx =
{ Ms.server; request= Vif_request0.of_reqd reqd; target; user's_value }
in
let env = Ms.run middlewares ctx Vif_m.Hmap.empty in
let request = request ~env server in
Log.debug (fun m -> m "Handle a new request to %s" target); Log.debug (fun m -> m "Handle a new request to %s" target);
let fn = R.dispatch ~default routes ~request ~target in let fn = R.dispatch ~default routes ~request ~target in
match fn server user's_value with Vif_response.Response -> () match fn server user's_value with Vif_response.Response -> ()
@ -165,8 +195,8 @@ let store_pid = function
output_string oc (string_of_int (Unix.getpid ())); output_string oc (string_of_int (Unix.getpid ()));
close_out oc close_out oc
let run ?(cfg = Vif_options.config_from_globals ()) ?(devices = Ds.[]) ~default let run ?(cfg = Vif_options.config_from_globals ()) ?(devices = Ds.[])
routes user's_value = ?(middlewares = Ms.[]) ~default routes user's_value =
let interactive = !Sys.interactive in let interactive = !Sys.interactive in
let domains = Miou.Domain.available () in let domains = Miou.Domain.available () in
store_pid cfg.pid; store_pid cfg.pid;
@ -185,10 +215,11 @@ let run ?(cfg = Vif_options.config_from_globals ()) ?(devices = Ds.[]) ~default
Ds.run Vif_d.empty devices user's_value Ds.run Vif_d.empty devices user's_value
in in
Logs.debug (fun m -> m "devices launched"); Logs.debug (fun m -> m "devices launched");
let fn0 = handler ~default routes devices user's_value in let fn0 = handler ~default ~middlewares routes devices user's_value in
let prm = Miou.async @@ fun () -> handle stop cfg fn0 in let prm = Miou.async @@ fun () -> handle stop cfg fn0 in
let tasks = let tasks =
List.init domains (fun _ -> handler ~default routes devices user's_value) List.init domains (fun _ ->
handler ~default ~middlewares routes devices user's_value)
in in
let tasks = let tasks =
if domains > 0 then Miou.parallel (handle stop cfg) tasks else [] if domains > 0 then Miou.parallel (handle stop cfg) tasks else []

View file

@ -22,10 +22,14 @@ module U : sig
val eval : ('f, string) t -> 'f val eval : ('f, string) t -> 'f
end end
module Json = Json
module Stream = Stream module Stream = Stream
module Headers : sig module Headers : sig
type t = (string * string) list type t = (string * string) list
val add_unless_exists : t -> string -> string -> t
val get : t -> string -> string option
end end
module Method : sig module Method : sig
@ -41,8 +45,6 @@ module Method : sig
| `Other of string ] | `Other of string ]
end end
module Json = Json
module Content_type : sig module Content_type : sig
type null type null
type json type json
@ -54,6 +56,10 @@ module Content_type : sig
val any : ('c, string) t val any : ('c, string) t
end end
module M : sig
type ('cfg, 'v) t
end
module Request : sig module Request : sig
type ('c, 'a) t type ('c, 'a) t
@ -64,6 +70,13 @@ module Request : sig
val to_string : ('c, 'a) t -> string val to_string : ('c, 'a) t -> string
val to_stream : ('c, 'a) t -> string Stream.stream val to_stream : ('c, 'a) t -> string Stream.stream
val of_json : (Content_type.json, 'a) t -> ('a, [ `Msg of string ]) result val of_json : (Content_type.json, 'a) t -> ('a, [ `Msg of string ]) result
val get : ('cfg, 'v) M.t -> ('c, 'a) t -> 'v option
type request
val headers_of_request : request -> Headers.t
val method_of_request : request -> Method.t
val target_of_request : request -> string
end end
module R : sig module R : sig
@ -168,6 +181,13 @@ module S : sig
val device : ('value, 'a) D.device -> t -> 'a val device : ('value, 'a) D.device -> t -> 'a
end end
module Ms : sig
type 'cfg t = [] : 'cfg t | ( :: ) : ('cfg, 'a) M.t * 'cfg t -> 'cfg t
type ('cfg, 'v) fn = Request.request -> string -> S.t -> 'cfg -> 'v option
val make : name:string -> ('cfg, 'v) fn -> ('cfg, 'v) M.t
end
module Status : sig module Status : sig
type t = type t =
[ `Accepted [ `Accepted
@ -257,6 +277,7 @@ val config :
val run : val run :
?cfg:config ?cfg:config
-> ?devices:'value Ds.t -> ?devices:'value Ds.t
-> ?middlewares:'value Ms.t
-> default:(('c, string) Request.t -> string -> S.t -> 'value -> Response.t) -> default:(('c, string) Request.t -> string -> S.t -> 'value -> Response.t)
-> (S.t -> 'value -> Response.t) R.route list -> (S.t -> 'value -> Response.t) R.route list
-> 'value -> 'value

View file

@ -2,3 +2,5 @@ type t = (string * string) list
let add_unless_exists hdrs k v = let add_unless_exists hdrs k v =
if List.mem_assoc k hdrs then hdrs else (k, v) :: hdrs if List.mem_assoc k hdrs then hdrs else (k, v) :: hdrs
let get hdrs key = List.assoc_opt key hdrs

View file

@ -0,0 +1,34 @@
module Key = struct
type 'a t = { name: string }
let make ~name = { name }
end
module Hmap = Hmap.Make (Key)
type ('cfg, 'v) fn = Vif_request0.t -> string -> Vif_s.t -> 'cfg -> 'v option
type ('cfg, 'v) t = Middleware : ('cfg, 'v) fn * 'v Hmap.key -> ('cfg, 'v) t
type 'cfg m = [] : 'cfg m | ( :: ) : ('cfg, 'a) t * 'cfg m -> 'cfg m
type ('value, 'a, 'c) ctx = {
server: Vif_s.t
; request: Vif_request0.t
; target: string
; user's_value: 'value
}
let make : type v. name:string -> ('cfg, v) fn -> ('cfg, v) t =
fun ~name fn ->
let key = Hmap.Key.create (Key.make ~name) in
Middleware (fn, key)
let rec run : type v. v m -> (v, 'a, 'c) ctx -> Hmap.t -> Hmap.t =
fun lst ctx env ->
match lst with
| [] -> env
| Middleware (fn, key) :: r -> begin
match fn ctx.request ctx.target ctx.server ctx.user's_value with
| Some value -> run r ctx (Hmap.add key value env)
| None -> run r ctx env
| exception _exn -> run r ctx env
end

View file

@ -6,19 +6,23 @@ type ('c, 'a) t = {
request: [ `V1 of H1.Request.t | `V2 of H2.Request.t ] request: [ `V1 of H1.Request.t | `V2 of H2.Request.t ]
; body: [ `V1 of H1.Body.Reader.t | `V2 of H2.Body.Reader.t ] ; body: [ `V1 of H1.Body.Reader.t | `V2 of H2.Body.Reader.t ]
; encoding: ('c, 'a) Vif_content_type.t ; encoding: ('c, 'a) Vif_content_type.t
; env: Vif_m.Hmap.t
} }
let of_reqd : type c a. let of_reqd : type c a.
encoding:(c, a) Vif_content_type.t -> Httpcats.Server.reqd -> (c, a) t = encoding:(c, a) Vif_content_type.t
fun ~encoding -> function -> env:Vif_m.Hmap.t
-> Httpcats.Server.reqd
-> (c, a) t =
fun ~encoding ~env -> function
| `V1 reqd -> | `V1 reqd ->
let request = `V1 (H1.Reqd.request reqd) in let request = `V1 (H1.Reqd.request reqd) in
let body = `V1 (H1.Reqd.request_body reqd) in let body = `V1 (H1.Reqd.request_body reqd) in
{ request; body; encoding } { request; body; encoding; env }
| `V2 reqd -> | `V2 reqd ->
let request = `V2 (H2.Reqd.request reqd) in let request = `V2 (H2.Reqd.request reqd) in
let body = `V2 (H2.Reqd.request_body reqd) in let body = `V2 (H2.Reqd.request_body reqd) in
{ request; body; encoding } { request; body; encoding; env }
let target { request; _ } = let target { request; _ } =
match request with match request with
@ -102,3 +106,12 @@ let of_json : type a.
error_msgf "Invalid JSON value" error_msgf "Invalid JSON value"
end end
end end
let get : type v. ('cfg, v) Vif_m.t -> ('a, 'c) t -> v option =
fun (Vif_m.Middleware (_, key)) { env; _ } -> Vif_m.Hmap.find key env
type request = Vif_request0.t
let headers_of_request = Vif_request0.headers
let method_of_request = Vif_request0.meth
let target_of_request = Vif_request0.target

17
lib/vif/vif_request0.ml Normal file
View file

@ -0,0 +1,17 @@
type t = V1 of H1.Request.t | V2 of H2.Request.t
let of_reqd = function
| `V1 reqd -> V1 (H1.Reqd.request reqd)
| `V2 reqd -> V2 (H2.Reqd.request reqd)
let headers = function
| V1 req -> H1.Headers.to_list req.H1.Request.headers
| V2 req -> H2.Headers.to_list req.H2.Request.headers
let meth = function
| V1 req -> req.H1.Request.meth
| V2 req -> req.H2.Request.meth
let target = function
| V1 req -> req.H1.Request.target
| V2 req -> req.H2.Request.target