mirror of
https://github.com/metosin/reitit.git
synced 2026-01-08 16:29:49 +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.
|
||||
|
||||
Read more about the [Ring Concepts](https://github.com/ring-clojure/ring/wiki/Concepts).
|
||||
|
||||
```clj
|
||||
[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
|
||||
(require '[reitit.ring :as ring])
|
||||
|
|
@ -18,10 +28,37 @@ Simple Ring app:
|
|||
(defn handler [_]
|
||||
{:status 200, :body "ok"})
|
||||
|
||||
(def app
|
||||
(ring/ring-handler
|
||||
(ring/router
|
||||
["/ping" handler])))
|
||||
(def router
|
||||
(ring/router
|
||||
["/ping" {:get 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:
|
||||
|
|
@ -36,31 +73,46 @@ Applying the handler:
|
|||
; {:status 200, :body "ok"}
|
||||
```
|
||||
|
||||
The expanded routes shows the compilation results:
|
||||
The router can be accessed via `get-router`:
|
||||
|
||||
```clj
|
||||
(-> app (ring/get-router) (reitit/routes))
|
||||
; [["/ping"
|
||||
; {:handler #object[...]}
|
||||
; #Methods{:any #Endpoint{:data {:handler #object[...]},
|
||||
; :handler #object[...],
|
||||
; :middleware []}}]]
|
||||
(-> app (ring/get-router) (r/compiled-routes))
|
||||
;[["/ping"
|
||||
; {:handler #object[...]}
|
||||
; #Methods{:get #Endpoint{:data {:handler #object[...]}
|
||||
; :handler #object[...]
|
||||
; :middleware []}
|
||||
; :options #Endpoint{:data {:handler #object[...]}
|
||||
; :handler #object[...]
|
||||
; :middleware []}}]]
|
||||
```
|
||||
|
||||
Note the compiled resuts as third element in the route vector.
|
||||
|
||||
# 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
|
||||
(def app
|
||||
(ring/ring-handler
|
||||
(ring/router
|
||||
["/ping" {:name ::ping
|
||||
:get handler
|
||||
:post handler}])))
|
||||
[["/all" handler]
|
||||
["/ping" {:name ::ping
|
||||
: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"})
|
||||
; {:status 200, :body "ok"}
|
||||
|
||||
|
|
@ -68,19 +120,26 @@ Handler are also looked under request-method keys: `:get`, `:head`, `:patch`, `:
|
|||
; 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:
|
||||
|
||||
```clj
|
||||
(-> app
|
||||
(ring/get-router)
|
||||
(reitit/match-by-name ::ping)
|
||||
:path)
|
||||
(r/match-by-name ::ping)
|
||||
(r/match->path))
|
||||
; "/ping"
|
||||
```
|
||||
|
||||
# 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`
|
||||
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"})
|
||||
; {: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)
|
||||
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)
|
||||
->endpoint (fn [p d m s]
|
||||
(-> (middleware/compile-result [p d] opts s)
|
||||
|
|
@ -37,7 +37,12 @@
|
|||
(fn [acc method]
|
||||
(cond-> acc
|
||||
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))]
|
||||
(if-not (seq childs)
|
||||
(->methods true top)
|
||||
|
|
@ -48,6 +53,9 @@
|
|||
(->methods (:handler top) data)
|
||||
childs))))
|
||||
|
||||
(defn default-options-handler [_]
|
||||
{:status 200, :body ""})
|
||||
|
||||
;;
|
||||
;; public api
|
||||
;;
|
||||
|
|
@ -57,6 +65,14 @@
|
|||
support for http-methods and Middleware. See [docs](https://metosin.github.io/reitit/)
|
||||
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:
|
||||
|
||||
(router
|
||||
|
|
@ -64,13 +80,13 @@
|
|||
[\"/users\" {:get get-user
|
||||
:post update-user
|
||||
:delete {:middleware [wrap-delete]
|
||||
:handler delete-user}}]])
|
||||
|
||||
See router options from [[reitit.core/router]] and [[reitit.middleware/router]]."
|
||||
:handler delete-user}}]])"
|
||||
([data]
|
||||
(router data nil))
|
||||
([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))))
|
||||
|
||||
(defn routes
|
||||
|
|
@ -182,7 +198,7 @@
|
|||
|
||||
| key | description |
|
||||
| --------------|-------------|
|
||||
| `:middleware` | Optional sequence of middleware that are wrap the [[ring-handler]]"
|
||||
| `:middleware` | Optional sequence of middleware that wrap the ring-handler"
|
||||
([router]
|
||||
(ring-handler router nil))
|
||||
([router default-handler]
|
||||
|
|
|
|||
|
|
@ -197,6 +197,47 @@
|
|||
(testing "handler rejects"
|
||||
(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
|
||||
(let [promise #(let [value (atom ::nil)]
|
||||
(fn
|
||||
|
|
|
|||
|
|
@ -183,6 +183,19 @@
|
|||
(-> {:request-method :get :uri "/swagger.json"}
|
||||
(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
|
||||
(let [app (ring/ring-handler
|
||||
(ring/router
|
||||
|
|
|
|||
Loading…
Reference in a new issue