mirror of
https://github.com/metosin/reitit.git
synced 2026-02-04 11:23:14 +00:00
Generate options-endpoints for ring by default
This commit is contained in:
parent
86eac2ff92
commit
da1cbf7121
4 changed files with 159 additions and 34 deletions
109
doc/ring/ring.md
109
doc/ring/ring.md
|
|
@ -2,15 +2,25 @@
|
||||||
|
|
||||||
[Ring](https://github.com/ring-clojure/ring) is a Clojure web applications library inspired by Python's WSGI and Ruby's Rack. By abstracting the details of HTTP into a simple, unified API, Ring allows web applications to be constructed of modular components that can be shared among a variety of applications, web servers, and web frameworks.
|
[Ring](https://github.com/ring-clojure/ring) is a Clojure web applications library inspired by Python's WSGI and Ruby's Rack. By abstracting the details of HTTP into a simple, unified API, Ring allows web applications to be constructed of modular components that can be shared among a variety of applications, web servers, and web frameworks.
|
||||||
|
|
||||||
|
Read more about the [Ring Concepts](https://github.com/ring-clojure/ring/wiki/Concepts).
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
[metosin/reitit-ring "0.2.2"]
|
[metosin/reitit-ring "0.2.2"]
|
||||||
```
|
```
|
||||||
|
|
||||||
Ring-router adds support for [handlers](https://github.com/ring-clojure/ring/wiki/Concepts#handlers), [middleware](https://github.com/ring-clojure/ring/wiki/Concepts#middleware) and routing based on `:request-method`. Ring-router is created with `reitit.ring/router` function. It uses a custom route compiler, creating a optimized data structure for handling route matches, with compiled middleware chain & handlers for all request methods. It also ensures that all routes have a `:handler` defined. `reitit.ring/ring-handler` is used to create a Ring handler out of ring-router.
|
## `reitit.ring/ring-router`
|
||||||
|
|
||||||
### Example
|
`ring-router` is a higher order router, which adds support for `:request-method` based routing, [handlers](https://github.com/ring-clojure/ring/wiki/Concepts#handlers) and [middleware](https://github.com/ring-clojure/ring/wiki/Concepts#middleware).
|
||||||
|
|
||||||
|
It accepts the following options:
|
||||||
|
|
||||||
Simple Ring app:
|
| key | description |
|
||||||
|
| ---------------------------------------|-------------|
|
||||||
|
| `:reitit.middleware/transform` | Function of `[Middleware] => [Middleware]` to transform the expanded Middleware (default: identity).
|
||||||
|
| `:reitit.middleware/registry` | Map of `keyword => IntoMiddleware` to replace keyword references into Middleware
|
||||||
|
| `:reitit.ring/default-options-handler` | Default handler for `:options` method in endpoints (default: default-options-handler)
|
||||||
|
|
||||||
|
Example router:
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
(require '[reitit.ring :as ring])
|
(require '[reitit.ring :as ring])
|
||||||
|
|
@ -18,10 +28,37 @@ Simple Ring app:
|
||||||
(defn handler [_]
|
(defn handler [_]
|
||||||
{:status 200, :body "ok"})
|
{:status 200, :body "ok"})
|
||||||
|
|
||||||
(def app
|
(def router
|
||||||
(ring/ring-handler
|
(ring/router
|
||||||
(ring/router
|
["/ping" {:get handler}]))
|
||||||
["/ping" handler])))
|
```
|
||||||
|
|
||||||
|
Match contains the ring-optimized `:result`:
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(require '[reitit.core :as r])
|
||||||
|
|
||||||
|
(r/match-by-path router "/ping")
|
||||||
|
;#Match{:template "/ping"
|
||||||
|
; :data {:get {:handler #object[...]}}
|
||||||
|
; :result #Methods{:get #Endpoint{...}
|
||||||
|
; :options #Endpoint{...}}
|
||||||
|
; :path-params {}
|
||||||
|
; :path "/ping"}
|
||||||
|
```
|
||||||
|
|
||||||
|
## `reitit.ring/ring-handler`
|
||||||
|
|
||||||
|
Given a `ring-router`, optional default-handler & options, `ring-handler` function will return a valid ring handler supporting both synchronous and [asynchronous](https://www.booleanknot.com/blog/2016/07/15/asynchronous-ring.html) request handling. The following options are available:
|
||||||
|
|
||||||
|
| key | description |
|
||||||
|
| --------------|-------------|
|
||||||
|
| `:middleware` | Optional sequence of middleware that wrap the ring-handler"
|
||||||
|
|
||||||
|
Simple Ring app:
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(def app (ring/ring-handler router))
|
||||||
```
|
```
|
||||||
|
|
||||||
Applying the handler:
|
Applying the handler:
|
||||||
|
|
@ -36,31 +73,46 @@ Applying the handler:
|
||||||
; {:status 200, :body "ok"}
|
; {:status 200, :body "ok"}
|
||||||
```
|
```
|
||||||
|
|
||||||
The expanded routes shows the compilation results:
|
The router can be accessed via `get-router`:
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
(-> app (ring/get-router) (reitit/routes))
|
(-> app (ring/get-router) (r/compiled-routes))
|
||||||
; [["/ping"
|
;[["/ping"
|
||||||
; {:handler #object[...]}
|
; {:handler #object[...]}
|
||||||
; #Methods{:any #Endpoint{:data {:handler #object[...]},
|
; #Methods{:get #Endpoint{:data {:handler #object[...]}
|
||||||
; :handler #object[...],
|
; :handler #object[...]
|
||||||
; :middleware []}}]]
|
; :middleware []}
|
||||||
|
; :options #Endpoint{:data {:handler #object[...]}
|
||||||
|
; :handler #object[...]
|
||||||
|
; :middleware []}}]]
|
||||||
```
|
```
|
||||||
|
|
||||||
Note the compiled resuts as third element in the route vector.
|
|
||||||
|
|
||||||
# Request-method based routing
|
# Request-method based routing
|
||||||
|
|
||||||
Handler are also looked under request-method keys: `:get`, `:head`, `:patch`, `:delete`, `:options`, `:post` or `:put`. Top-level handler is used if request-method based handler is not found.
|
Handlers can be placed either to the top-level (all methods) or under a spesific method (`:get`, `:head`, `:patch`, `:delete`, `:options`, `:post`, `:put` or `:trace`). Top-level handler is used if request-method based handler is not found.
|
||||||
|
|
||||||
|
By default, the `:options` route is generated for all paths - to enable thing like [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing).
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
(def app
|
(def app
|
||||||
(ring/ring-handler
|
(ring/ring-handler
|
||||||
(ring/router
|
(ring/router
|
||||||
["/ping" {:name ::ping
|
[["/all" handler]
|
||||||
:get handler
|
["/ping" {:name ::ping
|
||||||
:post handler}])))
|
:get handler
|
||||||
|
:post handler}]])))
|
||||||
|
```
|
||||||
|
|
||||||
|
Top-level handler catches all methods:
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(app {:request-method :delete, :uri "/all"})
|
||||||
|
; {:status 200, :body "ok"}
|
||||||
|
```
|
||||||
|
|
||||||
|
Method-level handler catches only the method:
|
||||||
|
|
||||||
|
```clj
|
||||||
(app {:request-method :get, :uri "/ping"})
|
(app {:request-method :get, :uri "/ping"})
|
||||||
; {:status 200, :body "ok"}
|
; {:status 200, :body "ok"}
|
||||||
|
|
||||||
|
|
@ -68,19 +120,26 @@ Handler are also looked under request-method keys: `:get`, `:head`, `:patch`, `:
|
||||||
; nil
|
; nil
|
||||||
```
|
```
|
||||||
|
|
||||||
|
By default, `:options` is also supported (ree router options to change this):
|
||||||
|
|
||||||
|
```clj
|
||||||
|
(app {:request-method :options, :uri "/ping"})
|
||||||
|
; {:status 200, :body ""}
|
||||||
|
```
|
||||||
|
|
||||||
Name-based reverse routing:
|
Name-based reverse routing:
|
||||||
|
|
||||||
```clj
|
```clj
|
||||||
(-> app
|
(-> app
|
||||||
(ring/get-router)
|
(ring/get-router)
|
||||||
(reitit/match-by-name ::ping)
|
(r/match-by-name ::ping)
|
||||||
:path)
|
(r/match->path))
|
||||||
; "/ping"
|
; "/ping"
|
||||||
```
|
```
|
||||||
|
|
||||||
# Middleware
|
# Middleware
|
||||||
|
|
||||||
Middleware can be added with a `:middleware` key, either to top-level or under `:request-method` submap. It's value should be a vector of any the following:
|
Middleware can be mounted using a `:middleware` key - either to top-level or under `:request-method` submap. It's value should be a vector of `reitit.middleware/IntoMiddleware` values. These include:
|
||||||
|
|
||||||
1. normal ring middleware function `handler -> request -> response`
|
1. normal ring middleware function `handler -> request -> response`
|
||||||
2. vector of middleware function `[handler args*] -> request -> response` and it's arguments
|
2. vector of middleware function `[handler args*] -> request -> response` and it's arguments
|
||||||
|
|
@ -141,7 +200,3 @@ Top-level middleware, applied before any routing is done:
|
||||||
(app {:request-method :get, :uri "/api/get"})
|
(app {:request-method :get, :uri "/api/get"})
|
||||||
; {:status 200, :body [:top :api :ok]}
|
; {:status 200, :body [:top :api :ok]}
|
||||||
```
|
```
|
||||||
|
|
||||||
# Async Ring
|
|
||||||
|
|
||||||
All built-in middleware provide both 2 and 3-arity and are compiled for both Clojure & ClojureScript, so they work with [Async Ring](https://www.booleanknot.com/blog/2016/07/15/asynchronous-ring.html) and [Node.js](https://nodejs.org) too.
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@
|
||||||
(update acc method expand opts)
|
(update acc method expand opts)
|
||||||
acc)) data http-methods)])
|
acc)) data http-methods)])
|
||||||
|
|
||||||
(defn compile-result [[path data] opts]
|
(defn compile-result [[path data] {:keys [::default-options-handler] :as opts}]
|
||||||
(let [[top childs] (group-keys data)
|
(let [[top childs] (group-keys data)
|
||||||
->endpoint (fn [p d m s]
|
->endpoint (fn [p d m s]
|
||||||
(-> (middleware/compile-result [p d] opts s)
|
(-> (middleware/compile-result [p d] opts s)
|
||||||
|
|
@ -37,7 +37,12 @@
|
||||||
(fn [acc method]
|
(fn [acc method]
|
||||||
(cond-> acc
|
(cond-> acc
|
||||||
any? (assoc method (->endpoint path data method nil))))
|
any? (assoc method (->endpoint path data method nil))))
|
||||||
(map->Methods {})
|
(map->Methods
|
||||||
|
{:options
|
||||||
|
(if default-options-handler
|
||||||
|
(->endpoint path (assoc data
|
||||||
|
:handler default-options-handler
|
||||||
|
:no-doc true) :options nil))})
|
||||||
http-methods))]
|
http-methods))]
|
||||||
(if-not (seq childs)
|
(if-not (seq childs)
|
||||||
(->methods true top)
|
(->methods true top)
|
||||||
|
|
@ -48,6 +53,9 @@
|
||||||
(->methods (:handler top) data)
|
(->methods (:handler top) data)
|
||||||
childs))))
|
childs))))
|
||||||
|
|
||||||
|
(defn default-options-handler [_]
|
||||||
|
{:status 200, :body ""})
|
||||||
|
|
||||||
;;
|
;;
|
||||||
;; public api
|
;; public api
|
||||||
;;
|
;;
|
||||||
|
|
@ -57,6 +65,14 @@
|
||||||
support for http-methods and Middleware. See [docs](https://metosin.github.io/reitit/)
|
support for http-methods and Middleware. See [docs](https://metosin.github.io/reitit/)
|
||||||
for details.
|
for details.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
| key | description |
|
||||||
|
| ---------------------------------------|-------------|
|
||||||
|
| `:reitit.middleware/transform` | Function of `[Middleware] => [Middleware]` to transform the expanded Middleware (default: identity).
|
||||||
|
| `:reitit.middleware/registry` | Map of `keyword => IntoMiddleware` to replace keyword references into Middleware
|
||||||
|
| `:reitit.ring/default-options-handler` | Default handler for `:options` method in endpoints (default: default-options-handler)
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
(router
|
(router
|
||||||
|
|
@ -64,13 +80,13 @@
|
||||||
[\"/users\" {:get get-user
|
[\"/users\" {:get get-user
|
||||||
:post update-user
|
:post update-user
|
||||||
:delete {:middleware [wrap-delete]
|
:delete {:middleware [wrap-delete]
|
||||||
:handler delete-user}}]])
|
:handler delete-user}}]])"
|
||||||
|
|
||||||
See router options from [[reitit.core/router]] and [[reitit.middleware/router]]."
|
|
||||||
([data]
|
([data]
|
||||||
(router data nil))
|
(router data nil))
|
||||||
([data opts]
|
([data opts]
|
||||||
(let [opts (meta-merge {:coerce coerce-handler, :compile compile-result} opts)]
|
(let [opts (merge {:coerce coerce-handler
|
||||||
|
:compile compile-result
|
||||||
|
::default-options-handler default-options-handler} opts)]
|
||||||
(r/router data opts))))
|
(r/router data opts))))
|
||||||
|
|
||||||
(defn routes
|
(defn routes
|
||||||
|
|
@ -182,7 +198,7 @@
|
||||||
|
|
||||||
| key | description |
|
| key | description |
|
||||||
| --------------|-------------|
|
| --------------|-------------|
|
||||||
| `:middleware` | Optional sequence of middleware that are wrap the [[ring-handler]]"
|
| `:middleware` | Optional sequence of middleware that wrap the ring-handler"
|
||||||
([router]
|
([router]
|
||||||
(ring-handler router nil))
|
(ring-handler router nil))
|
||||||
([router default-handler]
|
([router default-handler]
|
||||||
|
|
|
||||||
|
|
@ -197,6 +197,47 @@
|
||||||
(testing "handler rejects"
|
(testing "handler rejects"
|
||||||
(is (= -406 (:status (app {:request-method :get, :uri "/pong"}))))))))))
|
(is (= -406 (:status (app {:request-method :get, :uri "/pong"}))))))))))
|
||||||
|
|
||||||
|
(deftest default-options-handler-test
|
||||||
|
(let [response {:status 200, :body "ok"}
|
||||||
|
options-response (ring/default-options-handler :request)]
|
||||||
|
|
||||||
|
(testing "with defaults"
|
||||||
|
(let [app (ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
[["/get" {:get (constantly response)}]
|
||||||
|
["/options" {:options (constantly response)}]
|
||||||
|
["/any" (constantly response)]]))]
|
||||||
|
|
||||||
|
(testing "endpoint with a non-options handler"
|
||||||
|
(is (= response (app {:request-method :get, :uri "/get"})))
|
||||||
|
(is (= options-response (app {:request-method :options, :uri "/get"}))))
|
||||||
|
|
||||||
|
(testing "endpoint with a options handler"
|
||||||
|
(is (= response (app {:request-method :options, :uri "/options"}))))
|
||||||
|
|
||||||
|
(testing "endpoint with top-level handler"
|
||||||
|
(is (= response (app {:request-method :get, :uri "/any"})))
|
||||||
|
(is (= response (app {:request-method :options, :uri "/any"}))))))
|
||||||
|
|
||||||
|
(testing "disabled via options"
|
||||||
|
(let [app (ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
[["/get" {:get (constantly response)}]
|
||||||
|
["/options" {:options (constantly response)}]
|
||||||
|
["/any" (constantly response)]]
|
||||||
|
{::ring/default-options-handler nil}))]
|
||||||
|
|
||||||
|
(testing "endpoint with a non-options handler"
|
||||||
|
(is (= response (app {:request-method :get, :uri "/get"})))
|
||||||
|
(is (= nil (app {:request-method :options, :uri "/get"}))))
|
||||||
|
|
||||||
|
(testing "endpoint with a options handler"
|
||||||
|
(is (= response (app {:request-method :options, :uri "/options"}))))
|
||||||
|
|
||||||
|
(testing "endpoint with top-level handler"
|
||||||
|
(is (= response (app {:request-method :get, :uri "/any"})))
|
||||||
|
(is (= response (app {:request-method :options, :uri "/any"}))))))))
|
||||||
|
|
||||||
(deftest async-ring-test
|
(deftest async-ring-test
|
||||||
(let [promise #(let [value (atom ::nil)]
|
(let [promise #(let [value (atom ::nil)]
|
||||||
(fn
|
(fn
|
||||||
|
|
|
||||||
|
|
@ -183,6 +183,19 @@
|
||||||
(-> {:request-method :get :uri "/swagger.json"}
|
(-> {:request-method :get :uri "/swagger.json"}
|
||||||
(app) :body :x-id)))))
|
(app) :body :x-id)))))
|
||||||
|
|
||||||
|
(deftest with-options-endpoint-test
|
||||||
|
(let [app (ring/ring-handler
|
||||||
|
(ring/router
|
||||||
|
[["/ping"
|
||||||
|
{:options (constantly "options")}]
|
||||||
|
["/swagger.json"
|
||||||
|
{:get {:no-doc true
|
||||||
|
:handler (swagger/create-swagger-handler)}}]]))]
|
||||||
|
(is (= ["/ping"] (spec-paths app "/swagger.json")))
|
||||||
|
(is (= #{::swagger/default}
|
||||||
|
(-> {:request-method :get :uri "/swagger.json"}
|
||||||
|
(app) :body :x-id)))))
|
||||||
|
|
||||||
(deftest all-parameter-types-test
|
(deftest all-parameter-types-test
|
||||||
(let [app (ring/ring-handler
|
(let [app (ring/ring-handler
|
||||||
(ring/router
|
(ring/router
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue