folks, can someone help me figure out how to setup ring + component the right way? I will be giving more details here in the thread, but I want to be able to change my routes, eval the route and then have it applied to ring (only worried about development at the time). Nowadays I am forced to stop and start the component for changes to be visible in the API, ideally, I would only stop and start the component if I change something in the component.
my component start function is as follows (big, I know...) but the important part is http-server/routes, which points to my reitit configuration file:
(defrecord Ring [ring service database redis config firebase]
component/Lifecycle
(start [component]
(println "Starting API Server")
(if service
component
(assoc component :service (run-jetty
(wrap-with-request-id
(logger/wrap-with-logger
(wrap-format
(wrap-params
(ring/ring-handler
(ring/router
(http-server/routes)
{:exception pretty/exception
:validate spec/validate
:data {:coercion coercion-schema/coercion
:muuntaja m/instance
:middleware [swagger/swagger-feature
openapi/openapi-feature
parameters/parameters-middleware
muuntaja/format-negotiate-middleware
muuntaja/format-response-middleware
exception-middleware
muuntaja/format-request-middleware
multipart/multipart-middleware
(create-inject-dependencies-middleware database redis config firebase)]}})
(ring/create-default-handler))))
{:logger (fn [data] (timbre/info data))
:printer :no-color
:redact-keys #{:authorization :password :cookie :Set-Cookie}})) ring))))
the http-server/routes is a function that returns a map of my routes, basically, they are as follows:
(defn routes
[]
[["/birthdays"
{:get {:description "Shows all birthdays of a user"
:responses {200 {:description "Successfully fetched all birthdays"
:body responses.birthday/BirthdaysResponse}}
:handler #'birthday-all-handler}}]]]])
and the birthday-all-handler is a function, which is irrelevant to this, but I have looked into a lot of examples, and can't figure out a way to setup ring correctly, I want to just eval a change made in the routes, or in the handler, and have it applied to ring without restarting the whole component everytime. Does anyone have a clue on what may be wrong?It may not be possible when constructing your routes as fully realized data like that
What you need is some mutable indirection, like using var quote for a handler in regular ring
You might try asking in #reitit
what do you mean by routes as fully realized data?
Oh, actually I missed it, birthday-all-handler is specified via a var
So I would expect any changes to that handler to be immediately reflected
But the rest of the route structure is specified as an immutable datastructure that reitit then compiles into something, there is no mutable reference to allow for changing it
You could add one by sticking the built ring handler in an atom, then passing run-jetty a function that derefs the atom and calls the fn in there on its args
You would then need to add some kind of rebuild/refresh mechanism that rebuilds the handler and updates the atom
(start [component]
(println "Starting API Server")
(if service
component
(assoc component :service (run-jetty
(wrap-with-request-id
(logger/wrap-with-logger
(wrap-format
(wrap-params
(ring/reloading-ring-handler
(fn [] (ring/ring-handler
(ring/router
(http-server/routes)
{:exception pretty/exception
:validate spec/validate
:data {:coercion coercion-schema/coercion
:muuntaja m/instance
:middleware [swagger/swagger-feature
openapi/openapi-feature
parameters/parameters-middleware
muuntaja/format-negotiate-middleware
muuntaja/format-response-middleware
exception-middleware
muuntaja/format-request-middleware
multipart/multipart-middleware
(create-inject-dependencies-middleware database redis config firebase)]}})
(ring/create-default-handler))))))
{:logger (fn [data] (timbre/info data))
:printer :no-color
:redact-keys #{:authorization :password :cookie :Set-Cookie}})) ring))))@ikitommi do you have any idea if reloading-ring-handler would work in this case?
It rebuilds the routing tree on each request, so great for development/repl flow
so, yes
if you are curios, I'm using mount instead of component (with reitit) and it's working fine for reload - see • https://github.com/netdava/efactura-mea/blob/main/src/efactura_mea/main.clj • https://github.com/netdava/efactura-mea/blob/main/src/efactura_mea/http_server.clj
thanks everyone!! I'm still trying to figure out what's wrong, and I have zero idea, everything I do have no effect and I can only see the changes in my reitit routes or handles when I stop and start the component, I have tried to clean up the code a little bit and see if I could figure out what's wrong, also change the order of the handles and see if I get everything right, here's my entire ring component for reference, can anyone figure it out? I believe I'm using reloading-ring-handler the right but there's something here I'm not getting:
(ns birthdayapi.components.ring
(:require
[birthdayapi.diplomat.http-server :as http-server]
[com.stuartsierra.component :as component]
[muuntaja.core :as m]
[muuntaja.middleware :refer [wrap-format wrap-params]]
[reitit.coercion.malli]
[reitit.coercion.schema :as coercion-schema]
[reitit.dev.pretty :as pretty]
[reitit.openapi :as openapi]
[reitit.ring :as ring]
[reitit.ring.malli]
[reitit.ring.middleware.exception :as exception]
[reitit.ring.middleware.multipart :as multipart]
[reitit.ring.middleware.muuntaja :as muuntaja]
[reitit.ring.middleware.parameters :as parameters]
[reitit.spec :as spec]
[reitit.swagger :as swagger]
[ring.adapter.jetty :refer [run-jetty]]
[ring.logger.timbre :as logger]
[taoensso.timbre :as timbre]))
(defn exception-handler [data _exception _request]
(let [{:keys [status body]} data]
{:status (or status 500)
:body body}))
(def exception-middleware
(exception/create-exception-middleware
(merge
exception/default-handlers
{com.google.firebase.auth.FirebaseAuthException (partial exception-handler {:status 403})
::exception/default (partial exception-handler {:status 500 :body "Server error"})
::exception/wrap (fn [handler e request]
(println {:error (pr-str (:uri request))
:exception (pr-str e)})
(handler e request))})))
(defn wrap-dependencies
[database redis config firebase handler]
(fn [request]
(handler (-> request
(assoc :system/database database)
(assoc :system/redis (:connection redis))
(assoc :system/firebase (:firebase firebase))
(assoc :system/config config)))))
(defn wrap-with-request-id
[handler]
(fn [request]
(let [request-id (or (get-in request [:headers "x-request-id"])
(str (random-uuid)))]
(timbre/with-context {:request-id request-id}
(handler request)))))
(def ring-middlewares
[swagger/swagger-feature
openapi/openapi-feature
parameters/parameters-middleware
muuntaja/format-negotiate-middleware
muuntaja/format-response-middleware
exception-middleware
muuntaja/format-request-middleware
multipart/multipart-middleware])
(def router-options
{:exception pretty/exception
:validate spec/validate
:data {:coercion coercion-schema/coercion
:muuntaja m/instance
:middleware ring-middlewares}})
(defn wrap-with-logger
[handler]
(logger/wrap-with-logger handler {:logger (fn [data] (timbre/info data))
:printer :no-color
:redact-keys #{:authorization :password :cookie :Set-Cookie}}))
(defn create-router
[]
(ring/router
(http-server/routes) router-options))
(defn create-ring-handler
[]
(ring/ring-handler
(create-router)
(ring/create-default-handler)))
(defrecord Ring [ring service database redis config firebase]
component/Lifecycle
(start [component]
(println "Starting API Server")
(let [wrap-dependencies (partial wrap-dependencies database redis config firebase)]
(if service
component
(assoc component :service
(run-jetty
(-> (ring/reloading-ring-handler create-ring-handler)
wrap-with-request-id
wrap-with-logger
wrap-format
wrap-params
wrap-dependencies)
ring)))))
(stop [component]
(println "Stopping API Server")
(when service
(.stop service))
(assoc component :service nil)))
(defn service
[ring]
(map->Ring {:ring ring}))