Question

I have some routes.

(defroutes some-routes
    (GET "one" [] one)
    (GET "two" [] two))

(defroutes other-routes
  (GET "three" [] three)
  (GET "four" [] four))

(defroutes more-routes
  (GET "five" [] five)
  (GET "six" [] six))


(def all-routes 
  (routes app-routes
          (-> some-routes session/wrap-session my-interceptor)
          (-> more-routes session/wrap-session my-other-interceptor)
          other-routes))

I want to intercept the some-routes but not other-routes and perform a test based on the request (checking that a key exists in the session and some other stuff). I have more than one of these. my-other-interceptor does the same kind of thing but different.

So I start with this:

(defn my-interceptor [handler]
    (fn [request]
      (prn (-> request :session :thing-key))
        (let [thing (-> request :session :thing-key-id)]
          (if (nil? thing)
            (-> (response "Not authenticated"))
            (handler request)))))

This will allow access to the handler if :thing-key is set in the session.

Unfortunately this doesn't play nicely with having more than one set of routes. This check should only apply to some-routes and not other-routes. But until we execute the handler we don't know if the route matches. And at that point the handler has already executed. I could rewrite it to execute handler and then only perform the check if the response is non-nil, but this means I've executed a handler before checking for auth.

I followed this example, which exhibits the problem:

(defn add-app-version-header [handler]
  (fn [request]
    (let [resp (handler request)
      headers (:headers resp)]
      (assoc resp :headers 
        (assoc headers "X-APP-INFO" "MyTerifficApp Version 0.0.1-Alpha")))))

How do I do this? What I want is:

  • a way of checking a response (and some other logic) before handling a request
  • that I can apply to a large-ish set of routes' handlers
  • that isn't applied to all routes in the app
  • I will have more than one such handler doing a different kind of check on the session

How should I go about doing this?

Was it helpful?

Solution

The way to have separate handlers or middlewares is to decompose your routes using compojure.core/routes and use your handler only where you need it.

In your case, if you put your other-routes first, your problem should be solved.

As in:

(def app-routes
   (compojure.core/routes
       other-routes
       (-> some-routes
           session/wrap-session
           my-interceptor)))

Remember compojure routes are just ring handlers, you can always write a custom defroutes that calls your handler only if the route matches the request, this is the make route source code

(defn make-route
  "Returns a function that will only call the handler if the method and Clout
   route match the request."
  [method route handler]
  (if-method method
    (if-route route
      (fn [request]
        (render (handler request) request)))))

That way, if you have more than one conditioned handler you don't need to rely on putting those routes at the end of the composition.

Notice that approach is in case you want to keep your route handling code clean.

(my-def-routes routes
    (GET "/" request (show-all request))

If you don't wanna roll your own defroutes just call your interceptor inside:

(defroutes routes
   (GET "/" request (interceptor request show-all))

(defn interceptor
  [request handler]
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top