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?
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.