Re: Is it possible to access route params in middleware?

302 views
Skip to first unread message

James Reeves

unread,
Feb 15, 2013, 8:49:46 AM2/15/13
to Compojure
Yes, it is. Route parameters are placed on the :route-params and :params keys. However, you'll need to apply your middleware after the route parameter has been matched.

For example:

(def example-routes
  (context "/foo/:id" [id]
    (wrap-some-middleware
      (routes
        (GET "/" [] ...)
        ...))))

The "wrap-some-middleware" function will be able to access the :id parameter, because it has been applied after the context has been matched.

- James


On 15 February 2013 13:46, Felix <fe...@ordrin.com> wrote:
Hello - been working on my first small project in Clojure and digging it. One thing I've not been able to determine is if it's possible to access the route parameters in middleware? Any help/pointers appreciated!

felix

--
You received this message because you are subscribed to the Google Groups "Compojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to compojure+...@googlegroups.com.
To post to this group, send email to comp...@googlegroups.com.
Visit this group at http://groups.google.com/group/compojure?hl=en.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Felix

unread,
Feb 16, 2013, 8:50:39 PM2/16/13
to comp...@googlegroups.com, ja...@booleanknot.com
James,

Thank you so much - that is exactly what I needed. Seems obvious now that I see it. :)

felix

Anand Prakash

unread,
Jul 18, 2013, 8:33:36 PM7/18/13
to comp...@googlegroups.com, ja...@booleanknot.com
Hi James
I am also facing a similar issue. For my the route params are at the level of GET/POST etc. In that case, I will need to attach the middleware at each route, which defeats the purpose of putting middleware at the single place. With the current architecture it seems difficult to be able to do, but still hoping to see if there is some way.

Thanks

James Reeves

unread,
Jul 19, 2013, 3:51:37 PM7/19/13
to Anand Prakash, Compojure
Could you explain why you're trying to convert certain parameters into "ObjectId" instances? It might be that a better solution will present itself if you provide more context to your question.

- James

Anand Prakash

unread,
Jul 19, 2013, 4:02:57 PM7/19/13
to comp...@googlegroups.com, Anand Prakash, ja...@booleanknot.com
Hi James
Thanks for proposing to help.

Situation is as follows:
We have a system in which ids are ObjectIds. When clients sends a request to server they send string representations of the objectids. So we want to first convert all string to ObjectIds instances. Now we could do that by custom handling on params by param basis, or we could just have a middleware which converts anything which looks like an objectId string to ObjectId instance.

E.g.
POST /threads/:id  with json body {messagid, userid, text}
In this case I would need to convert messageid, userid and threadid to ObjectId instances. At the middleware stage json params, query params and form params are available to me and I can convert all of them to ObjectId instances. This takes care of the messageid and userid. However the threadid  (route params) gets added to params inside the handling of GET/POST/PUT etc, so I cannot use the middleware for those and need to handle them on a case by case basis. I am not that good with macros to be able to write mine own GET replacement which internally calls the compojure GET and also applies string->objectid on params.

Hope I was able to explain my situation.

Looking forward to your help on this.

Thanks
Anand

James Reeves

unread,
Jul 19, 2013, 4:26:04 PM7/19/13
to Compojure, Anand Prakash
Macros are powerful tools, but have a lot of disadvantages. In most cases, it's usually a mistake to use them unless you know what you're doing.

It sounds like you have a degree of abstraction you can exploit. Consider a function like:

  (post-object "/threads/:threadid" add-thread [:threadid :messageid :userid :text])

Where the source code looks like:

  (defn post-object [route controller param-names]
    (POST route [& params]
      (controller (convert-object-ids (select-keys params param-names)))))

Note that I'm being explicit about which parameter names I want to use. Arbitrarily walking a tree and guessing based on parameter names leaves more things that could go wrong.

- James



Mike Roberts

unread,
Jul 19, 2013, 4:40:33 PM7/19/13
to comp...@googlegroups.com
Hi James,

So I have a related question. If I have some middleware that I only want to ever apply to certain routes, but not (for example) to catch-all or not-found, do I have to use contexts to do this?

E.g. say I want to use lib-noir's wrap-force-ssl, but only for specific routes, I see it works by doing this:

(defroutes myroutes
  (context "/foo" []
     (wrap-force-ssl
      (routes
        (GET "/bar" [] "Will always use https")
        (GET "/baz" [] "Will always use https")
        ))) 
  (route/resources "/")
  (route/not-found "Not Found"))

but this forces my secure route to be sub-pathed (to /foo/bar). Is there something I'm missing so that I could use the middleware just for /bar and /baz, but at the root level, but still not have that middleware applied to resources and not-found?

I do realize that wrap-force-ssl is somewhat odd for a piece of middleware in that it does break execution flow, rather than just adding to the request / response.

Thanks

Mike



On Friday, February 15, 2013 8:49:46 AM UTC-5, James Reeves wrote:

James Reeves

unread,
Jul 19, 2013, 4:46:02 PM7/19/13
to Compojure
Sure, you can do this. Just remove the "context" from your example:

  (defroutes myroutes

    (wrap-force-ssl
     (routes
       (GET "/bar" [] "Will always use https")
       (GET "/baz" [] "Will always use https")))  

    (route/resources "/")
    (route/not-found "Not Found"))

- James

--
You received this message because you are subscribed to the Google Groups "Compojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to compojure+...@googlegroups.com.
To post to this group, send email to comp...@googlegroups.com.

Anand Prakash

unread,
Jul 19, 2013, 4:47:01 PM7/19/13
to comp...@googlegroups.com, Anand Prakash, ja...@booleanknot.com
Hi James
I am not doing the string->objectid conversion based on the name. ObjectIds have a pattern (def object-id-regex #"^[0-9a-fA-F]{24}$")
I am just considering walking the tree of params which satisfy this regex and converting them to ObjectId instances. The problem here is that the route params are not available at the middleware stage - which is my main question.

Thanks
Anand

Anand Prakash

unread,
Jul 19, 2013, 4:48:59 PM7/19/13
to comp...@googlegroups.com, ja...@booleanknot.com
+1
A sample from my code is

(defroutes api-routes

  (context "/admin/v1/apis" [] (auth/wrap-admin admin-routes))

  (context "/admin/v1/pages" [] (auth/wrap-admin admin-pages-routes))

  (context "/v1/apis" [] (auth/wrap-user v1-api-routes))

  (context "/v1/pages" [] v1-pages-routes)

  (context "/v1/pubapis" [] v1-pubroutes))


As you would notice here the routes are wrapped in the auth middlewares, not the contexts.

James Reeves

unread,
Jul 19, 2013, 4:50:56 PM7/19/13
to Compojure, Anand Prakash
Even so, I'd tend to err on the side of caution and encode parameters explicitly, rather than implicitly according to some pattern.

However, regardless of whether the encoding is implicit or explicit, you should still be able to make use of a function to create your routes based on this scheme.

- James

Mike Roberts

unread,
Jul 19, 2013, 4:56:33 PM7/19/13
to comp...@googlegroups.com, ja...@booleanknot.com
That doesn't work though, because in the case of a resource at (e.g.) /index.html, wrap-force-ssl is still evaulated. The behavior of wrap-force-ssl is such that if the request (and in this case any request) is not via https, then it is redirected to https, which I don't want.

Similarly I'd like middleware that checks for a certain user-id cookie and redirects if it doesn't exist on the request (and if it does it loads user information from a datastore and puts that on the request), but again I only want this to occur on certain routes.

Again, I understand that this is possibly an abuse of middleware (that it re-directs before passing control to the underlying handler) but since it existed in lib-noir I assumed it was still idiomatic - am I wrong in that assumption?

Thanks again

Anand Prakash

unread,
Jul 19, 2013, 5:00:09 PM7/19/13
to comp...@googlegroups.com, Anand Prakash, ja...@booleanknot.com
Got it. So my code will look like this

 (defn post-object [route controller]
    (POST route [& params]
      (controller (convert-object-ids params))))

(defn controller [params]
  (response {:foo "bar"}))

 (defroutes foo
       (post-object "/bar" controller))

Manually converting objects as I am doing right now, sounds better.

James Reeves

unread,
Jul 19, 2013, 5:04:49 PM7/19/13
to Compojure
On 19 July 2013 21:56, Mike Roberts <mike.b....@gmail.com> wrote:
That doesn't work though, because in the case of a resource at (e.g.) /index.html, wrap-force-ssl is still evaulated. The behavior of wrap-force-ssl is such that if the request (and in this case any request) is not via https, then it is redirected to https, which I don't want.

Ah I see. I'm afraid that with the current Compojure architecture, you'll need to either apply wrap-force-ssl individually to each top-level route you want SSL to be forced on, or supply wrap-force-ssl with a set of prefixes or regular expressions.

However, in any solution, you need to have some mechanism to tell wrap-force-ssl (or any other middleware) which routes you want to be secure and which you don't.

- James

James Reeves

unread,
Jul 19, 2013, 5:06:48 PM7/19/13
to Compojure, Anand Prakash
On 19 July 2013 22:00, Anand Prakash <anand....@gmail.com> wrote:
Got it. So my code will look like this

 (defn post-object [route controller]
    (POST route [& params]
      (controller (convert-object-ids params))))

(defn controller [params]
  (response {:foo "bar"}))

 (defroutes foo
       (post-object "/bar" controller))

Manually converting objects as I am doing right now, sounds better.

Yes, something like that. You can use functions to create routes, so the more consistent and uniform your API, the easier it is to abstract out common behaviour. Consistent APIs are also good for end users, as their functionality is more predictable.

- James

Anand Prakash

unread,
Jul 19, 2013, 5:09:45 PM7/19/13
to James Reeves, Compojure
Thanks James for the help.

Mike Roberts

unread,
Jul 19, 2013, 5:43:54 PM7/19/13
to comp...@googlegroups.com, ja...@booleanknot.com


On Friday, July 19, 2013 5:04:49 PM UTC-4, James Reeves wrote:
Ah I see. I'm afraid that with the current Compojure architecture, you'll need to either apply wrap-force-ssl individually to each top-level route you want SSL to be forced on, or supply wrap-force-ssl with a set of prefixes or regular expressions.

However, in any solution, you need to have some mechanism to tell wrap-force-ssl (or any other middleware) which routes you want to be secure and which you don't.

OK, good to know I'm not missing something - thanks!

I'll investigate writing a macro that does this cleanly (i.e. define the middleware for a set of routes once rather than per-route). If I come up with something I like I'll post back or send you a pull-request.

Mike
Reply all
Reply to author
Forward
0 new messages