Johnny Brown
Johnny Brown

Reputation: 1000

Ring solution to redirect missing trailing slash urls

The behavior I'm trying to implement using Ring routes is approximately described in this question.

Basically I have some URLs that end with trailing slashes, and I'm trying to create a middleware that will redirect from e.g example.com/foo to example.com/foo/ if and only if /foo/ is a valid URL and /foo is not.

I'm currently using this middleware

(defn good-response? [resp]
  (and resp (not= (:status resp) 404)))

(defn wrap-slash [handler]
  (fn [{:keys [uri] :as req}]
    (let [resp (handler req)]
      (if (or (good-response? resp) (.endsWith "/" uri))
        resp
        (let [added-slash (str uri "/")]
          (if (good-response? (handler (assoc req :uri added-slash)))
            (redirect added-slash)
            resp))))))

Which does almost everything it should: It redirects from /foo to /foo/ iff /foo/ exists and /foo does not.

My concern is that this solution will call (handler req) at least twice - once on the request for /foo and again when the client requests the redirected URL. It's not a problem now, but I could imagine it being painful to double the response time for some slow page with hundreds of DB queries or some such thing.

Is there a way to simply check if a handler exists for a given URL, without calling that handler? Could we avoid the problem entirely by say, making (:body request) lazy?

Upvotes: 1

Views: 952

Answers (2)

Kazuhiro Hara
Kazuhiro Hara

Reputation: 21

I fixed your code.

(defn good-response? [resp]
  (and resp (not= (:status resp) 404)))

(defn wrap-slash [handler]
  (fn [{:keys [uri] :as req}]
    (let [resp (handler req)]
      (if (or (good-response? resp) (.endsWith uri "/"))
        resp
        (let [added-slash (str uri "/")]          
          (if (good-response? (handler (-> req
                                           (assoc :uri added-slash)
                                           (assoc :path-info added-slash))))
            (redirect added-slash)
            resp))))))

you need to change :path-info.

Upvotes: 1

Joost Diepenmaat
Joost Diepenmaat

Reputation: 17773

There is no general way in ring to check for "is this a valid uri?" without calling the whole stack handler with that uri, since there is no central list of uris, but even then handlers can decline to handle a request for any reason at all, not just based on its uri.

I would probably go the other way around; for all handlers that actually need this behavior, make them catch the "unslashed" version too and redirect then if needed/useful.

Or use a separate handler/middleware if the url should ALWAYS end in a slash given some rules and let the redirect fail if it doesn't match. Either way the end user will get a 404, so who cares, really?

But the most specific handlers are usually in the best position to make the decision.

Oh, and you probably don't just forward a POST to some other URI.

Upvotes: 2

Related Questions