diff --git a/doc/ring/data_driven_middleware.md b/doc/ring/data_driven_middleware.md index f4477621..8b9f59d3 100644 --- a/doc/ring/data_driven_middleware.md +++ b/doc/ring/data_driven_middleware.md @@ -1,23 +1,25 @@ # 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. +Ring [defines middleware](https://github.com/ring-clojure/ring/wiki/Concepts#middleware) as a function of type `handler & args => request => response`. It's relatively easy to understand and enables good performance. Downside is that the middleware-chain is just a opaque function, making things like debugging and composition hard. It's too easy to apply the middleware in wrong order. -Reitit does things bit differently: +Reitit defines middleware as data: -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 +1. Middleware can be defined as first-class data entries +2. Middleware can be defined as a [duct-style](https://github.com/duct-framework/duct/wiki/Configuration) vector (of middleware) +4. Middleware can be optimized & [compiled](compiling_middleware.md) againt an endpoint +3. Middleware chain can be transformed by the router -### Middleware as data +## 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). +| 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. @@ -27,7 +29,7 @@ For the actual request processing, the Records are unwrapped into normal functio The following produce identical middleware runtime function. -#### Function +### Function ```clj (defn wrap [handler id] @@ -47,7 +49,7 @@ The following produce identical middleware runtime function. :wrap wrap})) ``` -#### Map +### Map ```clj (def wrap3 @@ -56,7 +58,9 @@ The following produce identical middleware runtime function. :wrap wrap}) ``` -### Using Middleware +## Using Middleware + +`:middleware` is merged to endpoints by the `router`. ```clj (require '[reitit.ring :as ring]) @@ -72,18 +76,61 @@ The following produce identical middleware runtime function. :handler handler}}]]))) ``` -All the middleware are called correctly: +All the middleware are applied correctly: ```clj (app {:request-method :get, :uri "/api/ping"}) ; {:status 200, :body [1 2 3 :handler]} ``` -### Future +## Compiling middleware + +Middleware can be optimized against an endpoint using [middleware compilation](compiling_middleware.md). + +## Transforming the middleware chain + +There is an extra option in ring-router (actually, in the undelaying middleware-router): `:reitit.ring.middleware/transform` to transform the middleware chain per endpoint. It sees the vector of compiled middleware and should return a new vector of middleware. + +#### Adding debug middleware between all other middleware + +```clj +(def app + (ring/ring-handler + (ring/router + ["/api" {:middleware [[wrap 1] [wrap2 2]]} + ["/ping" {:get {:middleware [[wrap3 3]] + :handler handler}}]] + {::middleware/transform #(interleave % (repeat [wrap :debug]))}))) +``` + +``` +(app {:request-method :get, :uri "/api/ping"}) +; {:status 200, :body [1 :debug 2 :debug 3 :debug :handler]} +``` + +#### Reversing the middleware chain + +```clj +(def app + (ring/ring-handler + (ring/router + ["/api" {:middleware [[wrap 1] [wrap2 2]]} + ["/ping" {:get {:middleware [[wrap3 3]] + :handler handler}}]] + {::middleware/transform reverse)}))) +``` + +``` +(app {:request-method :get, :uri "/api/ping"}) +; {:status 200, :body [3 2 1 :handler]} +``` + +## Roadmap for middleware Some things bubblin' under: -* Hooks to manipulate the `:middleware` chain before compilation +* Re-package all useful middleware into (optimized) data-driven Middleware + * just package or a new community-repo with rehosting stuffm? * 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}`