mirror of
https://github.com/metosin/reitit.git
synced 2025-12-17 08:21:11 +00:00
98 lines
3.5 KiB
Markdown
98 lines
3.5 KiB
Markdown
# Data-driven Middleware
|
|
|
|
Ring [defines middleware](https://github.com/ring-clojure/ring/wiki/Concepts#middleware) as a function of type `handler & args => request => response`. It's easy to undrstand and enables great performance. Still, in the end - the middleware-chain is just a opaque function, making things like documentation and debugging hard.
|
|
|
|
Reitit does things bit differently:
|
|
|
|
1. Middleware is defined as a vector (of middleware) enabling the chain to be malipulated before turned into the runtime middleware function.
|
|
2. Middleware can be defined as first-class data entries
|
|
|
|
### Middleware as data
|
|
|
|
All values in the `:middleware` vector in the route data are coerced into `reitit.ring.middleware/Middleware` Records with using the `reitit.ring.middleware/IntoMiddleware` Protocol. By default, functions, maps and `Middleware` records are allowed.
|
|
|
|
Records can have arbitrary keys, but the following keys have a special purpose:
|
|
|
|
| key | description |
|
|
| ------------|-------------|
|
|
| `:name` | Name of the middleware as a qualified keyword (optional)
|
|
| `:wrap` | The actual middleware function of `handler & args => request => response`
|
|
| `:gen-wrap` | Middleware function generation function, see [compiling middleware](compiling_middleware.md).
|
|
|
|
Middleware Records are accessible in their raw form in the compiled route results, thus available for inventories, creating api-docs etc.
|
|
|
|
For the actual request processing, the Records are unwrapped into normal functions and composed into a middleware function chain, yielding zero runtime penalty.
|
|
|
|
### Creating Middleware
|
|
|
|
The following produce identical middleware runtime function.
|
|
|
|
#### Function
|
|
|
|
```clj
|
|
(defn wrap [handler id]
|
|
(fn [request]
|
|
(handler (update request ::acc (fnil conj []) id))))
|
|
```
|
|
|
|
### Record
|
|
|
|
```clj
|
|
(require '[reitit.ring.middleware :as middleware])
|
|
|
|
(def wrap2
|
|
(middleware/create
|
|
{:name ::wrap2
|
|
:description "Middleware that does things."
|
|
:wrap wrap}))
|
|
```
|
|
|
|
#### Map
|
|
|
|
```clj
|
|
(def wrap3
|
|
{:name ::wrap3
|
|
:description "Middleware that does things."
|
|
:wrap wrap})
|
|
```
|
|
|
|
### Using Middleware
|
|
|
|
```clj
|
|
(require '[reitit.ring :as ring])
|
|
|
|
(defn handler [{:keys [::acc]}]
|
|
{:status 200, :body (conj acc :handler)})
|
|
|
|
(def app
|
|
(ring/ring-handler
|
|
(ring/router
|
|
["/api" {:middleware [[wrap 1] [wrap2 2]]}
|
|
["/ping" {:get {:middleware [[wrap3 3]]
|
|
:handler handler}}]])))
|
|
```
|
|
|
|
All the middleware are called correctly:
|
|
|
|
```clj
|
|
(app {:request-method :get, :uri "/api/ping"})
|
|
; {:status 200, :body [1 2 3 :handler]}
|
|
```
|
|
|
|
### Future
|
|
|
|
Some things bubblin' under:
|
|
|
|
* Hooks to manipulate the `:middleware` chain before compilation
|
|
* Support `Keyword` expansion into Middleware, enabling external Middleware Registries (duct/integrant/macchiato -style)
|
|
* Support Middleware dependency resolution with new keys `:requires` and `:provides`. Values are set of top-level keys of the request. e.g.
|
|
* `InjectUserIntoRequestMiddleware` requires `#{:session}` and provides `#{:user}`
|
|
* `AuthorizationMiddleware` requires `#{:user}`
|
|
* Support partial `s/keys` route data specs with Middleware (and Router). Merged together to define sound spec for the route data and/or route data for a given route.
|
|
* e.g. `AuthrorizationMiddleware` has a spec defining `:roles` key (a set of keywords)
|
|
* Documentation for the route data
|
|
* Route data is validated against the spec:
|
|
* Complain of keywords that are not handled by anything
|
|
* Propose fixes for typos (Figwheel-style)
|
|
|
|
Ideas welcome & see [issues](https://github.com/metosin/reitit/issues) for details.
|