reitit

agorgl 2024-07-09T16:42:51.707399Z

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?

ikitommi 2024-07-09T17:00:39.761529Z

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}]].

agorgl 2024-07-09T17:02:47.197369Z

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

agorgl 2024-07-09T17:04:18.297379Z

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)

ikitommi 2024-07-09T17:05:25.109279Z

good point, would you like to update the docs/docstrings for this?

agorgl 2024-07-09T17:06:52.352259Z

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

👍 1
agorgl 2024-07-09T17:08:30.803399Z

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

ikitommi 2024-07-09T17:09:58.276689Z

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

👍 1
ikitommi 2024-07-09T17:10:21.746519Z

with Virtual Threads, synchronous ring is both simple and performant.

Nim Sadeh 2024-07-09T17:00:03.252459Z

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.

Nim Sadeh 2024-07-09T17:18:47.731419Z

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.');
}

Nim Sadeh 2024-07-09T18:47:05.621839Z

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

agorgl 2024-07-09T17:14:26.287869Z

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

ikitommi 2024-07-10T15:31:11.504519Z

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.

ikitommi 2024-07-10T15:32:21.710729Z

I'm using integrant-repl and have a keyboard shortcut for reset, takes few millis and refreshes everything.

agorgl 2024-07-10T16:33:50.571299Z

Yeah I'll probably go that way too 🙂

agorgl 2024-07-09T17:16:40.365029Z

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)

ikitommi 2024-07-09T17:17:11.817499Z

there is reitit.ring/reloading-ring-handler that re-creates router for every request

ikitommi 2024-07-09T17:17:32.659239Z

not sure if that's documented...

agorgl 2024-07-09T17:18:50.765079Z

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)

agorgl 2024-07-09T17:36:16.917429Z

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 this

ikitommi 2024-07-09T17:52:17.283649Z

No, 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.

agorgl 2024-07-09T17:53:35.442739Z

but the top level var uses ring-reload/wrap-reload that covers this, doesn't it?

ikitommi 2024-07-09T17:53:41.369789Z

Reloading-ring-handler reloads on each request, makes <1ms, good for dev

ikitommi 2024-07-09T17:53:53.571119Z

yes, that should cover it, I think

agorgl 2024-07-09T17:54:33.115719Z

yes, everything above is for dev only

agorgl 2024-07-09T17:56:02.052629Z

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)

ikitommi 2024-07-09T17:58:54.752349Z

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.

agorgl 2024-07-09T18:22:17.731939Z

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

agorgl 2024-07-09T18:23:27.142519Z

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

agorgl 2024-07-09T18:33:37.794129Z

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 ?

agorgl 2024-07-10T16:38:28.687539Z

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}]]

wevrem 2024-07-10T18:19:03.113539Z

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}]]

👍 1
wevrem 2024-07-10T18:19:46.649569Z

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}]]

wevrem 2024-07-10T18:20:39.717569Z

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}]]

agorgl 2024-07-10T18:59:31.638109Z

Kinda counter-intuitive, but thanks for the explanation!

wevrem 2024-07-09T19:51:13.105579Z

Does the nested version produce “//sample” as the single route? (Note the two slashes.) That’s probably not what you want.