Polish docs

This commit is contained in:
Tommi Reiman 2018-07-28 12:01:12 +03:00
parent dc0fae875a
commit 8321589f04
9 changed files with 127 additions and 49 deletions

View file

@ -20,31 +20,33 @@
; ["/api/3" {}]]
```
* A [Guide to compose routers](https://metosin.github.io/reitit/advanced/composing_routers.html)
* Welcome Middleware and Intercetor Registries!
* when Keywords are used in place of middleware / interceptor, a lookup is done into Router option `::middleware/registry` (or `::interceptor/registrty`) with the key. Fails fast with missing registry entries.
* fixes [#32](https://github.com/metosin/reitit/issues/32)
* when Keywords are used in place of middleware / interceptor, a lookup is done into Router option `::middleware/registry` (or `::interceptor/registry`) with the key. Fails fast with missing registry entries.
* fixes [#32](https://github.com/metosin/reitit/issues/32).
* full documentation [here](https://metosin.github.io/reitit/ring/middleware_registry.html).
```clj
(require '[reitit.ring :as ring])
(require '[reitit.middleware :as middleware])
(defn wrap-xyz [handler value]
(fn [request]
(handler (update request :xyz (fnil + 0) value))))
(def app
(ring/ring-handler
(ring/router
["/api" {:middleware [[:xyz 20]]}
["/ping" {:middleware [:xyz10]
:get identity}]]
{::middleware/registry {:xyz wrap-xyz
:xyz10 [:xyz 10]}})))
(-> {:request-method :get, :uri "/api/ping"} (app) :xyz)
; 30
```
(require '[reitit.ring :as ring])
(require '[reitit.middleware :as middleware])
(defn wrap-bonus [handler value]
(fn [request]
(handler (update request :bonus (fnil + 0) value))))
(def app
(ring/ring-handler
(ring/router
["/api" {:middleware [[:bonus 20]]}
["/bonus" {:middleware [:bonus10]
:get (fn [{:keys [bonus]}]
{:status 200, :body {:bonus bonus}})}]]
{::middleware/registry {:bonus wrap-bonus
:bonus10 [:bonus 10]}})))
(app {:request-method :get, :uri "/api/bonus"})
; {:status 200, :body {:bonus 30}}
```
## `reitit-swagger`

View file

@ -27,6 +27,7 @@
* [Static Resources](ring/static.md)
* [Dynamic Extensions](ring/dynamic_extensions.md)
* [Data-driven Middleware](ring/data_driven_middleware.md)
* [Middleware Registry](ring/middleware_registry.md)
* [Pluggable Coercion](ring/coercion.md)
* [Route Data Validation](ring/route_data_validation.md)
* [Compiling Middleware](ring/compiling_middleware.md)

View file

@ -19,7 +19,7 @@ Let's create a router:
["/bar/:id" ::bar]]))
```
We can query it's resolved routes and options:
We can query the resolved routes and options:
```clj
(r/routes router)
@ -135,7 +135,7 @@ Matching by path:
; :path "/olipa/iso/kala"}
```
That didn't work as we wanted, as the nested routers don't have such a route. Thing is that the core routing doesn't understand anything about our new `:router` key, so it only matched against the top-level router, which gave a match for the catch-all path.
That didn't work as we wanted, as the nested routers don't have such a route. The core routing doesn't understand anything the `:router` key, so it only matched against the top-level router, which gave a match for the catch-all path.
As the `Match` contains all the route data, we can create a new matching function that understands the `:router` key. Below is a function that does recursive matching using the subrouters. It returns either `nil` or a vector of mathces.
@ -214,9 +214,9 @@ First, we need to modify our matching function to support router references:
(list match))))
```
Then, we need some router (references).
Then, we need some routers.
First, a reference to a router that can be updated on demand on background, for example when a new entry in inserted into a database. We'll wrap the router into a `atom`:
First, a reference to a router that can be updated on background, for example when a new entry in inserted into a database. We'll wrap the router into a `atom`:
```clj
(def beer-router
@ -251,20 +251,20 @@ We can compose the routers into a system-level static root router:
Matching root routes:
```clj
(name-path "/vodka/russian")
(name-path router "/vodka/russian")
; nil
(name-path "/gin/napue")
(name-path router "/gin/napue")
; [:napue]
```
Matching (nested) beer routes:
```clj
(name-path "/beers/lager")
(name-path router "/beers/lager")
; [:beers :lager]
(name-path "/beers/saison")
(name-path router "/beers/saison")
; nil
```
@ -277,7 +277,7 @@ No saison!? Let's add the route:
There we have it:
```clj
(name-path "/beers/saison")
(name-path router "/beers/saison")
; [:beers :saison]
```
@ -294,16 +294,16 @@ We can't add conflicting routes:
The dynamic routes are re-created on every request:
```clj
(name-path "/dynamic/duo")
(name-path router "/dynamic/duo")
; [:dynamic :duo71]
(name-path "/dynamic/duo")
(name-path router "/dynamic/duo")
; [:dynamic :duo55]
```
### Performance
With nested routers, instead of having to do just one route match, matching is recursive, which adds a small cost. All nested routers need to be of type catch-all at top-level, which is order of magnitude slower than fully static routes. Dynamic routes are the slowest ones, at least an order of magnitude slower, as the router needs to be recreated for each request.
With nested routers, instead of having to do just one route match, matching is recursive, which adds a small cost. All nested routers need to be of type catch-all at top-level, which is order of magnitude slower than fully static routes. Dynamic routes are the slowest ones, at least two orders of magnitude slower, as the router needs to be recreated for each request.
A quick benchmark on the recursive lookups:
@ -332,26 +332,23 @@ Comparing the dynamic routing performance with Compojure:
|------------------|---------|-----------------------
| `/dynamic/duo` | 20000ns | compojure
We could use the Router `:compile` hook to compile the nested routers for better performance. Also, the dynamic routing could be made faster, by allowing router creation time features like conflict resolution to be disabled.
Can we make the nester routing faster? Sure. We could use the Router `:compile` hook to compile the nested routers for better performance. We could also allow router creation rules to be disabled, to get the dynamic routing much faster.
### When to use nested routers?
Nesting routers is not trivial and because of that, should be avoided. For dynamic (request-time) route generation, it's the only choise. For other cases, nested routes are most likely a better option.
Nesting routers is not trivial and because of that, should be avoided. For dynamic (request-time) route generation, it's the only choise. For other cases, nested routes are most likely a better option.
Let's re-create the previous example with normal route composition.
Let's re-create the previous example with normal route nesting/composition.
A helper to create beer-routes and the root router.
A helper to the root router:
```clj
(defn beer-routes [beers]
(for [beer beers]
[(str "/" beer) (keyword "beer" beer)]))
(defn create-router [beer-routes]
(defn create-router [beers]
(r/router
[["/gin/napue" :napue]
["/ciders/*" :ciders]
["/beers" beer-routes]
["/beers" (for [beer beers]
[(str "/" beer) (keyword "beer" beer)])]
["/dynamic/*" {:name :dynamic
:router dynamic-router}]]))
```
@ -363,7 +360,7 @@ New new root router *reference* and a helper to reset it:
(atom (create-router nil)))
(defn reset-router! [beers]
(reset! router (-> beers beer-routes create-router)))
(reset! router (create-router beers)))
```
The routing tree:
@ -402,7 +399,7 @@ And the routing works:
;[:beer/sahti]
```
The beer-routes all now match in constant time.
All the beer-routes now match in constant time.
| path | time | type
|-----------------|---------|-----------------------

View file

@ -28,6 +28,7 @@
{:file "doc/advanced/README.md"}
["Configuring Routers"
{:file "doc/advanced/configuring_routers.md"}]
["Composing Routers" {:file "doc/advanced/composing_routers.md"}]
["Different Routers" {:file "doc/advanced/different_routers.md"}]
["Route Validation" {:file "doc/advanced/route_validation.md"}]
["Dev Workflow" {:file "doc/advanced/dev_workflow.md"}]]
@ -40,6 +41,7 @@
["Dynamic Extensions" {:file "doc/ring/dynamic_extensions.md"}]
["Data-driven Middleware"
{:file "doc/ring/data_driven_middleware.md"}]
["Middleware Registry" {:file "doc/ring/middleware_registry.md"}]
["Pluggable Coercion" {:file "doc/ring/coercion.md"}]
["Route Data Validation"
{:file "doc/ring/route_data_validation.md"}]

View file

@ -6,6 +6,7 @@
* [Static Resources](static.md)
* [Dynamic Extensions](dynamic_extensions.md)
* [Data-driven Middleware](data_driven_middleware.md)
* [Middleware Registry](middleware_registry.md)
* [Pluggable Coercion](coercion.md)
* [Route Data Validation](route_data_validation.md)
* [Compiling Middleware](compiling_middleware.md)

View file

@ -0,0 +1,63 @@
# Middleware Registry
The `:middleware` syntax in `reitit-ring` supports also Keywords. Keywords are looked from Middleware Registry, which is a map of `keyword => IntoMiddleware`. Middleware registry should be stored under key `:reitit.middleware/registry` in the router options. If a middleware keyword isn't found in the registry, router creation fails fast with descriptive error message.
## Examples
Application using middleware defined in the Middleware Registry:
```clj
(require '[reitit.ring :as ring])
(require '[reitit.middleware :as middleware])
(defn wrap-bonus [handler value]
(fn [request]
(handler (update request :bonus (fnil + 0) value))))
(def app
(ring/ring-handler
(ring/router
["/api" {:middleware [[:bonus 20]]}
["/bonus" {:middleware [:bonus10]
:get (fn [{:keys [bonus]}]
{:status 200, :body {:bonus bonus}})}]]
{::middleware/registry {:bonus wrap-bonus
:bonus10 [:bonus 10]}})))
```
Works as expected:
```clj
(app {:request-method :get, :uri "/api/bonus"})
; {:status 200, :body {:bonus 30}}
```
Router creation fails fast if registry doesn't contain the Middleware:
```clj
(def app
(ring/ring-handler
(ring/router
["/api" {:middleware [[:bonus 20]]}
["/bonus" {:middleware [:bonus10]
:get (fn [{:keys [bonus]}]
{:status 200, :body {:bonus bonus}})}]]
{::middleware/registry {:bonus wrap-bonus}})))
;CompilerException clojure.lang.ExceptionInfo: Middleware :bonus10 not found in registry.
;
;Available middleware in registry:
;
;| :id | :description |
;|--------+--------------------------------------|
;| :bonus | reitit.ring_test$wrap_bonus@59fddabb |
```
## When to use the registry?
Middleware as Keywords helps to keep the routes (all but handlers) as literal data (e.g. data that evaluates to itself) enabling the routes to be persisted in external formats like EDN-files and databases. Duct is a good example where the [middleware can be referenced from EDN-files](https://github.com/duct-framework/duct/wiki/Configuration). It should be easy to make Duct configuration a Middleware Registry in `reitit-ring`.
The bad thing it's an extra indirection, making things more complex and removed the default IDE-support of "goto definition" or "look source".
## TODO
* a prefilled registry of common middleware in the `reitit-middleware`

View file

@ -80,10 +80,12 @@ Name-based reverse routing:
# 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 value of the following:
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:
1. normal ring middleware function `handler -> request -> response`
2. vector of middleware function `handler ?args -> request -> response` and optinally it's args.
2. vector of middleware function `[handler args*] -> request -> response` and it's arguments
3. a [data-driven middleware](data_driven_middleware.md) record or a map
4. a Keyword name, to lookup the middleware from a [Middleware Registry](middleware_registry.md)
A middleware and a handler:

View file

@ -20,7 +20,7 @@
(into-interceptor interceptor data opts))
(throw
(ex-info
(str "Interceptor " (pr-str this) " not found in registry.")
(str "Interceptor " this " not found in registry.")
{:keyword this
:registry registry}))))
@ -126,6 +126,7 @@
| key | description |
| --------------------------------|-------------|
| `:reitit.interceptor/transform` | Function of [Interceptor] => [Interceptor] to transform the expanded Interceptors (default: identity).
| `:reitit.interceptor/registry` | Map of `keyword => IntoInterceptor` to replace keyword references into Interceptor
See router options from [[reitit.core/router]]."
([data]

View file

@ -1,5 +1,6 @@
(ns reitit.middleware
(:require [meta-merge.core :refer [meta-merge]]
[clojure.pprint :as pprint]
[reitit.core :as r]
[reitit.impl :as impl]))
@ -20,7 +21,14 @@
(into-middleware middleware data opts))
(throw
(ex-info
(str "Middleware " (pr-str this) " not found in registry.")
(str
"Middleware " this " not found in registry.\n\n"
(if (seq registry)
(str
"Available middleware in registry:\n"
(with-out-str
(pprint/print-table [:id :description] (for [[k v] registry] {:id k :description v}))))
"see [reitit.middleware/router] on how to add middleware to the registry.\n") "\n")
{:keyword this
:registry registry}))))
@ -127,6 +135,7 @@
| 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
See other router options from [[reitit.core/router]]."
([data]