mirror of
https://github.com/metosin/reitit.git
synced 2025-12-17 08:21:11 +00:00
72 lines
3.6 KiB
Markdown
72 lines
3.6 KiB
Markdown
# Compiling Middleware
|
|
|
|
The [meta-data extensions](ring.md#meta-data-based-extensions) are a easy way to extend the system. Routes meta-data can be transformed into any shape (records, functions etc.) in route compilation, enabling fast access at request-time.
|
|
|
|
Still, we can do better. As we know the exact route that interceptor/middleware is linked to, we can pass the (compiled) route information into the interceptor/middleware at creation-time. It can extract and transform relevant data just for it and pass it into the actual request-handler via a closure - yielding faster runtime processing.
|
|
|
|
To do this we use [middleware records](ring.md#middleware-records) `:gen` hook instead of the normal `:wrap`. `:gen` expects a function of `route-meta router-opts => wrap`. Middleware can also return `nil`, which effective unmounts the middleware. Why mount a `wrap-enforce-roles` middleware for a route if there are no roles required for it?
|
|
|
|
To demonstrate the two approaches, below are response coercion middleware written as normal ring middleware function and as middleware record with `:gen`. These are the actual codes are from [`reitit.coercion`](https://github.com/metosin/reitit/blob/master/src/reitit/coercion.cljc):
|
|
|
|
## Naive
|
|
|
|
* Extracts the compiled route information on every request.
|
|
|
|
```clj
|
|
(defn wrap-coerce-response
|
|
"Pluggable response coercion middleware.
|
|
Expects a :coercion of type `reitit.coercion.protocol/Coercion`
|
|
and :responses from route meta, otherwise does not mount."
|
|
[handler]
|
|
(fn
|
|
([request]
|
|
(let [response (handler request)
|
|
method (:request-method request)
|
|
match (ring/get-match request)
|
|
responses (-> match :result method :meta :responses)
|
|
coercion (-> match :meta :coercion)
|
|
opts (-> match :meta :opts)]
|
|
(if (and coercion responses)
|
|
(let [coercers (response-coercers coercion responses opts)
|
|
coerced (coerce-response coercers request response)]
|
|
(coerce-response coercers request (handler request)))
|
|
(handler request))))
|
|
([request respond raise]
|
|
(let [response (handler request)
|
|
method (:request-method request)
|
|
match (ring/get-match request)
|
|
responses (-> match :result method :meta :responses)
|
|
coercion (-> match :meta :coercion)
|
|
opts (-> match :meta :opts)]
|
|
(if (and coercion responses)
|
|
(let [coercers (response-coercers coercion responses opts)
|
|
coerced (coerce-response coercers request response)]
|
|
(handler request #(respond (coerce-response coercers request %))))
|
|
(handler request respond raise))))))
|
|
```
|
|
|
|
## Compiled
|
|
|
|
* Route information is provided via a closure
|
|
* Pre-compiled coercers
|
|
* Mounts only if `:coercion` and `:responses` are defined for the route
|
|
|
|
```clj
|
|
(def gen-wrap-coerce-response
|
|
"Generator for pluggable response coercion middleware.
|
|
Expects a :coercion of type `reitit.coercion.protocol/Coercion`
|
|
and :responses from route meta, otherwise does not mount."
|
|
(middleware/create
|
|
{:name ::coerce-response
|
|
:gen (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)))))))}))
|
|
```
|
|
|
|
The `:gen` -version has 50% less code, is easier to reason about and is 2-4x faster on basic perf tests.
|