1 | ---
|
2 | title: Defining and Working With Middleware
|
3 | layout: default
|
4 | category: Kettle
|
5 | ---
|
6 |
|
7 | ### Working with middleware
|
8 |
|
9 | The most crucial structuring device in the expressjs (or wider pillarjs) community is known as ***[middleware](http://expressjs.com/guide/using-middleware.html)***.
|
10 | In its most basic form, a piece of middleware is simply a function with the following signature:
|
11 |
|
12 | middleware(req, res, next)
|
13 |
|
14 | The elements `req` and `res` have been described in the section on [request components](RequestHandlersAndApps.md#members-defined-by-the-kettle-framework-at-top-level-on-a-request-component). The element `next` is a callback provided
|
15 | by the framework to be invoked when the middleware has completed its task. This could be seen as a form of [continuation passing style](https://en.wikipedia.org/wiki/Continuation-passing_style) with 0 arguments –
|
16 | although only in terms of control flow since in general middleware has its effect as a result of side-effects on the request and response. In express, middleware are typically accumulated in arrays or groups of arrays
|
17 | by directives such as `app.use`. If a piece of middleware completes without error, it will invoke the `next` callback with no argument, which will signal that control should pass to the next middleware in the
|
18 | current sequence, or back to the framework if the sequence is at an end. Providing an argument to the callback `next` is intended to signal an error
|
19 | and the framework will then abort the middleware chain and propagate the argument, conventionally named `err`, to an error handler. This creates an analogy with executing
|
20 | [promise sequences](http://stackoverflow.com/questions/24586110/resolve-promises-one-after-another-i-e-in-sequence) which we will return to when we construct [middleware components](#defining-and-registering-middleware-components).
|
21 |
|
22 | In Kettle, middleware can be scheduled more flexibly than by simply being accumulated in arrays – the priority of a piece of middleware can be freely adjusted by assigning it a [Priority](http://docs.fluidproject.org/infusion/development/Priorities.html)
|
23 | as seen in many places in the Infusion framework, and so integrators can easily arrange for middleware to be inserted in arbitrary positions in already existing applications.
|
24 |
|
25 | Middleware is accumulated at two levels in a Kettle application – firstly, overall middleware is accumulated at the top level of a `kettle.server` in an option named `rootMiddleware`. This is analogous to express
|
26 | [app-level middleware](http://expressjs.com/guide/using-middleware.html#middleware.application) registered with `app.use`. Secondly, individual request middleware
|
27 | can be attached to an individual `kettle.request` in its options at `requestMiddleware`. This is analogous to express [router-level middleware](http://expressjs.com/guide/using-middleware.html#middleware.router).
|
28 | The structure of these two options areas is the same, which we name `middlewareSequence`. When the request begins to be handled, the framework
|
29 | will execute the following in sequence:
|
30 |
|
31 | * The root middleware attached to the `kettle.server`
|
32 | * The request middleware attached to the resolved `kettle.request` component
|
33 | * The actual request handler designated by the request component's invoker `handleRequest`
|
34 |
|
35 | If any of the middleware in this sequence signals an error, the entire sequence will be aborted and an error returned to the client.
|
36 |
|
37 | ### Structure of entries in a `middlewareSequence`
|
38 |
|
39 | A `middlewareSequence` is a free hash of keys, considered as **namespaces** for the purpose of resolving [Priorities](http://docs.fluidproject.org/infusion/development/Priorities.html) onto
|
40 | records of type `middlewareEntry`:
|
41 |
|
42 | ```javascript
|
43 | {
|
44 | <middlewareKey> : <middlewareEntry>,
|
45 | <middlewareKey> : <middlewareEntry>,
|
46 | ...
|
47 | }
|
48 | ```
|
49 |
|
50 | <table>
|
51 | <thead>
|
52 | <tr>
|
53 | <th colspan="3">Members of a <code>middlewareEntry</code> entry within the <code>middlewareSequence</code> block of a component (<code>rootMiddleware</code> for <code>kettle.server</code> or <code>requestMiddleware</code> for <code>kettle.request</code>)</th>
|
54 | </tr>
|
55 | <tr>
|
56 | <th>Member</th>
|
57 | <th>Type</th>
|
58 | <th>Description</th>
|
59 | </tr>
|
60 | </thead>
|
61 | <tbody>
|
62 | <tr>
|
63 | <td><code>middleware</code></td>
|
64 | <td><code>String</code> (<a href="http://docs.fluidproject.org/infusion/development/IoCReferences.html">IoC Reference</a>)</td>
|
65 | <td>An IoC reference to the middleware component which should be inserted into the handler sequence. Often this will be qualified by the context <code>{middlewareHolder}</code> – e.g. <code>{middlewareHolder}.session</code> – to reference the core
|
66 | middleware collection attached to the <code>kettle.server</code> but middleware could be resolved from anywhere visible in the component tree. This should be a reference to a component descended from the grade <code>kettle.middleware</code></td>
|
67 | </tr>
|
68 | <tr>
|
69 | <td><code>priority</code> (optional)</td>
|
70 | <td><code>String</code> (<a href="http://docs.fluidproject.org/infusion/development/Priorities.html">Priority</a>)</td>
|
71 | <td>An encoding of a priority relative to some other piece of middleware within the same group – will typically be <code>before:middlewareKey</code> or <code>after:middlewareKey</code> for the <code>middlewareKey</code> of some
|
72 | other entry in the group</td>
|
73 | </tr>
|
74 | </tbody>
|
75 | </table>
|
76 |
|
77 | ### Defining and registering middleware components
|
78 |
|
79 | A piece of Kettle middleware is derived from grade `kettle.middleware`. This is a very simple grade which defines a single invoker named `handle` which accepts one argument, a `kettle.request`, and returns a
|
80 | promise representing the completion of the middleware. Conveniently a `fluid.promise` implementation is available in the framework, but you can return any variety of `thenable` that you please. Here is a skeleton,
|
81 | manually implemented middleware component:
|
82 |
|
83 | ```javascript
|
84 | fluid.defaults("examples.customMiddleware", {
|
85 | gradeNames: "kettle.middleware",
|
86 | invokers: {
|
87 | handle: "examples.customMiddleware.handle"
|
88 | }
|
89 | });
|
90 |
|
91 | examples.customMiddleware.handle = function (request) {
|
92 | var togo = fluid.promise();
|
93 | if (request.req.params.id === 42) {
|
94 | togo.resolve();
|
95 | } else {
|
96 | togo.reject({
|
97 | isError: true,
|
98 | statusCode: 401,
|
99 | message: "Only the id 42 is authorised"
|
100 | });
|
101 | }
|
102 | return togo;
|
103 | };
|
104 | ```
|
105 |
|
106 | The framework makes it very easy to adapt any standard express middleware into a middleware component by means of the adaptor grade `kettle.plainMiddleware`. This accepts any standard express
|
107 | middleware as the option named `middleware` and from it fabricates a `handle` method with the semantic we just saw earlier. Any options that the middleware accepts can be forwarded to it from
|
108 | the component's options. Here is an example from the framework's own `json` middleware grade:
|
109 |
|
110 | ```javascript
|
111 | kettle.npm.bodyParser = require("body-parser");
|
112 |
|
113 | fluid.defaults("kettle.middleware.json", {
|
114 | gradeNames: ["kettle.plainMiddleware"],
|
115 | middlewareOptions: {}, // see https://github.com/expressjs/body-parser#bodyparserjsonoptions
|
116 | middleware: "@expand:kettle.npm.bodyParser.json({that}.options.middlewareOptions)"
|
117 | });
|
118 | ```
|
119 |
|
120 | Consult the Infusion documentation on the [compact format for expanders](http://docs.fluidproject.org/infusion/development/ExpansionOfComponentOptions.html#compact-format-for-expanders) if you
|
121 | are unfamiliar with this syntax for designating elements in component options which arise from function calls.
|
122 |
|
123 | If your middleware may act asynchronously by performing some raw I/O, you must use the grade `kettle.plainAsyncMiddleware`
|
124 | instead. This is to ensure that the Kettle [request component](RequestHandlersAndApps.md#request-components)
|
125 | is unmarked during the period that the system is not acting on behalf of the currently incoming request. If the code
|
126 | for the middleware is under your control, it is recommended that wherever possible you use [dataSources][DataSources.md]
|
127 | for I/O since their callbacks automatically perform the necessary
|
128 | [request marking](DataSources.md#callback-wrapping-in-datasources).
|
129 |
|
130 | ### Built-in standard middleware bundled with Kettle
|
131 |
|
132 | Here we describe the built-in middleware supplied with Kettle, which is mostly sourced from standard middleware in the [express](http://expressjs.com/) and [pillarjs](https://github.com/pillarjs)
|
133 | communities. You can consult the straightforward implementations in [KettleMiddleware.js](https://github.com/fluid-project/kettle/tree/master/lib/KettleMiddleware.js) for suggestions for how
|
134 | to implement your own.
|
135 |
|
136 | <div style="font-size: smaller">
|
137 | <table>
|
138 | <thead>
|
139 | <tr><th>Grade name</th><th>Middleware name</th><th>Description</th><th>Accepted options</th><th>Standard IoC Path</th></tr>
|
140 | </thead>
|
141 | <tbody>
|
142 | <tr>
|
143 | <td><code>kettle.middleware.json</code></td>
|
144 | <td><a href="https://github.com/expressjs/body-parser">expressjs/body-parser</a></td>
|
145 | <td>Parses JSON request bodies, possibly with compression.</td>
|
146 | <td><code>middlewareOptions</code>, forwarded to <a href="https://github.com/expressjs/body-parser#bodyparserjsonoptions"<code>bodyParser.json(options)</code></a></td>
|
147 | <td><code>{middlewareHolder}.json</code></td>
|
148 | </tr>
|
149 | <tr>
|
150 | <td><code>kettle.middleware.urlencoded</code></td>
|
151 | <td><a href="https://github.com/expressjs/body-parser">expressjs/body-parser</a></td>
|
152 | <td>Applies URL decoding to a submitted request body</td>
|
153 | <td><code>middlewareOptions</code>, forwarded to <a href="https://github.com/expressjs/body-parser#bodyparserurlencodedoptions"><code>bodyParser.urlencoded(options)</code></a></td>
|
154 | <td><code>{middlewareHolder}.urlencoded</code></td>
|
155 | </tr>
|
156 | <tr>
|
157 | <td><code>kettle.middleware.cookieParser</code></td>
|
158 | <td><a href="https://github.com/expressjs/cookie-parser">expressjs/cookie-parser</a></td>
|
159 | <td>Parses the <code>Cookie</code> header as well as signed cookies via <code>req.secret</code>.</td>
|
160 | <td><code>secret</code> and <code>middlewareOptions</code>, forwarded to the two arguments of <a href="https://github.com/expressjs/cookie-parser#cookieparsersecret-options"><code>cookieParser(secret, options)</code></a></td>
|
161 | <td>none</td>
|
162 | </tr>
|
163 | <tr>
|
164 | <td><code>kettle.middleware.session</code></td>
|
165 | <td><a href="https://github.com/expressjs/session">expressjs/session</a></td>
|
166 | <td>Stores and retrieves <code>req.session</code> from various backends</td>
|
167 | <td><code>middlewareOptions</code>, forwarded to <a href="https://github.com/expressjs/session#sessionoptions"><code>session(options)</code></a></td>
|
168 | <td><code>{middlewareHolder}.session</code> when using <code>kettle.server.sessionAware</code> server</td>
|
169 | </tr>
|
170 | <tr>
|
171 | <td><code>kettle.middleware.static</code></td>
|
172 | <td><a href="https://github.com/expressjs/serve-static">expressjs/serve-static</a></td>
|
173 | <td>Serves static content from the filesystem</td>
|
174 | <td><code>root</code> and <code>middlewareOptions</code>, forwarded to the two arguments of <a href="https://github.com/expressjs/serve-static#servestaticroot-options"><code>serveStatic(root, options)</code></a></td>
|
175 | <td>none – user must configure on each use</td>
|
176 | </tr>
|
177 | <tr>
|
178 | <td><code>kettle.middleware.CORS</code></td>
|
179 | <td><a href="https://github.com/fluid-project/kettle/tree/master/lib/KettleMiddleware.js">Kettle built-in</a></td>
|
180 | <td>Adds <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS">CORS</a> headers to outgoing HTTP request to enable cross-domain access</td>
|
181 | <td><code>allowMethods {String}</code> (default <code>"GET"</code>), </br><code>origin {String}</code> (default <code>*</code>), </br> <code>credentials {Boolean}</code> (default <code>true</code>) </br>-
|
182 | see <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#The_HTTP_response_headers">CORS response headers</a></td>
|
183 | <td><code>{middlewareHolder}.CORS</code></td>
|
184 | </tr>
|
185 | <tr>
|
186 | <td><code>kettle.middleware.null</code></td>
|
187 | <td><a href="https://github.com/fluid-project/kettle/tree/master/lib/KettleMiddleware.js">Kettle built-in</a></td>
|
188 | <td>No-op middleware, useful for overriding and inactivating undesired middleware</td>
|
189 | <td>none</td>
|
190 | <td><code>{middlewareHolder}.null</code></td>
|
191 | </tr>
|
192 | </tbody>
|
193 | </table>
|
194 |
|
195 | </div>
|
196 |
|
197 | Middleware which it makes sense to share configuration application-wide is stored in a standard holder of grade `kettle.standardMiddleware` which is descended from the grade `kettle.middlewareHolder` – the
|
198 | context reference `{middlewareHolder}` is recommended for referring to this if required – e.g. `{middlewareHolder}.session`.
|
199 |
|
200 | #### Using the static middleware
|
201 |
|
202 | Here is an example of mounting a section of a module's filesystem path at a particular URL. In this case, we want to mount the `src` directory of our Infusion module at the global path `/infusion/`, a common
|
203 | enough requirement. Note that this is done by registering a *handler* just as with any other Kettle request handler, even though in this case the useful request handling function is actually achieved
|
204 | by the middleware. The only function of the request handler is to serve the 404 message in case the referenced file is not found in the mounted image – in this case, it can refer to the standard builtin handler
|
205 | named `kettle.request.notFoundHandler`. Note that the request handler must declare explicitly that it will handle all URLs under its prefix path by declaring a route of `/*` – this is different to the express
|
206 | model of routing and middleware handling. Kettle will not dispatch a request to a handler unless its route matches all of the incoming URL.
|
207 |
|
208 | Note that our static middleware can refer symbolically to the path of any module loaded using Infusion's module system
|
209 | [`fluid.module.register`](http://docs.fluidproject.org/infusion/development/NodeAPI.html#fluid-module-register-name-basedir-modulerequire-) by means of interpolated terms such as `%infusion`.
|
210 |
|
211 | Our config:
|
212 |
|
213 | ```json
|
214 | {
|
215 | "type": "examples.static.config",
|
216 | "options": {
|
217 | "gradeNames": ["fluid.component"],
|
218 | "components": {
|
219 | "server": {
|
220 | "type": "kettle.server",
|
221 | "options": {
|
222 | "port": 8081,
|
223 | "components": {
|
224 | "infusionStatic": {
|
225 | "type": "kettle.middleware.static",
|
226 | "options": {
|
227 | "root": "%infusion/src/"
|
228 | }
|
229 | },
|
230 | "app": {
|
231 | "type": "kettle.app",
|
232 | "options": {
|
233 | "requestHandlers": {
|
234 | "staticHandler": {
|
235 | "type": "examples.static.handler",
|
236 | "prefix": "/infusion",
|
237 | "route": "/*",
|
238 | "method": "get"
|
239 | }
|
240 | }
|
241 | }
|
242 | }
|
243 | }
|
244 | }
|
245 | }
|
246 | }
|
247 | }
|
248 | }
|
249 | ```
|
250 |
|
251 | Our handler:
|
252 |
|
253 | ```javascript
|
254 | fluid.defaults("examples.static.handler", {
|
255 | gradeNames: "kettle.request.http",
|
256 | requestMiddleware: {
|
257 | "static": {
|
258 | middleware: "{server}.infusionStatic"
|
259 | }
|
260 | },
|
261 | invokers: {
|
262 | handleRequest: {
|
263 | funcName: "kettle.request.notFoundHandler"
|
264 | }
|
265 | }
|
266 | });
|
267 |
|
268 | ``` |
\ | No newline at end of file |