reitit/doc/ring/compiling_middleware.md

72 lines
3.5 KiB
Markdown
Raw Normal View History

2017-09-18 05:30:03 +00:00
# Compiling Middleware
2017-09-14 13:33:36 +00:00
The [dynamic extensions](dynamic_extensions.md) is a easy way to extend the system. To enable fast lookups into route data, we can compile them into any shape (records, functions etc.) we want, enabling fast access at request-time.
2017-09-14 13:33:36 +00:00
Still, we can do much better. As we know the exact route that middleware/interceptor is linked to, we can pass the (compiled) route information into the middleware/interceptor at creation-time. It can do local reasoning: extract and transform relevant data just for it and pass it into the actual request-handler via a closure - yielding much faster runtime processing. It can also decide not to mount itself by returning `nil`. Why mount a `wrap-enforce-roles` middleware for a route if there are no roles required for it?
2017-09-14 13:33:36 +00:00
2017-11-18 10:47:16 +00:00
To enable this we use [middleware records](data_driven_middleware.md) `:gen-wrap` key instead of the normal `:wrap`. `:gen-wrap` expects a function of `route-data router-opts => ?wrap`.
2017-09-14 13:33:36 +00:00
To demonstrate the two approaches, below are response coercion middleware written as normal ring middleware function and as middleware record with `:gen-wrap`. Actual codes can be found in [`reitit.ring.coercion`](https://github.com/metosin/reitit/blob/master/src/reitit/ring/coercion.cljc):
2017-09-14 13:33:36 +00:00
2017-09-18 05:30:03 +00:00
## Naive
2017-09-14 13:33:36 +00:00
* Reads the compiled route information on every request.
2017-09-14 13:33:36 +00:00
```clj
(defn wrap-coerce-response
"Pluggable response coercion middleware.
Expects a :coercion of type `reitit.coercion.protocol/Coercion`
2017-11-18 10:47:16 +00:00
and :responses from route data, otherwise will do nothing."
2017-09-14 13:33:36 +00:00
[handler]
(fn
([request]
(let [response (handler request)
method (:request-method request)
match (ring/get-match request)
2017-11-18 10:47:16 +00:00
responses (-> match :result method :data :responses)
coercion (-> match :data :coercion)
opts (-> match :data :opts)]
2017-09-14 13:33:36 +00:00
(if (and coercion responses)
(let [coercers (response-coercers coercion responses opts)]
(coerce-response coercers request response))
response)))
2017-09-14 13:33:36 +00:00
([request respond raise]
(let [method (:request-method request)
2017-09-14 13:33:36 +00:00
match (ring/get-match request)
2017-11-18 10:47:16 +00:00
responses (-> match :result method :data :responses)
coercion (-> match :data :coercion)
opts (-> match :data :opts)]
2017-09-14 13:33:36 +00:00
(if (and coercion responses)
(let [coercers (response-coercers coercion responses opts)]
2017-09-14 13:33:36 +00:00
(handler request #(respond (coerce-response coercers request %))))
(handler request respond raise))))))
```
2017-09-18 05:30:03 +00:00
## Compiled
2017-09-14 13:33:36 +00:00
* Route information is provided via a closure
* Pre-compiled coercers
* Mounts only if `:coercion` and `:responses` are defined for the route
```clj
(require '[reitit.ring.middleware :as middleware])
2017-09-14 13:33:36 +00:00
(def gen-wrap-coerce-response
"Generator for pluggable response coercion middleware.
Expects a :coercion of type `reitit.coercion.protocol/Coercion`
2017-11-18 10:47:16 +00:00
and :responses from route data, otherwise does not mount."
2017-09-14 13:33:36 +00:00
(middleware/create
{:name ::coerce-response
:gen-wrap (fn [{:keys [responses coercion opts]} _]
(if (and coercion responses)
(let [coercers (response-coercers coercion responses opts)]
(fn [handler]
(fn
([request]
(coerce-response coercers request (handler request)))
([request respond raise]
(handler request #(respond (coerce-response coercers request %)) raise)))))))}))
2017-09-14 13:33:36 +00:00
```
The latter has 50% less code, is easier to reason about and is much faster.