mirror of
https://github.com/metosin/reitit.git
synced 2026-01-06 07:39:49 +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"}]
|
||||
["Reverse-routing" {:file "doc/ring/reverse_routing.md"}]
|
||||
["Default handler" {:file "doc/ring/default_handler.md"}]
|
||||
["Slash handler" {:file "doc/ring/slash_handler.md"}]
|
||||
["Static Resources" {:file "doc/ring/static.md"}]
|
||||
["Dynamic Extensions" {:file "doc/ring/dynamic_extensions.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)))]
|
||||
(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
|
||||
"A default ring handler that can handle the following cases,
|
||||
configured via options:
|
||||
|
|
|
|||
|
|
@ -239,6 +239,69 @@
|
|||
(is (= response (app {:request-method :get, :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
|
||||
(let [promise #(let [value (atom ::nil)]
|
||||
(fn
|
||||
|
|
|
|||
Loading…
Reference in a new issue