This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-02-23
Channels
- # announcements (2)
- # atom-editor (3)
- # babashka (49)
- # beginners (100)
- # biff (9)
- # calva (78)
- # clj-kondo (18)
- # clojure (143)
- # clojure-europe (13)
- # clojure-germany (1)
- # clojure-nl (2)
- # clojure-spec (5)
- # clojure-sweden (2)
- # clojure-uk (4)
- # clojurescript (58)
- # conjure (1)
- # cursive (4)
- # datascript (11)
- # datomic (63)
- # docker (7)
- # emacs (18)
- # events (1)
- # fulcro (18)
- # graalvm (5)
- # helix (4)
- # improve-getting-started (13)
- # jobs (4)
- # jobs-discuss (3)
- # lsp (15)
- # malli (90)
- # membrane (14)
- # off-topic (12)
- # other-languages (5)
- # pedestal (7)
- # polylith (53)
- # re-frame (15)
- # reitit (23)
- # releases (4)
- # remote-jobs (9)
- # ring (11)
- # shadow-cljs (90)
- # specter (2)
- # testing (3)
- # tools-build (63)
- # vim (2)
- # xtdb (8)
Hi! I've been debugging an issue where a middleware is being applied twice. I've narrowed it down to a smallish test case which looks like this:
(deftest middleware-being-applied-twice
(testing "middleware shouldn't be applied twice"
(let [merge-routers (fn [& routers]
(ring/router
(apply merge (map r/routes routers))
(apply merge (map r/options routers))))
counter (atom 0)
middleware (fn [handler]
(fn [request]
(swap! counter inc)
(prn "counter:" @counter)
(handler request)))
handler (fn [_req] {:status 200 :body @counter})
router1 (ring/router
[["/one" {:roles #{:public} :get handler}]])
router2 (ring/router
[["/two" {:roles #{:public} :get handler}]]
{:data {:middleware [middleware]}})
merged-routers (merge-routers router1 router2)
app (ring/routes (ring/ring-handler merged-routers))
result (app {:request-method :get, :uri "/two"})]
(is (= 1 @counter))
(prn result)
(prn @counter))))
It seems to happen when the two routers are merged. Any ideas about how to make it only apply once?Merge route trees instead of routers. Routers are separate functions, if you middlewares have side-effects, both router functions will execute them.
Or... ring-handler is a function at least,hmph.
Aha and merge takes routes from both routers and then creates one router with route trees from both
The merge-routers
function is from here:
https://github.com/metosin/reitit/blob/master/doc/advanced/composing_routers.md#merging-routers
If you just want to separate some routes per namespace etc, you don't need to create router for both, you could just define parts of route tree data. And you don't need to use router
to add route-data to every route under route tree branch:
(def tree1 [["/one" ...]])
(def tree2 [["/two" {:middleware [middleware]} ...]])
(def router (ring/router [tree1 tree2])
Doesn't really solve this, but goes around at least.Yes, admittedly a better solution, but the problem is that in the real app there are actually two routers which needs to be merged into a single one.
I'm not sure, but I think when you create router, in your code, the :data
option is "compiled" into every route inside router
. Then on the merge the routes from r/routes
call already include the middleware, and now the options merge adds another middleware when the new router is created to each route.
Try (apply merge (map #(dissoc (r/options %) :data) routers))
to drop :data
option from the merge, as they are already applied to routes (if I guess correctly what is happening.)
Thanks. That makes the test pass. But that will also not apply the middleware to the "/one" route, right..?
I'll see if I can rearrange some things and apply the router wide middleware after the merging has taken place.
Router creation does "resolve-routes" and that will apply route data merge, including route data from router opts: https://github.com/metosin/reitit/blob/master/modules/reitit-core/src/reitit/impl.cljc#L72-L74
Other router options probably don't affect the r/routes
result the same way
Ok, thanks. Good to know. Will fiddle with it some more.
Hmm, can I create a new router from an existing router while also adding some route data?
yeah, but for the meta merge will concat e.g. vector values
Ok, I think I have something which looks like it could work. Thanks for your help.
if route already has {:middleware [1]}
and router options has {:data {:middleware [2]}}
the resulting route in the router has :middleware [1 2]
Right. I've been down that path of debugging a little. I saw no option for doing some type of distinct
meta merge so I ended up with duplicates in my vectors.
Doing something along the lines of
merged-routers (merge-routers
router1
router2
(ring/router
[]
{:data {:middleware [middleware]}}))
Seemed to do the trick. Thanks again for your help.How do I coerce a list query parameter? /products?productIds=1,2
I would expect productIds = [1 2]
[["/products" {:get {:parameters {:query [:map [:productIds [:sequential int?]]]}
:responses {200 {:body [:sequential int?]}}
:handler (fn [{{productIds :query} :parameters}]
{:status 200
:body (:productIds productIds)})}}]]