reitit/doc/ring/ring.md

199 lines
5.1 KiB
Markdown
Raw Normal View History

2017-09-18 05:30:03 +00:00
# Ring Router
2017-09-14 13:33:36 +00:00
2017-09-18 05:30:03 +00:00
[Ring](https://github.com/ring-clojure/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 runs a custom route compiler, creating a optimized stucture for handling route matches, with compiled middleware chain & handlers for all request methods. It also ensures that all routes have a `:handler` defined.
2017-09-14 13:33:36 +00:00
Simple Ring app:
```clj
(require '[reitit.ring :as ring])
(defn handler [_]
{:status 200, :body "ok"})
(def app
(ring/ring-handler
(ring/router
["/ping" handler])))
```
Applying the handler:
```clj
(app {:request-method :get, :uri "/favicon.ico"})
; nil
(app {:request-method :get, :uri "/ping"})
; {:status 200, :body "ok"}
```
The expanded routes shows the compilation results:
2017-09-14 13:33:36 +00:00
```clj
(-> app (ring/get-router) (reitit/routes))
; [["/ping"
; {:handler #object[...]}
2017-11-18 10:47:16 +00:00
; #Methods{:any #Endpoint{:data {:handler #object[...]},
2017-09-14 13:33:36 +00:00
; :handler #object[...],
; :middleware []}}]]
```
Note that the compiled resuts as third element in the route vector.
2017-09-18 05:30:03 +00:00
# Request-method based routing
2017-09-14 13:33:36 +00:00
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.
```clj
(def app
(ring/ring-handler
(ring/router
["/ping" {:name ::ping
:get handler
:post handler}])))
(app {:request-method :get, :uri "/ping"})
; {:status 200, :body "ok"}
(app {:request-method :put, :uri "/ping"})
; nil
```
Name-based reverse routing:
2017-09-14 13:33:36 +00:00
```clj
(-> app
(ring/get-router)
(reitit/match-by-name ::ping)
:path)
; "/ping"
```
2017-09-18 05:30:03 +00:00
# Middleware
2017-09-14 13:33:36 +00:00
Middleware can be added with a `:middleware` key, either to top-level or under `:request-method` submap. It's value should be a vector value of the following:
2017-09-14 13:33:36 +00:00
1. normal ring middleware function `handler -> request -> response`
2017-09-14 13:33:36 +00:00
2. vector of middleware function `handler ?args -> request -> response` and optinally it's args.
A middleware and a handler:
```clj
(defn wrap [handler id]
(fn [request]
(handler (update request ::acc (fnil conj []) id))))
(defn handler [{:keys [::acc]}]
{:status 200, :body (conj acc :handler)})
```
App with nested middleware:
```clj
(def app
(ring/ring-handler
(ring/router
["/api" {:middleware [#(wrap % :api)]}
["/ping" handler]
["/admin" {:middleware [[wrap :admin]]}
["/db" {:middleware [[wrap :db]]
:delete {:middleware [[wrap :delete]]
2017-09-14 13:33:36 +00:00
:handler handler}}]]])))
```
Middleware is applied correctly:
```clj
(app {:request-method :delete, :uri "/api/ping"})
; {:status 200, :body [:api :handler]}
```
```clj
(app {:request-method :delete, :uri "/api/admin/db"})
; {:status 200, :body [:api :admin :db :delete :handler]}
```
2018-01-23 19:14:47 +00:00
# Default handler
2017-11-09 15:59:24 +00:00
2018-01-23 19:14:47 +00:00
By default, if no routes match, `nil` is returned, which is not valid response in Ring:
2017-11-09 15:59:24 +00:00
2018-01-23 19:14:47 +00:00
```clj
(defn handler [_]
{:status 200, :body ""})
(def app
(ring/ring-handler
(ring/router
["/ping" handler])))
(app {:uri "/invalid"})
; nil
```
Setting the default-handler as a second argument to `ring-handler`:
2017-11-09 15:59:24 +00:00
```clj
(def app
2018-01-23 19:14:47 +00:00
(ring/ring-handler
(ring/router
["/ping" handler])
(constantly {:status 404, :body ""})))
2017-11-09 15:59:24 +00:00
(app {:uri "/invalid"})
2018-01-23 19:14:47 +00:00
; {:status 404, :body ""}
```
To get more correct http error responses, `ring/create-default-handler` can be used. It differentiates `:not-found` (no route matched), `:method-not-accepted` (no method matched) and `:not-acceptable` (handler returned `nil`).
With defaults:
```clj
(def app
(ring/ring-handler
(ring/router
[["/ping" {:get handler}]
["/pong" (constantly nil)]])
(ring/create-default-handler)))
(app {:request-method :get, :uri "/ping"})
; {:status 200, :body ""}
(app {:request-method :get, :uri "/"})
; {:status 404, :body ""}
(app {:request-method :post, :uri "/ping"})
; {:status 405, :body ""}
(app {:request-method :get, :uri "/pong"})
; {:status 406, :body ""}
```
With custom responses:
```clj
(def app
(ring/ring-handler
(ring/router
[["/ping" {:get handler}]
["/pong" (constantly nil)]])
(ring/create-default-handler
{:not-found (constantly {:status 404, :body "kosh"})
:method-not-allowed (constantly {:status 405, :body "kosh"})
:not-acceptable (constantly {:status 406, :body "kosh"})})))
(app {:request-method :get, :uri "/ping"})
; {:status 200, :body ""}
(app {:request-method :get, :uri "/"})
; {:status 404, :body "kosh"}
(app {:request-method :post, :uri "/ping"})
; {:status 405, :body "kosh"}
(app {:request-method :get, :uri "/pong"})
; {:status 406, :body "kosh"}
2017-11-09 15:59:24 +00:00
```
2017-09-18 05:30:03 +00:00
# Async Ring
2017-09-14 13:33:36 +00:00
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.