Fork me on GitHub

Question: can I access the interceptor chains of other routes from inside an interceptor (through some path in the context)?


I guess I can access the router interceptor, so could that be used to construct interceptor chains for named routes inside another interceptor?


Ok, so I had a look at the Pedestal source code and this is what I came up with:

(defn- find-router*
  (loop [[ic & remaining] stack]
    (if (= (:name ic) ::route/router)
      (recur remaining))))

(def find-router
  (memoize find-router*))

(defn context->router
  (find-router (::chain/stack ctx)))

;; Modified version of `io.pedestal.http.route/try-routing-for`
(defn routing-for
  [router query-string verb]
  (let [context {:request {:path-info query-string
                           :request-method verb}}
        context ((:enter router) context)]
    (:route context)))
And then inside the Interceptor you can do e.g.
(-> (context->router ctx)
    (routing-for "/" :get))
I’m not sure if it’s possible to resolve routes by route-names in this way, though.


It seems you can do this to get a URL from the context and a route-name:

(defn url-for
  [{:keys [bindings] :as ctx} & args]
  (let [*url-for* @(get bindings #'io.pedestal.http.route/*url-for*)]
    (apply *url-for* args)))


But it doesn’t seem possible to get a fully bidirectional variant, since you still need a verb and possibly some route-specific params to resolve to the correct routing.


What do you need to do?


I’m trying to make a function to be used when generating frontend content that can determine whether access to a route is possible or not based on the existence of certain interceptors so that I can e.g. disable/remove certain links dynamically, but I want to accomplish it by reading from the service context only.


So that you can do something like [(restrict-to ...) ....] in the interceptor chain of one route and then in a response content-generating interceptor of another route you call something like (allowed? ...) in some hiccup-generating logic to determine whether a route is authenticated and/or authorised for the SAML user, using this information to modify the HTML content being served in order to nudge the user away from clicking links that will end badly (e.g. 403 forbidden).


The trick is trying to do it in a decoupled way, only considering what’s in context.


I admit it's kind of an experimental approach, but I did have to solve something similar at my old work right before I left in their huge legacy system (in fact, I hear they're still trying to solve it) and I have this feeling that a fairly integrated system could be made from a few basic interceptors and some helper functions.


I still don’t quite get it :) are you saying something like “the UI shouldn’t contain links that will be rejected anyway”? And then using your pedestal routes to figure that out?


I’m not 100% on the extra data that you can put in pedestals routes (I don’t heavily use them and for front end I use reitit) but I would probably move this info out to some other data structure and generate both interceptors and permission logic based on it...


I’m still ruminating on the frontend part, specifically how to integrate this with a SPA :)


And sure you can always go back one level, but that introduces coupling, along with additional configuration and parameters to pass along to functions. The challenge is to get rid of those :)


We introduced a permissions namespace that speaks the domain language, and many resources (served by graphql, but should work in any case) expose flags about permitted operations...


So that the SPA can do data-driven UI. Still in the discovery phase though...


Yeah, something like that is definitely an option. Direct coupling with an API is quite convenient, but if I want this to be a in a library I’m going to need something slightly more generic. Maybe it’ll take a few walks before I figure out a good way 🙂

Michael Stokley16:11:56

if i'm running a pedestal server in a separate thread (for testing purposes), how can i clean it up, or kill it? i've tried (.interrupt thread-with-running-server) but it doesn't seem to help

Michael Stokley16:11:41

we also tried putting the server in an atom and running (http/stop @server-atom) but since running the server blocks, it never gets swapped into the atom in the first place

Michael Stokley16:11:53

at least, this is what we think is happening

Michael Stokley16:11:08

should this just be handled by setting :http/join? to false?

Michael Stokley18:11:44

ok, figured this out. need to add a try catch in the function passed to the thread to allow it to be interrupt-able. then i can run whatever shutdown actions i need.


Join to false seems more robust to me

🙏 3

Do pedestal (or some pedestal lib) do routing given the HTTP domain?


In ring/pedestal, the domain is the :host


So, let me tell you a story ... the other day, we had a network problem in our load balancers; this caused IO problems writing the response to the client (`io.pedestal.http.impl.servlet-interceptor/ring-response`); the exceptions thrown were caught by stylobate and logged. The exception occured inside io.pedestal.http.impl.servlet_interceptor/write-body. • Problem was, other interceptors had attached large maps to the :request ; logging that produced 800KB of output per exception which caused a cascade of other problems • When using io.pedestal.http/create-server you end up threading through io.pedestal.http.impl.servlet-interceptor/http-interceptor-service-fn where stylobate and ring-response interceptors are added • This is in internal implementation, and things are private; very hard to override stylobate's behavior here without forcing access to private functions So, I'm changing our interceptors to, on leave/error, remove the added keys, but that's not totally foolproof. I really just want to tweak stylobate's logging to not include the context (or, perhaps, carefully include only selected keys from the context and request). I'm thinking the best solution might be to add our own own top-level error reporting interceptor and put a ring-response directly after; this will do the expected response writing work, and then the Pedestal-added ring-response will see that the response has already been committed (and the :body removed) and do nothing. TL;DR; if you are putting large maps into the context and they percolate back up out of your code, and an exception is thrown when writing the response stream to the client, then you will experience logging grief, and there isn't an easy way to override Pedestal's behavior here.

👍 12

I'm trying a solution, an interceptor that sits early in the stack, and green-lists all the context and request keys (on enter) and uses select-keys on leave/error (obviously, also allowing :response).