mirror of
https://github.com/metosin/reitit.git
synced 2026-02-17 16:25:57 +00:00
Merge pull request #159 from valerauko/trailing-slash
Handle trailing slashes
This commit is contained in:
commit
3e6f1c0548
4 changed files with 159 additions and 0 deletions
|
|
@ -25,6 +25,7 @@
|
||||||
["Ring-router" {:file "doc/ring/ring.md"}]
|
["Ring-router" {:file "doc/ring/ring.md"}]
|
||||||
["Reverse-routing" {:file "doc/ring/reverse_routing.md"}]
|
["Reverse-routing" {:file "doc/ring/reverse_routing.md"}]
|
||||||
["Default handler" {:file "doc/ring/default_handler.md"}]
|
["Default handler" {:file "doc/ring/default_handler.md"}]
|
||||||
|
["Slash handler" {:file "doc/ring/slash_handler.md"}]
|
||||||
["Static Resources" {:file "doc/ring/static.md"}]
|
["Static Resources" {:file "doc/ring/static.md"}]
|
||||||
["Dynamic Extensions" {:file "doc/ring/dynamic_extensions.md"}]
|
["Dynamic Extensions" {:file "doc/ring/dynamic_extensions.md"}]
|
||||||
["Data-driven Middleware" {:file "doc/ring/data_driven_middleware.md"}]
|
["Data-driven Middleware" {:file "doc/ring/data_driven_middleware.md"}]
|
||||||
|
|
|
||||||
64
doc/ring/slash_handler.md
Normal file
64
doc/ring/slash_handler.md
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
# Slash handler
|
||||||
|
|
||||||
|
The router works with precise matches. If a route is defined without a trailing slash, for example, it won't match a request with a slash.
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(require '[reitit.ring :as ring])
|
||||||
|
|
||||||
|
(def app
|
||||||
|
(ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
["/ping" (constantly {:status 200, :body ""})])))
|
||||||
|
|
||||||
|
(app {:uri "/ping/"})
|
||||||
|
; nil
|
||||||
|
```
|
||||||
|
|
||||||
|
Sometimes it is desirable that paths with and without a trailing slash are recognized as the same.
|
||||||
|
|
||||||
|
Setting the `redirect-trailing-slash-handler` as a second argument to `ring-handler`:
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(def app
|
||||||
|
(ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
[["/ping" (constantly {:status 200, :body ""})]
|
||||||
|
["/pong/" (constantly {:status 200, :body ""})]])
|
||||||
|
(ring/redirect-trailing-slash-handler)))
|
||||||
|
|
||||||
|
(app {:uri "/ping/"})
|
||||||
|
; {:status 308, :headers {"Location" "/ping"}, :body ""}
|
||||||
|
(app {:uri "/pong"})
|
||||||
|
; {:status 308, :headers {"Location" "/pong/"}, :body ""}
|
||||||
|
```
|
||||||
|
|
||||||
|
`redirect-trailing-slash-handler` accepts an optional `:method` parameter that allows configuring how (whether) to handle missing/extra slashes. The default is to handle both.
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(def app
|
||||||
|
(ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
[["/ping" (constantly {:status 200, :body ""})]
|
||||||
|
["/pong/" (constantly {:status 200, :body ""})]])
|
||||||
|
; only handle extra trailing slash
|
||||||
|
(ring/redirect-trailing-slash-handler {:method :strip})))
|
||||||
|
|
||||||
|
(app {:uri "/ping/"})
|
||||||
|
; {:status 308, :headers {"Location" "/ping"}, :body ""}
|
||||||
|
(app {:uri "/pong"})
|
||||||
|
; nil
|
||||||
|
```
|
||||||
|
```clj
|
||||||
|
(def app
|
||||||
|
(ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
[["/ping" (constantly {:status 200, :body ""})]
|
||||||
|
["/pong/" (constantly {:status 200, :body ""})]])
|
||||||
|
; only handle missing trailing slash
|
||||||
|
(ring/redirect-trailing-slash-handler {:method :add})))
|
||||||
|
|
||||||
|
(app {:uri "/ping/"})
|
||||||
|
; nil
|
||||||
|
(app {:uri "/pong"})
|
||||||
|
; {:status 308, :headers {"Location" "/pong/"}, :body ""}
|
||||||
|
```
|
||||||
|
|
@ -110,6 +110,37 @@
|
||||||
(respond nil)))]
|
(respond nil)))]
|
||||||
(f handlers))))))
|
(f handlers))))))
|
||||||
|
|
||||||
|
(defn redirect-trailing-slash-handler
|
||||||
|
"A ring handler that redirects a missing path if there is an
|
||||||
|
existing path that only differs in the ending slash.
|
||||||
|
|
||||||
|
| key | description |
|
||||||
|
|---------|-------------|
|
||||||
|
| :method | :add - redirects slash-less to slashed |
|
||||||
|
| | :strip - redirects slashed to slash-less |
|
||||||
|
| | :both - works both ways (default) |
|
||||||
|
"
|
||||||
|
([] (redirect-trailing-slash-handler {:method :both}))
|
||||||
|
([{:keys [method]}]
|
||||||
|
(let [redirect-handler (fn redirect-handler [request]
|
||||||
|
(let [uri (:uri request)
|
||||||
|
status (if (= (:request-method request) :get) 301 308)
|
||||||
|
maybe-redirect (fn maybe-redirect [path]
|
||||||
|
(if (r/match-by-path (::r/router request) path)
|
||||||
|
{:status status
|
||||||
|
:headers {"Location" path}
|
||||||
|
:body ""}))]
|
||||||
|
(if (str/ends-with? uri "/")
|
||||||
|
(if (not= method :add)
|
||||||
|
(maybe-redirect (subs uri 0 (-> uri count dec))))
|
||||||
|
(if (not= method :strip)
|
||||||
|
(maybe-redirect (str uri "/"))))))]
|
||||||
|
(fn
|
||||||
|
([request]
|
||||||
|
(redirect-handler request))
|
||||||
|
([request respond _]
|
||||||
|
(respond (redirect-handler request)))))))
|
||||||
|
|
||||||
(defn create-default-handler
|
(defn create-default-handler
|
||||||
"A default ring handler that can handle the following cases,
|
"A default ring handler that can handle the following cases,
|
||||||
configured via options:
|
configured via options:
|
||||||
|
|
|
||||||
|
|
@ -239,6 +239,69 @@
|
||||||
(is (= response (app {:request-method :get, :uri "/any"})))
|
(is (= response (app {:request-method :get, :uri "/any"})))
|
||||||
(is (= response (app {:request-method :options, :uri "/any"}))))))))
|
(is (= response (app {:request-method :options, :uri "/any"}))))))))
|
||||||
|
|
||||||
|
(deftest trailing-slash-handler-test
|
||||||
|
(let [ok {:status 200, :body "ok"}
|
||||||
|
routes [["/slash-less" {:get (constantly ok),
|
||||||
|
:post (constantly ok)}]
|
||||||
|
["/with-slash/" {:get (constantly ok),
|
||||||
|
:post (constantly ok)}]]]
|
||||||
|
(testing "using :method :add"
|
||||||
|
(let [app (ring/ring-handler
|
||||||
|
(ring/router routes)
|
||||||
|
(ring/redirect-trailing-slash-handler {:method :add}))]
|
||||||
|
|
||||||
|
(testing "exact matches work"
|
||||||
|
(is (= ok (app {:request-method :get, :uri "/slash-less"})))
|
||||||
|
(is (= ok (app {:request-method :post, :uri "/slash-less"})))
|
||||||
|
(is (= ok (app {:request-method :get, :uri "/with-slash/"})))
|
||||||
|
(is (= ok (app {:request-method :post, :uri "/with-slash/"}))))
|
||||||
|
|
||||||
|
(testing "adds slashes"
|
||||||
|
(is (= 301 (:status (app {:request-method :get, :uri "/with-slash"}))))
|
||||||
|
(is (= 308 (:status (app {:request-method :post, :uri "/with-slash"})))))
|
||||||
|
|
||||||
|
(testing "does not strip slashes"
|
||||||
|
(is (= nil (app {:request-method :get, :uri "/slash-less/"})))
|
||||||
|
(is (= nil (app {:request-method :post, :uri "/slash-less/"}))))))
|
||||||
|
|
||||||
|
(testing "using :method :strip"
|
||||||
|
(let [app (ring/ring-handler
|
||||||
|
(ring/router routes)
|
||||||
|
(ring/redirect-trailing-slash-handler {:method :strip}))]
|
||||||
|
|
||||||
|
(testing "exact matches work"
|
||||||
|
(is (= ok (app {:request-method :get, :uri "/slash-less"})))
|
||||||
|
(is (= ok (app {:request-method :post, :uri "/slash-less"})))
|
||||||
|
(is (= ok (app {:request-method :get, :uri "/with-slash/"})))
|
||||||
|
(is (= ok (app {:request-method :post, :uri "/with-slash/"}))))
|
||||||
|
|
||||||
|
(testing "does not add slashes"
|
||||||
|
(is (= nil (app {:request-method :get, :uri "/with-slash"})))
|
||||||
|
(is (= nil (app {:request-method :post, :uri "/with-slash"}))))
|
||||||
|
|
||||||
|
(testing "strips slashes"
|
||||||
|
(is (= 301 (:status (app {:request-method :get, :uri "/slash-less/"}))))
|
||||||
|
(is (= 308 (:status (app {:request-method :post, :uri "/slash-less/"})))))))
|
||||||
|
|
||||||
|
(testing "without option (equivalent to using :method :both)"
|
||||||
|
(let [app (ring/ring-handler
|
||||||
|
(ring/router routes)
|
||||||
|
(ring/redirect-trailing-slash-handler))]
|
||||||
|
|
||||||
|
(testing "exact matches work"
|
||||||
|
(is (= ok (app {:request-method :get, :uri "/slash-less"})))
|
||||||
|
(is (= ok (app {:request-method :post, :uri "/slash-less"})))
|
||||||
|
(is (= ok (app {:request-method :get, :uri "/with-slash/"})))
|
||||||
|
(is (= ok (app {:request-method :post, :uri "/with-slash/"}))))
|
||||||
|
|
||||||
|
(testing "adds slashes"
|
||||||
|
(is (= 301 (:status (app {:request-method :get, :uri "/with-slash"}))))
|
||||||
|
(is (= 308 (:status (app {:request-method :post, :uri "/with-slash"})))))
|
||||||
|
|
||||||
|
(testing "strips slashes"
|
||||||
|
(is (= 301 (:status (app {:request-method :get, :uri "/slash-less/"}))))
|
||||||
|
(is (= 308 (:status (app {:request-method :post, :uri "/slash-less/"})))))))))
|
||||||
|
|
||||||
(deftest async-ring-test
|
(deftest async-ring-test
|
||||||
(let [promise #(let [value (atom ::nil)]
|
(let [promise #(let [value (atom ::nil)]
|
||||||
(fn
|
(fn
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue