Question

I'm writing some middleware for Ring and I'm really confused as to why I have to reverse the order of the middleware.

I've found this blog post but it doesn't explain why I have to reverse it.

Here's a quick excerpt from the blog post:

(def app
  (wrap-keyword-params (wrap-params my-handler)))

The response would be:

{; Trimmed for brevity
 :params {"my_param" "54"}}

Note that the wrap keyword params didn't get called on it because the params hash didn't exist yet. But when you reverse the order of the middleware like so:

(def app
  (wrap-params (wrap-keyword-params my-handler)))

{; Trimmed for brevity
 :params {:my_param "54"}}

It works.

Could somebody please explain why you have to reverse the order of the middleware?

Was it helpful?

Solution

It helps to visualize what middleware actually is.

(defn middleware [handler]
  (fn [request]
    ;; ...
    ;; Do something to the request before sending it down the chain.
    ;; ...
    (let [response (handler request)]
      ;; ...
      ;; Do something to the response that's coming back up the chain.
      ;; ...
      response)))

That right there was pretty much the a-ha moment for me.

What's confusing at first glance is that middleware isn't applied to the request, which is what you're thinking of.

Recall that a Ring app is just a function that takes a request and returns a response (which means it's a handler):

((fn [request] {:status 200, ...}) request)  ;=> response

Let's zoom out a little bit. We get another handler:

((GET "/" [] "Hello") request)  ;=> response

Let's zoom out a little more. We find the my-routes handler:

(my-routes request)  ;=> response

Well, what if you wanted to do something before sending the request to the my-routes handler? You can wrap it with another handler.

((fn [req] (println "Request came in!") (my-routes req)) request)  ;=> response

That's a little hard to read, so let's break out for clarity. We can define a function that returns that handler. Middleware are functions that take a handler and wrap it another handler. It doesn't return a response. It returns a handler that can return a response.

(defn println-middleware [wrapped-func]
  (fn [req]
    (println "Request came in!")
    (wrapped-func req)))

((println-middleware my-route) request)  ;=> response

And if we need to do something before even println-middleware gets the request, then we can wrap it again:

((outer-middleware (println-middleware my-routes)) request)  ;=> response

The key is that my-routes, just like your my-handler, is the only named function that actually takes the request as an argument.

One final demonstration:

(handler3 (handler2 (handler1 request)))  ;=> response
((middleware1 (middleware2 (middleware3 handler1))) request)  ;=> response

I write so much because I can sympathize. But scroll back up to my first middleware example and hopefully it makes more sense.

OTHER TIPS

The ring middleware is a series of functions which when stacked up return a handler function.

The section of the article that answers your question:

In case of Ring wrappers, typically we have “before” decorators that perform some preparations before calling the “real” business function. Since they are higher order functions and not direct function calls, they are applied in reversed order. If one depends on the other, the dependent one needs to be on the “inside”.

Here is a contrived example:

(let [post-wrap (fn [handler]
                  (fn [request]
                    (str (handler request) ", post-wrapped")))
      pre-wrap (fn [handler]
                 (fn [request]
                   (handler (str request ", pre-wrapped"))))
      around (fn [handler]
               (fn [request]
                 (str (handler (str request ", pre-around")) ", post-around")))
      handler (-> (pre-wrap identity)
                  post-wrap
                  around)]
  (println (handler "(this was the input)")))

This prints and returns:

(this was the input), pre-around, pre-wrapped, post-wrapped, post-around
nil

As you may know the ring app is actually just a function that receives a request map and returns a response map.

In the first case the order in which the functions are applied is this:

request -> [wrap-keyword-params -> wrap-params -> my-handler] -> response

wrap-keyword-params looks for the key :params in the request but it's not there since wrap-params is the one who adds that key based on the "urlencoded parameters from the query string and form body".

When you invert the order of those two:

request -> [wrap-params -> wrap-keyword-params -> my-handler] -> response

You get the desired result since once the request gets to wrap-keyword-params, wrap-params has already added the corresponding keys.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top