Fork me on GitHub
#reitit
<
2023-09-22
>
socksy10:09:49

I'm having some difficulty getting the reloading-ring-handler to work, and I think it's because my brain is not fully good. I'm using integrant, and I have a router state that looks like this:

(defmethod ig/init-key ::router [_ {:keys [config routes]}]
  (if (get-in config [:reitit-router :reloading-router?])
    (fn [] (reitit.http/router routes))
    (constantly (reitit.http/router routes))))
which, from my understanding is recommended from the https://cljdoc.org/d/fi.metosin/reitit/0.7.0-alpha6/doc/advanced/dev-workflow#an-easy-fix. routes here is from an integrant state, which consists of things like:
(defmethod ig/init-key ::version-routes [_ _]
  [["/version" {:get {:no-auth? true
                      :handler version}}]])
After this, I can call this state as a function, and it returns a router (either dynamically compiled or cached in a closure, depending on the config). Now I want to use this config in a ring router. So I have a function make-handler, that uses reitit.ring/reloading-ring-handler like so:
(defn make-handler [router reloading?]
  (let [handler (fn []
                  (reitit.http/ring-handler (router)
                                            (make-default-handler)
                                            {:executor sieppari/executor}))]
    (if reloading?
      (reitit.ring/reloading-ring-handler handler)
      (handler))))
which seems to fit the doc string of the reloading-ring-handler . The router passed in is the ::router state from above, which is called as a function inside the ring-handler. I'm thinking maybe this is where my issue lies, since I am guessing ring-handler closes over (router), but I don't know how to fix it. For completeness, I'm using it from a jetty state like so:
(defmethod ig/init-key ::jetty [_ {:keys [config router port]}]
  (jetty/run-jetty (make-handler router
                                 bearer-token-interceptor
                                 (get-in config [:reitit-router :reloading-router?]))
                   {:port port :join? false}))
So my question — is there a way to get this to work so that when I eval a handler in the REPL, it will immediately be reflected the next time I GET this route?

Felipe12:09:49

maybe -- just maybe -- move make-handler to a separate fn and pass it to run-jetty with (jetty/run-jetty #'handler)?

Felipe12:09:33

I took integrant out of the equation and got this minimal example to work:

(ns reloading-handler
  (:require
   [reitit.ring :as ring]
   [ring.adapter.jetty :as jetty]))

(defn hello-handler [_req] {:status 200 :body "Something else"})

(def router #(ring/router ((fn [& _] [["/hello" hello-handler]]))))

(def app (ring/reloading-ring-handler #(ring/ring-handler (router))))

(defn start-server! [& _args] (jetty/run-jetty #'app {:port 8080, :join? false}))

(comment
  (def server (start-server!))
  (require '[clojure.java.shell :as shell])
  (:out (shell/sh "curl" ""))
  nil)

Felipe12:09:32

hmm, if I use plain run-jetty app it still works

socksy13:09:32

Hey Felipe! Hope all is good :) I think integrant might be part of the problem here. There's no (def app) that you can use a var deref to talk to — I suppose I could make one using (alter-var-root!) but I'd really rather not... I think with your example you don't even need to have the var deref because (ring/ring-handler (router)) is basically recompiling the whole router, and that's being called each time with the (ring/reloading-ring-handler) so it shouldn't need an extra deref step? (The source for reloading-ring-handler is basically (defn reloading-ring-handler [f] (fn [request] ((f) request)))) Thinking about it a bit, I guess part of the issue is probably that routes itself is coming in statically as an integrant state. Because the handlers in the datastructures are plain vars (e.g. in the version routes it's {:handler version}, with version being a function), I guess they are freeze dried at the point of when (ig/init system) , and no matter the amount of dynamism I give the ring handler itself, it'll always be using the same function to respond? In previous routers I would always solve this with the var deref form, e.g. {:handler #'version} , but this doesn't work with reitit :(

socksy13:09:22

hmm I have been informed by a colleague that #'handler is working on another project. I notice that I get a multimethod error like

HTTP ERROR 500 clojure.lang.ExceptionInfo: No implementation of method: :into-interceptor of protocol: #'reitit.interceptor/IntoInterceptor found for class: clojure.lang.Var
whereas I see in https://clojurians-log.clojureverse.org/reitit/2021-01-18 that there was previously another multimethod failure (of the protocol #'reitit.core/Expand). I am wondering if that was solved, but for some reason it isn't solved when using sieppari, and the interceptors can't compile?

socksy13:09:26

aha!

(extend-protocol reitit.interceptor/IntoInterceptor
  clojure.lang.Var
  (into-interceptor [this data opts]
    (reitit.interceptor/into-interceptor (var-get this) data opts)))
this makes it work finally! Now to work out what of all these different reloadable-router stuff is actually necessary

Felipe14:09:33

wow, nice!

socksy13:09:22
replied to a thread:I'm having some difficulty getting the `reloading-ring-handler` to work, and I think it's because my brain is not fully good. I'm using integrant, and I have a router state that looks like this: (defmethod ig/init-key ::router [_ {:keys [config routes]}] (if (get-in config [:reitit-router :reloading-router?]) (fn [] (reitit.http/router routes)) (constantly (reitit.http/router routes)))) which, from my understanding is recommended from the https://cljdoc.org/d/fi.metosin/reitit/0.7.0-alpha6/doc/advanced/dev-workflow#an-easy-fix. `routes` here is from an integrant state, which consists of things like: (defmethod ig/init-key ::version-routes [_ _] [["/version" {:get {:no-auth? true :handler version}}]]) After this, I can call this state as a function, and it returns a router (either dynamically compiled or cached in a closure, depending on the config). Now I want to use this config in a ring router. So I have a function `make-handler`, that uses `reitit.ring/reloading-ring-handler` like so: (defn make-handler [router reloading?] (let [handler (fn [] (reitit.http/ring-handler (router) (make-default-handler) {:executor sieppari/executor}))] (if reloading? (reitit.ring/reloading-ring-handler handler) (handler)))) which seems to fit the doc string of the `reloading-ring-handler` . The `router` passed in is the `::router` state from above, which is called as a function inside the `ring-handler`. I'm thinking maybe this is where my issue lies, since I am guessing `ring-handler` closes over `(router)`, but I don't know how to fix it. For completeness, I'm using it from a jetty state like so: (defmethod ig/init-key ::jetty [_ {:keys [config router port]}] (jetty/run-jetty (make-handler router bearer-token-interceptor (get-in config [:reitit-router :reloading-router?])) {:port port :join? false})) So my question — is there a way to get this to work so that when I eval a handler in the REPL, it will immediately be reflected the next time I `GET` this route?

hmm I have been informed by a colleague that #'handler is working on another project. I notice that I get a multimethod error like

HTTP ERROR 500 clojure.lang.ExceptionInfo: No implementation of method: :into-interceptor of protocol: #'reitit.interceptor/IntoInterceptor found for class: clojure.lang.Var
whereas I see in https://clojurians-log.clojureverse.org/reitit/2021-01-18 that there was previously another multimethod failure (of the protocol #'reitit.core/Expand). I am wondering if that was solved, but for some reason it isn't solved when using sieppari, and the interceptors can't compile?