6

I'm helping to set up a Web site with Clojure's Noir framework, though I have a lot more experience with Django/Python. In Django, I'm used to URLs such as

http://site/some/url 

being 302-redirected automagically to

http://site/some/url/

Noir is more picky and does not do this.

What would be the proper way to do this automatically? Since good URLs are an important way of addressing into a site, and many users will forget the trailing slash, this is basic functionality I'd like to add to my site.

EDIT: Here is what finally worked for me, based on @IvanKoblik's suggestions:

(defn wrap-slash [handler]
  (fn [{:keys [uri] :as req}]
    (if (and (.endsWith uri "/") (not= uri "/"))
      (handler (assoc req :uri (.substring uri
                                0 (dec (count uri)))))
      (handler req))))
JohnJ
  • 4,753
  • 2
  • 28
  • 40
  • Could you elaborate a little more? For example URL to this question on StackOverflow does not have trailing slash. – Ivan Koblik Sep 18 '12 at 07:46
  • Correct - though if you add one the URL still works correctly, which is not the case in my Noir example. I would like to handle both cases with the same view/route code. – JohnJ Sep 18 '12 at 13:12
  • I see, I have updated my answer with another possible solution. – Ivan Koblik Sep 18 '12 at 13:18

2 Answers2

2

I think this may be possible with a custom middleware. noir/server has public function add-middleware.

Here's a page from webnoir explaining how to do that.

Judging by the source code this custom middleware is executed first, so you'd be on your own in terms of sessions, cookies, url params, etc.


I wrote a very silly version of the middleware wrapper that checks if request URI ends with slash and if not redirects to URI with slash at the end:

(use [ring.util.response :only [redirect]])

(defn wrap-slash [handler]
  (fn [{:keys [uri] :as req}]
    (if (.endsWith uri "/")
      (handler req)
      (redirect
       (str uri "/")))))

I tested it on my ring/moustache web app and it worked reasonably well.


EDIT1 (Expanding my answer after your reply to my comment.)

You could use custom middleware to either add or strip URL of trailing slash. Just do something like this to strip away trailing slash:

(use [ring.util.response :only [redirect]])

(defn add-slash [handler]
  (fn [{:keys [uri] :as req}]
    (if (.endsWith uri "/")
      (handler (assoc req 
                      :uri (.substring uri 
                                       0 (dec (count uri)))))
      (handler req))))
Ivan Koblik
  • 4,285
  • 1
  • 30
  • 33
  • Your first solution worked for me after adding (server/add-middleware wrap-slash). Why is the code in EDIT1 needed? Thanks! – JohnJ Sep 18 '12 at 13:37
  • @JohnJ From your reply I realized that it was not essential for you redirect to `/some/url/`, I thought that you just wanted to make sure that `/some/url` and `/some/url/` led to same responses. Please be careful with the first solution as I think it breaks on URLs with parameters. – Ivan Koblik Sep 18 '12 at 14:14
  • Thanks -- can you be a little more explicit how you would use the second solution you proposed? Thanks again. – JohnJ Sep 18 '12 at 14:27
  • @JohnJ Happy to help! I have updated my answer with a complete second example. – Ivan Koblik Sep 18 '12 at 14:41
  • Hmmm -- I did try the new code and Noir raises a 404 error. The first version works, however. – JohnJ Sep 19 '12 at 02:44
  • I guess it's because application expects URLs to have trailing slash. – Ivan Koblik Sep 19 '12 at 11:51
  • 1
    Modified version posted in original question – JohnJ Sep 19 '12 at 15:28
1

I found this useful:

(defpage "" []
  (response/redirect "/myapp/"))

/myapp -> /myapp/

Timothy Pratley
  • 10,586
  • 3
  • 34
  • 63