Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Page
Library
Module
Module type
Parameter
Class
Class type
Source
router_impl.ml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
module type S = sig module IO : Types.IO type route type rate_limiter type addr type handler = addr Handler.Make(IO).t type middleware = handler -> handler val no_middleware : middleware val pipeline : middleware list -> middleware val router : route list -> handler val route : ?rate_limit:rate_limiter -> ?mw:middleware -> ?regex:bool -> string -> handler -> route val scope : ?rate_limit:rate_limiter -> ?mw:middleware -> string -> route list -> route val no_route : route val virtual_hosts : ?meth:[ `ByURL | `SNI ] -> (string * handler) list -> handler end module Make (RateLimiter : Rate_limiter_impl.S) (Logger : Logger_impl.S) : S with module IO = RateLimiter.IO and type rate_limiter := RateLimiter.t and type addr := RateLimiter.Addr.t = struct module IO = RateLimiter.IO module Addr = RateLimiter.Addr type handler = Addr.t Handler.Make(IO).t type middleware = handler -> handler type route = route' list and route' = { route : bool * string; handler : handler; rate_limit : RateLimiter.t option; } let no_route = [] let route ?rate_limit ?(mw = Fun.id) ?(regex = false) r handler = [ { route = (regex, r); handler = mw handler; rate_limit } ] let compare_url u u' = match (u, u') with | "", "/" | "/", "" | "", "" -> true | "", _ | _, "" -> false | _, _ -> if String.equal u u' then true else if String.ends_with ~suffix:"/" u then String.equal (String.sub u 0 (String.length u - 1)) u' else if String.ends_with ~suffix:"/" u' then String.equal (String.sub u' 0 (String.length u' - 1)) u else false let match_ (regex, route) path = if regex then `Grp (Re.exec_opt (Re.Perl.re route |> Re.Perl.compile) path) else `Bool (compare_url route path) let router routes req = let routes = List.concat routes in let path = Request.target req in let route = List.fold_left (fun acc { route; handler; rate_limit } -> match acc with | None -> ( match match_ route path with | `Bool true -> Some (handler, rate_limit, None) | `Grp (Some _ as g) -> Some (handler, rate_limit, g) | `Bool false | `Grp None -> None) | Some _ -> acc) None routes in match route with | None -> Response.(response Status.not_found "") |> IO.return | Some (handler, limit_opt, params) -> ( let req = Request.attach_params req params in match limit_opt with | None -> handler req | Some limiter -> ( match RateLimiter.check limiter req with | None -> Logger.info (fun log -> log "'%a' is rate limited" Addr.pp (Request.ip req)); handler req | Some resp -> resp)) let scope ?rate_limit ?(mw = Fun.id) prefix routes = List.concat routes |> List.map (fun { route = typ, r; handler; _ } -> { route = (typ, prefix ^ r); handler = mw handler; rate_limit }) let virtual_hosts ?(meth = `SNI) domains_handler req = let req_host = match meth with | `SNI -> Request.sni req | `ByURL -> Request.uri req |> Uri.host |> Option.get (* Guaranteed by [Protocol.make_request]. *) in match List.find_opt (fun (d, _) -> d = req_host) domains_handler with | None -> assert false (* Guaranteed by [Protocol.make_request]. *) | Some (_, handler) -> handler req let no_middleware h req = h req let rec pipeline mws handler = match mws with [] -> handler | m :: ms -> m (pipeline ms handler) end