Hello! I'm reading the reitit docs and I have a question: Is :middleware keyword in reitit.ring/ring-handler the same as wrapping the ring-handler result with the same wrappers? Why use that instead of normal composition like any other ring middleware?
Good question, they behave exactly the same. Why use the option? You can compose them in the same way as route-spesific middleware, e.g. [[wrap-something {:key true}]].
I suppose the only reason to use this, is to have consistency between the middlewares wrapping the whole routes-handler and the middlewares applied to specific routes
An explanation on ordering would be nice too, I suppose having
:middleware [wrap-something-1 wrap-something-2 wrap-something-3]
is the same as
(wrap-something-3 (wrap-something-2 (wrap-something-1 handler)))
and same as (-> handler wrap-something-1 wrap-something-2 wrap-something-3)
good point, would you like to update the docs/docstrings for this?
Yes of course! I'll mess around a little bit to make sure I get it correctly and I'll make a PR for this
My previous project was with pedestal and interceptors although a bit more complex seem a bit more intuitive to me, compared to middlewares that kinda combine similar functionality to a single concept
as the chain is a data, you could also do: • generate documentation (e.g. a web page of the router: routes, middleware it has etc • transform the chain programmatically, e.g. reorder, interleave with debug/logging etc. there are examples on how to do this for routes
with Virtual Threads, synchronous ring is both simple and performant.
Hello! I have a router that needs the raw request body for hmac-based verification. My router has coercions that populate the body-params from the request body, which seems to empty out the body. Is there a way to get the raw request body without moving the coercion out? I tried to reserialize the body with Cheshire but that didn't work.
I am basically trying to replicate this Node.js code, but I can't without the raw body
const crypto = require('crypto');
const secret = '[SIGNING_SECRET]';
const hmac = crypto.createHmac('sha256', secret);
const digest = Buffer.from(hmac.update(request.rawBody).digest('hex'), 'utf8');
const signature = Buffer.from(request.get('X-Signature') || '', 'utf8');
if (!crypto.timingSafeEqual(digest, signature)) {
throw new Error('Invalid signature.');
}This basically boils down to needing to get the raw request body after reitit.coercion.malli/coercion , coercion/coerce-request-middleware, and/or muuntaja/format-request-middleware - I am wondering if there's a way to do that or to add a middleware that preserves the raw request
Another question I'm pretty sure has been asked multiple times: What would be the proper way to implement cascaded hot reloading in the handlers, route definitions as well as middleware applied to them? I suppose there are multiple ways of achieving this, I'm looking for the proper/least wasteful one in terms of reloading the stuff it needs
Hmm. I think you are right on cascading constants, e.g. defs. If your route is generated with functions returning route sniplets, the functions get recalled on each request and whatever Vars they are referencing, get their latest value.
I'm using integrant-repl and have a keyboard shortcut for reset, takes few millis and refreshes everything.
Yeah I'll probably go that way too 🙂
For a simple ring app, wrapping a var'ed #'handler to wrap-reload would suffice before passing them to run-jetty like this:
(jetty/run-jetty (ring-reload/wrap-reload #'handler)there is reitit.ring/reloading-ring-handler that re-creates router for every request
not sure if that's documented...
i read the source of reloading-ring-handler isn't it the same as doing:
(def routes
["/" {:get index}])
(def routes-handler
(reitit-ring/ring-handler
(reitit-ring/router routes)
(reitit-ring/routes
(reitit-ring/create-resource-handler {:path "/"})
(reitit-ring/create-default-handler
{:not-found (constantly (resp/not-found "Not found"))}))))
(jetty/run-jetty #'routes-handler opts)So far my hot reloading setup is this:
(def routes
["/" {:get index}])
(defn routes-handler []
(reitit-ring/ring-handler
(reitit-ring/router routes)
(reitit-ring/routes
(reitit-ring/create-resource-handler {:path "/"})
(reitit-ring/create-default-handler
{:not-found (constantly (resp/not-found "Not found"))}))))
(defn wrap-devel [handler]
(-> handler
(ring-reload/wrap-reload)
(ring-refresh/wrap-refresh)))
(def handler
(-> (routes-handler)
(ring-defaults/wrap-defaults ring-defaults/site-defaults)
(wrap-devel)))
(defn start []
(let [opts {:port 8080 :join? false}]
(jetty/run-jetty #'handler opts)))
It seems to work but I'm trying to understand it in depth to find out if there is a better / more proper way to do thisNo, it's not: if you have routes spanning multiple namespaces and you reload one namespace (which doesn't contain the top-level Var) or redefine one Var that returns routes - the top-level Var-reference doesn't see this and nothing happens.
but the top level var uses ring-reload/wrap-reload that covers this, doesn't it?
Reloading-ring-handler reloads on each request, makes <1ms, good for dev
yes, that should cover it, I think
yes, everything above is for dev only
I thought that wrapping something in a var and dereferencing that var later is the same as wrapping this something into a function that returns this something and calling the function later, so #'handler should be equal to (reloading-ring-handler handler)
please look at the source code, reloading-ring-handler requires a 0-arity fn that is called on request-time. Example in docstring. So, it's different.
https://github.com/metosin/reitit/blob/master/modules/reitit-ring/src/reitit/ring.cljc#L371
Hmm I think I get the difference now and why both ways have similar results:
1. In the case of #'handler with wrap-reload : When a request is made after any code change, the wrap-reload uses tools.namespace and reloads/re-evals the changed namespaces before handling the response. After the reload the request is passed through the var'd #'handler that dereferences the latest handler value that is rebuilt with a new router after the reload
2. In the case of reloading-ring-handler the whole ring-handler middleware is being rebuilt in every request through the function passed as argument to reloading-ring-handler . In this case though it just rebuilds the router and does not do any cascading re-evaluation of any dependencies that might have changed (like handlers). It will work only if the relevant functions/vars used by the router are already re-evaluated beforehand
In the second case to have a hot-reload on save workflow we would still need something like wrap-reload/tools.namespace to re-eval all the needed router dependencies before reloading-ring-handler rebuilds the router
Yet another question: In the docs, nested routes seem to be supported, so this should be working but it doesn't:
(def routes
["/" {:get index}
["/sample" {:get sample}]])
(defn routes-handler []
(reitit-ring/ring-handler
(reitit-ring/router routes)
(reitit-ring/routes
(reitit-ring/create-resource-handler {:path "/"})
(reitit-ring/create-default-handler
{:not-found (constantly (resp/not-found "Not found"))}))))
It returns 404 for both / and /sample paths
In the contrary, this (flattened) routes specification works as expected:
(def routes
[["/" {:get index}]
["/sample" {:get sample}]])
Is this a bug in the implementation or is it a limitation of reitit.ring/router in contrary to the core reitit.core/router ?Yep, thats what it produces:
[["//sample"
{:get {:handler #function[example.ring-playground.server/sample]}}
{:get
{:data {:handler #function[example.ring-playground.server/sample]},
:handler #function[example.ring-playground.server/sample],
:path "//sample",
:method :get,
:middleware []},
:head nil,
:post nil,
:put nil,
:delete nil,
:connect nil,
:options
{:data
{:no-doc true,
:handler #function[reitit.ring/fn--15624/fn--15633]},
:handler #function[reitit.ring/fn--15624/fn--15633],
:path "//sample",
:method :options,
:middleware []},
:trace nil,
:patch nil}]]I don’t think it’s a bug, but it has caused me confusion at times. If you want to go nested, you need a final “leaf” branch for every desired route. You don’t get a route for a “parent”. So you could do something like this:
["" {:options-that-apply-to-all :child-routes ...}
["/" {:get index :child-specific :option}]
["/sample" {:get sample}]]
You could get the same result with this, moving the slash to the parent:
["/" {:options-that-apply-to-all :child-routes ...}
["" {:get index :child-specific :option}]
["sample" {:get sample}]]Or maybe you don’t want trailing slashes, so you do something like this:
["" {:options-that-apply-to-all :child-routes ...}
["" {:get index :child-specific :option}]
["/sample" {:get sample}]]Kinda counter-intuitive, but thanks for the explanation!
Does the nested version produce “//sample” as the single route? (Note the two slashes.) That’s probably not what you want.