Passing state as parameter to a ring handler?

前端 未结 3 512
渐次进展
渐次进展 2021-01-02 00:24

How does one inject state into ring handlers most conveniently (without using global vars)?

Here is an example:

(defroutes main-routes
  (GET \"/api/         


        
相关标签:
3条回答
  • 2021-01-02 00:57

    In addition to what Alex described some routing frameworks for ring have a place for additional arguments which can be accessed by all handlers. In reitit this would work by putting custom objects under :data:

     (reiti.ring/ring-handler
       (reiti.ring/router
        [ ["/api"
          ["/math" {:get {:parameters {:query {:x int?, :y int?}}
                          :responses  {200 {:body {:total pos-int?}}}
                          :handler    (fn [{{{:keys [x y]} :query} :parameters}]
                                        {:status 200
                                         :body   {:total (+ x y)}})}}]] ]
        {:syntax    :bracket
         :exception pretty/exception
         :data      {:your-custom-data your-custom-data
                     :coercion   reitit.coercion.spec/coercion
                     :muuntaja   m/instance
                     :middleware []}}))
    

    In your handler you're supposed to only work with :parameters, but you will be able to access your custom data by selecting :reitit.core/match and :data. The argument that the handler receives is based entirely on this:

    (defrecord Match [template data result path-params path])
    (defrecord PartialMatch [template data result path-params required])
    
    0 讨论(0)
  • The "correct" way to do this is to use a dynamically bound var. You define a var with:

    (def ^:dynamic some-state nil)
    

    And then you create some ring middleware which binds the var for each handler call:

    (defn wrap-some-state-middleware [handler some-state-value]
      (fn [request]
        (bind [some-state some-state-value]
          (handler request))))
    

    You would use this to inject dependencies by using this in your 'main' function where you launch the server:

    (def app (-> handler
                 (wrap-some-state-middleware {:db ... :log ...})))
    
    0 讨论(0)
  • 2021-01-02 01:13

    I've seen this done a couple of ways. The first is using middleware that injects the state as a new key in the request map. For instance:

    (defroutes main-routes
      (GET "/api/fu" [:as request]
        (rest-of-the-app (:app-state request))))
    
    (defn app-middleware [f state]
      (fn [request]
        (f (assoc request :app-state state))))
    
    (def app
      (-> main-routes
          (app-middleware (create-app-state))
          handler/api))
    

    The other approach is to replace the call to defroutes, which behind the scenes will create a handler and assign it to a var, with a function that will accept some state and then create the routes, injecting the state as parameters to function calls within the route definitions:

    (defn app-routes [the-state]
      (compojure.core/routes
        (GET "/api/fu" [] (rest-of-the-app the-state))))
    
    (def app
      (-> (create-app-state)
          app-routes
          api/handler))
    

    Given a choice, I'd probably go with the second approach.

    0 讨论(0)
提交回复
热议问题