From 044c0d6163f1d807b871317c170c59a606d347b5 Mon Sep 17 00:00:00 2001 From: Tommi Reiman Date: Fri, 17 Aug 2018 14:59:02 +0300 Subject: [PATCH] http-router --- modules/reitit-core/src/reitit/core.cljc | 2 +- modules/reitit-http/src/reitit/http.cljc | 102 +++++++++++++------- modules/reitit-ring/src/reitit/ring.cljc | 114 ++++++++++++----------- 3 files changed, 127 insertions(+), 91 deletions(-) diff --git a/modules/reitit-core/src/reitit/core.cljc b/modules/reitit-core/src/reitit/core.cljc index 70a5680a..88503b2c 100644 --- a/modules/reitit-core/src/reitit/core.cljc +++ b/modules/reitit-core/src/reitit/core.cljc @@ -335,7 +335,7 @@ (str ":single-static-path-router requires exactly 1 static route: " compiled-routes) {:routes compiled-routes}))) (let [[n :as names] (find-names compiled-routes opts) - [[p data result] :as compiled] compiled-routes + [[p data result]] compiled-routes p #?(:clj (.intern ^String p) :cljs p) match (->Match p data result {} p) routes (uncompile-routes compiled-routes)] diff --git a/modules/reitit-http/src/reitit/http.cljc b/modules/reitit-http/src/reitit/http.cljc index 8da70d88..a7039ff1 100644 --- a/modules/reitit-http/src/reitit/http.cljc +++ b/modules/reitit-http/src/reitit/http.cljc @@ -5,37 +5,16 @@ [reitit.core :as r] [reitit.impl :as impl])) -(defrecord Endpoint [data handler path method interceptors]) +(defrecord Endpoint [data handler path method interceptors queue]) -(defn http-handler - "Creates a ring-handler out of a http-router and - an interceptor runner. - Optionally takes a ring-handler which is called - in no route matches." - ([router runner] - (http-handler router runner nil)) - ([router runner default-handler] - (let [default-handler (or default-handler (fn ([_]) ([_ respond _] (respond nil))))] - (with-meta - (fn [request] - (if-let [match (r/match-by-path router (:uri request))] - (let [method (:request-method request) - path-params (:path-params match) - result (:result match) - interceptors (-> result method :interceptors) - request (-> request - (impl/fast-assoc :path-params path-params) - (impl/fast-assoc ::r/match match) - (impl/fast-assoc ::r/router router))] - (:response (runner interceptors request))) - (default-handler request))) - {::r/router router})))) - -(defn get-router [handler] - (-> handler meta ::r/router)) - -(defn get-match [request] - (::r/match request)) +(defprotocol Executor + (queue + [this interceptors] + "takes a sequence of interceptors and compiles them to queue for the executor") + (execute + [this request interceptors] + [this request interceptors respond raise] + "executes the interceptor chain")) (defn coerce-handler [[path data] {:keys [expand] :as opts}] [path (reduce @@ -44,7 +23,7 @@ (update acc method expand opts) acc)) data ring/http-methods)]) -(defn compile-result [[path data] opts] +(defn compile-result [[path data] {:keys [::queue] :as opts}] (let [[top childs] (ring/group-keys data) ->handler (fn [handler] (if handler @@ -54,10 +33,13 @@ (let [data (update data :handler ->handler)] (interceptor/compile-result [path data] opts scope))) ->endpoint (fn [p d m s] - (-> (compile [p d] opts s) - (map->Endpoint) - (assoc :path p) - (assoc :method m))) + (let [compiled (compile [p d] opts s) + interceptors (:interceptors compiled)] + (-> compiled + (map->Endpoint) + (assoc :path p) + (assoc :method m) + (assoc :queue ((or queue identity) interceptors))))) ->methods (fn [any? data] (reduce (fn [acc method] @@ -94,3 +76,53 @@ ([data opts] (let [opts (meta-merge {:coerce coerce-handler, :compile compile-result} opts)] (r/router data opts)))) + +(defn ring-handler + "Creates a ring-handler out of a http-router, + a default ring-handler and options map, with the following keys: + + | key | description | + | ----------------|-------------| + | `:executor` | [[Executor]] for the interceptor chain + | `:interceptors` | Optional sequence of interceptors that are always run before any other interceptors, even for the default handler" + [router default-handler {:keys [executor interceptors]}] + (let [default-handler (or default-handler (fn ([_]) ([_ respond _] (respond nil)))) + default-queue (queue executor (interceptor/into-interceptor (concat interceptors [default-handler]) nil (r/options router))) + router-opts (-> (r/options router) + (assoc ::queue (partial queue executor)) + (update :interceptors (partial concat interceptors))) + router (router (r/routes router) router-opts)] + (with-meta + (fn + ([request] + (if-let [match (r/match-by-path router (:uri request))] + (let [method (:request-method request) + path-params (:path-params match) + result (:result match) + interceptors (-> result method :interceptors) + request (-> request + (impl/fast-assoc :path-params path-params) + (impl/fast-assoc ::r/match match) + (impl/fast-assoc ::r/router router))] + (execute executor interceptors request)) + (execute executor default-queue request))) + ([request respond raise] + (if-let [match (r/match-by-path router (:uri request))] + (let [method (:request-method request) + path-params (:path-params match) + result (:result match) + interceptors (-> result method :interceptors) + request (-> request + (impl/fast-assoc :path-params path-params) + (impl/fast-assoc ::r/match match) + (impl/fast-assoc ::r/router router))] + (execute executor interceptors request respond raise)) + (execute executor default-queue request respond raise)) + nil)) + {::r/router router}))) + +(defn get-router [handler] + (-> handler meta ::r/router)) + +(defn get-match [request] + (::r/match request)) diff --git a/modules/reitit-ring/src/reitit/ring.cljc b/modules/reitit-ring/src/reitit/ring.cljc index c01b1434..6e6a3b90 100644 --- a/modules/reitit-ring/src/reitit/ring.cljc +++ b/modules/reitit-ring/src/reitit/ring.cljc @@ -3,9 +3,8 @@ [reitit.middleware :as middleware] [reitit.core :as r] [reitit.impl :as impl] - #?@(:clj [ - [ring.util.mime-type :as mime-type] - [ring.util.response :as response]]) + #?@(:clj [[ring.util.mime-type :as mime-type] + [ring.util.response :as response]]) [clojure.string :as str])) (def http-methods #{:get :head :post :put :delete :connect :options :trace :patch}) @@ -19,6 +18,61 @@ [top (assoc childs k v)] [(assoc top k v) childs])) [{} {}] data)) +(defn coerce-handler [[path data] {:keys [expand] :as opts}] + [path (reduce + (fn [acc method] + (if (contains? acc method) + (update acc method expand opts) + acc)) data http-methods)]) + +(defn compile-result [[path data] opts] + (let [[top childs] (group-keys data) + ->endpoint (fn [p d m s] + (-> (middleware/compile-result [p d] opts s) + (map->Endpoint) + (assoc :path p) + (assoc :method m))) + ->methods (fn [any? data] + (reduce + (fn [acc method] + (cond-> acc + any? (assoc method (->endpoint path data method nil)))) + (map->Methods {}) + http-methods))] + (if-not (seq childs) + (->methods true top) + (reduce-kv + (fn [acc method data] + (let [data (meta-merge top data)] + (assoc acc method (->endpoint path data method method)))) + (->methods (:handler top) data) + childs)))) + +;; +;; public api +;; + +(defn router + "Creates a [[reitit.core/Router]] from raw route data and optionally an options map with + support for http-methods and Middleware. See [docs](https://metosin.github.io/reitit/) + for details. + + Example: + + (router + [\"/api\" {:middleware [wrap-format wrap-oauth2]} + [\"/users\" {:get get-user + :post update-user + :delete {:middleware [wrap-delete] + :handler delete-user}}]]) + + See router options from [[reitit.core/router]] and [[reitit.middleware/router]]." + ([data] + (router data nil)) + ([data opts] + (let [opts (meta-merge {:coerce coerce-handler, :compile compile-result} opts)] + (r/router data opts)))) + (defn routes "Create a ring handler by combining several handlers into one." [& handlers] @@ -156,7 +210,8 @@ (impl/fast-assoc ::r/match match) (impl/fast-assoc ::r/router router))] ((routes handler default-handler) request respond raise)) - (default-handler request respond raise)))) + (default-handler request respond raise)) + nil)) {::r/router router})))) (defn get-router [handler] @@ -164,54 +219,3 @@ (defn get-match [request] (::r/match request)) - -(defn coerce-handler [[path data] {:keys [expand] :as opts}] - [path (reduce - (fn [acc method] - (if (contains? acc method) - (update acc method expand opts) - acc)) data http-methods)]) - -(defn compile-result [[path data] opts] - (let [[top childs] (group-keys data) - ->endpoint (fn [p d m s] - (-> (middleware/compile-result [p d] opts s) - (map->Endpoint) - (assoc :path p) - (assoc :method m))) - ->methods (fn [any? data] - (reduce - (fn [acc method] - (cond-> acc - any? (assoc method (->endpoint path data method nil)))) - (map->Methods {}) - http-methods))] - (if-not (seq childs) - (->methods true top) - (reduce-kv - (fn [acc method data] - (let [data (meta-merge top data)] - (assoc acc method (->endpoint path data method method)))) - (->methods (:handler top) data) - childs)))) - -(defn router - "Creates a [[reitit.core/Router]] from raw route data and optionally an options map with - support for http-methods and Middleware. See [docs](https://metosin.github.io/reitit/) - for details. - - Example: - - (router - [\"/api\" {:middleware [wrap-format wrap-oauth2]} - [\"/users\" {:get get-user - :post update-user - :delete {:middleware [wrap-delete] - :handler delete-user}}]]) - - See router options from [[reitit.core/router]] and [[reitit.middleware/router]]." - ([data] - (router data nil)) - ([data opts] - (let [opts (meta-merge {:coerce coerce-handler, :compile compile-result} opts)] - (r/router data opts))))