Fork me on GitHub

What’s the point of interceptors having both enter and leave functions? Why are interceptors not “just functions” which can be executed in order? I read the interceptors reference but I’m having a hard time understanding what is the benefit of having both enter and leave functions? And why the request has to “make a loop” by first executing all enter functions and then all the leave functions?


@hequ Let me answer that with a practical use


First, an interceptor I wrote that sees if a later interceptor has filled in a :response, and if its a get request will fill in the standard Single Page App code


;; ----------------------------------------------------------------------------
(def serve-elm-on-404
  "Intercepts any unhandled get requests and serves the SPA."
   (fn [context]
     (if (and (not (http/response? (:response context)))
              (= :get (:request-method (:request context))))
         {:status 200
          :headers {"Content-Type" "text/html"}
            {:lang "en"}
              [:meta {:charset "UTF-8"}]
              [:style "body { padding: 0; margin: 0; }"]]
              [:div {:id "mount"}]
              [:script {:type "text/javascript"} (-> (io/resource "public/app.js") ;; TODO: Cache
              [:script {:type "text/javascript"}
               "var app = Elm.Main.init({node: document.getElementById('mount')});"]]])})


You can write this in the "function stacking" way for sure, so its not the best example, but it is a case where it is pretty clear what is going on


since you have the :response as just a detail of how the different layers communicate


the other way would look like


(defn wrap-with-spa [route]
   (fn [req]
      (let [res (route req)]
        (if (nil? res)
           (... single page app ...)


A better example might be this


(def requires-auth-interceptor
  "An interceptor that looks for a user in the request map and
  (if there is none), early returns a 401. "
  {:name ::requires-auth-interceptor
   (fn [context]
     (let [{:keys [user]} (:request context)]
       (if-not user
         (-> context
             (assoc :response unauthorized-response)


Since the interceptors are a chain, we can do meta stuff like re-arrange or add steps to what comes next


The other thing it allows is seperation of "async" steps


if one of the interceptors returns a core.async channel, pedestal will handle the juggling of that for you


and no other interceptor needs to know that what was returned happened asynchronously


(afaik, i haven't tested that for anything)


I guess it really isn't a hard need to


a whole bunch, in fact most, http frameworks get by with either middleware stacking or some other system


but there are some concrete benefits to doing it this way


since the same "chain of steps that happen one after another" model can be generalized to more than http


(huge grains of salt - I have been frequently befuddled by pedestal and that hasn't stopped)


(but I have made decent progress writing my hobby app with it so i at least kinda sorta know some things)


Thank you for the concrete examples! So to test my understanding: if your route is about getting some user-data from a database table you could write an enter interceptor which could (for example) first check if that data is already available in some cache and early exit if it is? And if not then there is an enter interceptor which could fetch that user-data and then one or more leave interceptors which would transform that data to a correct response format? Or does it even matter if those are enter or leave interceptors?

Louis Kottmann18:11:07

I'd say use :enter interceptors to build your response, and use :leave interceptors to massage it

Louis Kottmann18:11:05

for example there is a http/json-body that comes with pedestal that will set the content-type to application/json if no content-type is already set

Louis Kottmann18:11:10

you could do it in an :enter interceptor but as soon as an interceptor returns a context with a :response it will start leaving

Louis Kottmann18:11:28

so to make a reusable default it is in its own :leave interceptor


oh, so you can control the flow by attaching a response to the context. Well that’s nice.

Louis Kottmann18:11:38

I have complex a chain of boolean logic which would be tens of levels of nested conditionals with short-circuits via cond now it is a pedestal interceptor (non-http) chain and they are all packaged in their nice little interceptors, which makes it composable for other chains as well

Louis Kottmann18:11:42

it's pretty nifty

Louis Kottmann18:11:57

and yeah there are gotchas, for example I got bitten earlier today because I was declare-ing interceptors before providing them and then defining them, and pedestal crashed because some declared interceptors were "unbound" and it could not check their type so I had to reorder the code in the correct order (and lose some readability)


I probably have too limited understanding for now as my use cases are a pretty basic rest api. But thanks anyway for providing some light on this topic! 🙂