1 | ---
|
2 | title: Defining and Working With Middleware
|
3 | layout: default
|
4 | category: Kettle
|
5 | ---
|
6 | # Defining and Working With Middleware
|
7 |
|
8 | ## Working with middleware
|
9 |
|
10 | The most crucial structuring device in the expressjs (or wider pillarjs) community is known as ***[middleware](http://expressjs.com/guide/using-middleware.html)***.
|
11 | In its most basic form, a piece of middleware is simply a function with the following signature:
|
12 |
|
13 | middleware(req, res, next)
|
14 |
|
15 | The elements `req` and `res` have been described in the section on
|
16 | [request components](RequestHandlersAndApps.md#members-defined-by-the-kettle-framework-at-top-level-on-a-request-component).
|
17 | The element `next` is a callback provided
|
18 | by the framework to be invoked when the middleware has completed its task. This could be seen as a form of
|
19 | [continuation passing style](https://en.wikipedia.org/wiki/Continuation-passing_style) with 0 arguments –
|
20 | although only in terms of control flow since in general middleware has its effect as a result of side-effects on the
|
21 | request and response. In express, middleware are typically accumulated in arrays or groups of arrays by directives such
|
22 | as `app.use`. If a piece of middleware completes without error, it will invoke the `next` callback with no argument,
|
23 | which will signal that control should pass to the next middleware in the current sequence, or back to the framework if
|
24 | the sequence is at an end. Providing an argument to the callback `next` is intended to signal an error and the framework
|
25 | will then abort the middleware chain and propagate the argument, conventionally named `err`, to an error handler.
|
26 | This creates an analogy with executing
|
27 | [promise sequences](http://stackoverflow.com/questions/24586110/resolve-promises-one-after-another-i-e-in-sequence)
|
28 | which we will return to when we construct [middleware components](#defining-and-registering-middleware-components).
|
29 |
|
30 | In Kettle, middleware can be scheduled more flexibly than by simply being accumulated in arrays – the priority of a
|
31 | piece of middleware can be freely adjusted by assigning it a
|
32 | [Priority](http://docs.fluidproject.org/infusion/development/Priorities.html)
|
33 | as seen in many places in the Infusion framework, and so integrators can easily arrange for middleware to be inserted
|
34 | in arbitrary positions in already existing applications.
|
35 |
|
36 | Middleware is accumulated at two levels in a Kettle application – firstly, overall middleware is accumulated at the top
|
37 | level of a `kettle.server` in an option named `rootMiddleware`. This is analogous to express
|
38 | [app-level middleware](http://expressjs.com/guide/using-middleware.html#middleware.application) registered with
|
39 | `app.use`. Secondly, individual request middleware can be attached to an individual `kettle.request` in its options at
|
40 | `requestMiddleware`. This is analogous to express
|
41 | [router-level middleware](http://expressjs.com/guide/using-middleware.html#middleware.router).
|
42 | The structure of these two options areas is the same, which we name `middlewareSequence`. When the request begins to be
|
43 | handled, the framework will execute the following in sequence:
|
44 |
|
45 | * The root middleware attached to the `kettle.server`
|
46 | * The request middleware attached to the resolved `kettle.request` component
|
47 | * The actual request handler designated by the request component's invoker `handleRequest`
|
48 |
|
49 | If any of the middleware in this sequence signals an error, the entire sequence will be aborted and an error returned
|
50 | to the client.
|
51 |
|
52 | ### Structure of entries in a `middlewareSequence`
|
53 |
|
54 | A `middlewareSequence` is a free hash of keys, considered as **namespaces** for the purpose of resolving
|
55 | [Priorities](http://docs.fluidproject.org/infusion/development/Priorities.html) onto
|
56 | records of type `middlewareEntry`:
|
57 |
|
58 | ```snippet
|
59 | {
|
60 | <middlewareKey> : <middlewareEntry>,
|
61 | <middlewareKey> : <middlewareEntry>,
|
62 | ...
|
63 | }
|
64 | ```
|
65 |
|
66 | <table>
|
67 | <thead>
|
68 | <tr>
|
69 | <th colspan="3">Members of a <code>middlewareEntry</code> entry within the <code>middlewareSequence</code>
|
70 | block of a component (<code>rootMiddleware</code> for <code>kettle.server</code> or
|
71 | <code>requestMiddleware</code> for <code>kettle.request</code>)
|
72 | </th>
|
73 | </tr>
|
74 | <tr>
|
75 | <th>Member</th>
|
76 | <th>Type</th>
|
77 | <th>Description</th>
|
78 | </tr>
|
79 | </thead>
|
80 | <tbody>
|
81 | <tr>
|
82 | <td><code>middleware</code></td>
|
83 | <td><code>String</code>
|
84 | (<a href="http://docs.fluidproject.org/infusion/development/IoCReferences.html">IoC Reference</a>)
|
85 | </td>
|
86 | <td>An IoC reference to the middleware component which should be inserted into the handler sequence. Often
|
87 | this will be qualified by the context <code>{middlewareHolder}</code>
|
88 | e.g. <code>{middlewareHolder}.session</code> – to reference the core middleware collection attached to
|
89 | the <code>kettle.server</code> but middleware could be resolved from anywhere visible in the component
|
90 | tree. This should be a reference to a component descended from the grade <code>kettle.middleware</code>
|
91 | </td>
|
92 | </tr>
|
93 | <tr>
|
94 | <td><code>priority</code> (optional)</td>
|
95 | <td><code>String</code>
|
96 | (<a href="http://docs.fluidproject.org/infusion/development/Priorities.html">Priority</a>)
|
97 | </td>
|
98 | <td>An encoding of a priority relative to some other piece of middleware within the same group will
|
99 | typically be <code>before:middlewareKey</code> or <code>after:middlewareKey</code> for the
|
100 | <code>middlewareKey</code> of some other entry in the group
|
101 | </td>
|
102 | </tr>
|
103 | </tbody>
|
104 | </table>
|
105 |
|
106 | ### Defining and registering middleware components
|
107 |
|
108 | A piece of Kettle middleware is derived from grade `kettle.middleware`. This is a very simple grade which defines a
|
109 | single invoker named `handle` which accepts one argument, a `kettle.request`, and returns a
|
110 | promise representing the completion of the middleware. Conveniently a `fluid.promise` implementation is available in the
|
111 | framework, but you can return any variety of `thenable` that you please. Here is a skeleton,
|
112 | manually implemented middleware component:
|
113 |
|
114 | ```javascript
|
115 | fluid.defaults("examples.customMiddleware", {
|
116 | gradeNames: "kettle.middleware",
|
117 | invokers: {
|
118 | handle: "examples.customMiddleware.handle"
|
119 | }
|
120 | });
|
121 |
|
122 | examples.customMiddleware.handle = function (request) {
|
123 | var togo = fluid.promise();
|
124 | if (request.req.params.id === 42) {
|
125 | togo.resolve();
|
126 | } else {
|
127 | togo.reject({
|
128 | isError: true,
|
129 | statusCode: 401,
|
130 | message: "Only the id 42 is authorised"
|
131 | });
|
132 | }
|
133 | return togo;
|
134 | };
|
135 | ```
|
136 |
|
137 | The framework makes it very easy to adapt any standard express middleware into a middleware component by means of the
|
138 | adaptor grade `kettle.plainMiddleware`. This accepts any standard express middleware as the option named `middleware`
|
139 | and from it fabricates a `handle` method with the semantic we just saw earlier. Any options that the middleware accepts
|
140 | can be forwarded to it from the component's options. Here is an example from the framework's own `json` middleware
|
141 | grade:
|
142 |
|
143 | ```javascript
|
144 | kettle.npm.bodyParser = require("body-parser");
|
145 |
|
146 | fluid.defaults("kettle.middleware.json", {
|
147 | gradeNames: ["kettle.plainMiddleware"],
|
148 | middlewareOptions: {}, // see https://github.com/expressjs/body-parser#bodyparserjsonoptions
|
149 | middleware: "@expand:kettle.npm.bodyParser.json({that}.options.middlewareOptions)"
|
150 | });
|
151 | ```
|
152 |
|
153 | Consult the Infusion documentation on the [compact format for
|
154 | expanders](http://docs.fluidproject.org/infusion/development/ExpansionOfComponentOptions.html#compact-format-for-expanders)
|
155 | if you are unfamiliar with this syntax for designating elements in component options which arise from function calls.
|
156 |
|
157 | If your middleware may act asynchronously by performing some raw I/O, you must use the grade
|
158 | `kettle.plainAsyncMiddleware` instead. This is to ensure that the Kettle
|
159 | [request component](RequestHandlersAndApps.md#request-components) is unmarked during the period that the system is not
|
160 | acting on behalf of the currently incoming request. If the code for the middleware is under your control, it is
|
161 | recommended that wherever possible you use [dataSources][DataSources.md] for I/O since their callbacks automatically
|
162 | perform the necessary [request marking](DataSources.md#callback-wrapping-in-datasources).
|
163 |
|
164 | ### Built-in standard middleware bundled with Kettle
|
165 |
|
166 | Here we describe the built-in middleware supplied with Kettle, which is mostly sourced from standard middleware in the
|
167 | [express](http://expressjs.com/) and [pillarjs](https://github.com/pillarjs) communities. You can consult the
|
168 | straightforward implementations in
|
169 | [KettleMiddleware.js](https://github.com/fluid-project/kettle/tree/master/lib/KettleMiddleware.js) for suggestions on
|
170 | how to implement your own.
|
171 |
|
172 | <div style="font-size: smaller">
|
173 | <table>
|
174 | <thead>
|
175 | <tr><th>Grade name</th><th>Middleware name</th><th>Description</th><th>Accepted options</th><th>Standard IoC Path</th></tr>
|
176 | </thead>
|
177 | <tbody>
|
178 | <tr>
|
179 | <td><code>kettle.middleware.json</code></td>
|
180 | <td><a href="https://github.com/expressjs/body-parser">expressjs/body-parser</a></td>
|
181 | <td>Parses JSON request bodies, possibly with compression.</td>
|
182 | <td><code>middlewareOptions</code>, forwarded to
|
183 | <a href="https://github.com/expressjs/body-parser#bodyparserjsonoptions"<code>bodyParser.json(options)</code></a>
|
184 | </td>
|
185 | <td><code>{middlewareHolder}.json</code></td>
|
186 | </tr>
|
187 | <tr>
|
188 | <td><code>kettle.middleware.urlencoded</code></td>
|
189 | <td><a href="https://github.com/expressjs/body-parser">expressjs/body-parser</a></td>
|
190 | <td>Applies URL decoding to a submitted request body</td>
|
191 | <td><code>middlewareOptions</code>, forwarded to
|
192 | <a href="https://github.com/expressjs/body-parser#bodyparserurlencodedoptions"><code>bodyParser.urlencoded(options)</code></a>
|
193 | </td>
|
194 | <td><code>{middlewareHolder}.urlencoded</code></td>
|
195 | </tr>
|
196 | <tr>
|
197 | <td><code>kettle.middleware.cookieParser</code></td>
|
198 | <td><a href="https://github.com/expressjs/cookie-parser">expressjs/cookie-parser</a></td>
|
199 | <td>Parses the <code>Cookie</code> header as well as signed cookies via <code>req.secret</code>.</td>
|
200 | <td><code>secret</code> and <code>middlewareOptions</code>, forwarded to the two arguments of
|
201 | <a href="https://github.com/expressjs/cookie-parser#cookieparsersecret-options"><code>cookieParser(secret, options)</code></a>
|
202 | </td>
|
203 | <td>none</td>
|
204 | </tr>
|
205 | <tr>
|
206 | <td><code>kettle.middleware.multer</code></td>
|
207 | <td><a href="https://github.com/expressjs/multer">expressjs/multer</a></td>
|
208 | <td>Handles <code>multipart/form-data</code>, primarily for file uploading.</td>
|
209 | <td><code>middlewareOptions</code>, forwarded to <code>multer(options)</code>, and <code>formFieldOptions</code>,
|
210 | used to configure the field parameters for uploaded files as described in
|
211 | <a href="https://github.com/expressjs/multer#usage">multer's documentation</a>. <strong>Note</strong>: Multer's
|
212 | storage and fileFilter <code>multer</code> options require functions as their values, and are implemented in
|
213 | Kettle using <code>subcomponents</code>; see the documentation below on using
|
214 | <code>kettle.middleware.multer</code> for more details.
|
215 | </td>
|
216 | <td>none – user must configure on each use</td>
|
217 | </tr>
|
218 | <tr>
|
219 | <td><code>kettle.middleware.session</code></td>
|
220 | <td><a href="https://github.com/expressjs/session">expressjs/session</a></td>
|
221 | <td>Stores and retrieves <code>req.session</code> from various backends</td>
|
222 | <td><code>middlewareOptions</code>, forwarded to
|
223 | <a href="https://github.com/expressjs/session#sessionoptions"><code>session(options)</code></a>
|
224 | </td>
|
225 | <td><code>{middlewareHolder}.session</code> when using <code>kettle.server.sessionAware</code> server</td>
|
226 | </tr>
|
227 | <tr>
|
228 | <td><code>kettle.middleware.static</code></td>
|
229 | <td><a href="https://github.com/expressjs/serve-static">expressjs/serve-static</a></td>
|
230 | <td>Serves static content from the filesystem</td>
|
231 | <td><code>root</code> and <code>middlewareOptions</code>, forwarded to the two arguments of
|
232 | <a href="https://github.com/expressjs/serve-static#servestaticroot-options">
|
233 | <code>serveStatic(root, options)</code></a>
|
234 | </td>
|
235 | <td>none – user must configure on each use</td>
|
236 | </tr>
|
237 | <tr>
|
238 | <td><code>kettle.middleware.CORS</code></td>
|
239 | <td><a href="https://github.com/fluid-project/kettle/tree/master/lib/KettleMiddleware.js">Kettle built-in</a></td>
|
240 | <td>Adds <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS">CORS</a> headers to
|
241 | outgoing HTTP request to enable cross-domain access
|
242 | </td>
|
243 | <td><code>allowMethods {String}</code> (default <code>"GET"</code>), </br><code>origin {String}</code>
|
244 | (default <code>*</code>), </br> <code>credentials {Boolean}</code> (default <code>true</code>) </br> - see
|
245 | <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#The_HTTP_response_headers">CORS
|
246 | response headers</a>
|
247 | </td>
|
248 | <td><code>{middlewareHolder}.CORS</code></td>
|
249 | </tr>
|
250 | <tr>
|
251 | <td><code>kettle.middleware.null</code></td>
|
252 | <td><a href="https://github.com/fluid-project/kettle/tree/master/lib/KettleMiddleware.js">Kettle built-in</a></td>
|
253 | <td>No-op middleware, useful for overriding and inactivating undesired middleware</td>
|
254 | <td>none</td>
|
255 | <td><code>{middlewareHolder}.null</code></td>
|
256 | </tr>
|
257 | </tbody>
|
258 | </table>
|
259 |
|
260 | </div>
|
261 |
|
262 | Middleware which it makes sense to share configuration application-wide is stored in a standard holder of grade
|
263 | `kettle.standardMiddleware` which is descended from the grade `kettle.middlewareHolder` – the context reference
|
264 | `{middlewareHolder}` is recommended for referring to this if required – e.g. `{middlewareHolder}.session`.
|
265 |
|
266 | #### Using the static middleware
|
267 |
|
268 | Here is an example of mounting a section of a module's filesystem path at a particular URL. In this case, we want to
|
269 | mount the `src` directory of our Infusion module at the global path `/infusion/`, a common enough requirement. Note
|
270 | that this is done by registering a *handler* just as with any other Kettle request handler, even though in this case
|
271 | the useful request handling function is actually achieved by the middleware. The only function of the request handler
|
272 | is to serve the 404 message in case the referenced file is not found in the mounted image – in this case, it can refer
|
273 | to the standard builtin handler named `kettle.request.notFoundHandler`. Note that the request handler must declare
|
274 | explicitly that it will handle all URLs under its prefix path by declaring a route of `/*` – this is different to the
|
275 | express model of routing and middleware handling. Kettle will not dispatch a request to a handler unless its route
|
276 | matches all of the incoming URL.
|
277 |
|
278 | Note that our static middleware can refer symbolically to the path of any module loaded using Infusion's module system
|
279 | [`fluid.module.register`](http://docs.fluidproject.org/infusion/development/NodeAPI.html#fluid-module-register-name-basedir-modulerequire-)
|
280 | by means of interpolated terms such as `%infusion`.
|
281 |
|
282 | Our config:
|
283 |
|
284 | ```json
|
285 | {
|
286 | "type": "examples.static.config",
|
287 | "options": {
|
288 | "gradeNames": ["fluid.component"],
|
289 | "components": {
|
290 | "server": {
|
291 | "type": "kettle.server",
|
292 | "options": {
|
293 | "port": 8081,
|
294 | "components": {
|
295 | "infusionStatic": {
|
296 | "type": "kettle.middleware.static",
|
297 | "options": {
|
298 | "root": "%infusion/src/"
|
299 | }
|
300 | },
|
301 | "app": {
|
302 | "type": "kettle.app",
|
303 | "options": {
|
304 | "requestHandlers": {
|
305 | "staticHandler": {
|
306 | "type": "examples.static.handler",
|
307 | "prefix": "/infusion",
|
308 | "route": "/*",
|
309 | "method": "get"
|
310 | }
|
311 | }
|
312 | }
|
313 | }
|
314 | }
|
315 | }
|
316 | }
|
317 | }
|
318 | }
|
319 | }
|
320 | ```
|
321 |
|
322 | Our handler:
|
323 |
|
324 | ```javascript
|
325 | fluid.defaults("examples.static.handler", {
|
326 | gradeNames: "kettle.request.http",
|
327 | requestMiddleware: {
|
328 | "static": {
|
329 | middleware: "{server}.infusionStatic"
|
330 | }
|
331 | },
|
332 | invokers: {
|
333 | handleRequest: {
|
334 | funcName: "kettle.request.notFoundHandler"
|
335 | }
|
336 | }
|
337 | });
|
338 | ```
|
339 |
|
340 | #### Using the multer middleware
|
341 |
|
342 | This shows a single-file upload that allows image files only and saves them to disk storage; for more examples of
|
343 | possible usage, refer to the `kettle.tests.multer.config.json5` configuration file in `tests/configs`, the middleware
|
344 | source code, and the Multer documentation.
|
345 |
|
346 | Code for this example can be found in `/examples/multipartForm`.
|
347 |
|
348 | ```javascript
|
349 | fluid.defaults("examples.uploadConfig", {
|
350 | "gradeNames": ["fluid.component"],
|
351 | "components": {
|
352 | "server": {
|
353 | "type": "kettle.server",
|
354 | "options": {
|
355 | "port": 8081,
|
356 | "components": {
|
357 | "imageUpload": {
|
358 | "type": "kettle.middleware.multer",
|
359 | "options": {
|
360 | "formFieldOptions": {
|
361 | "method": "single",
|
362 | "fieldName": "image"
|
363 | },
|
364 | "components": {
|
365 | "storage": {
|
366 | "type": "kettle.middleware.multer.storage.disk",
|
367 | "options": {
|
368 | "destination": "./examples/multipartForm/uploads"
|
369 | }
|
370 | },
|
371 | "fileFilter": {
|
372 | "type": "kettle.middleware.multer.filter.mimeType",
|
373 | "options": {
|
374 | "acceptedMimeTypes": ["image/png", "image/jpg", "image/gif"]
|
375 | }
|
376 | }
|
377 | }
|
378 | }
|
379 | },
|
380 | "app": {
|
381 | "type": "kettle.app",
|
382 | "options": {
|
383 | "requestHandlers": {
|
384 | "imageUploadHandler": {
|
385 | "type": "examples.uploadConfig.handler",
|
386 | "route": "/upload",
|
387 | "method": "post"
|
388 | }
|
389 | }
|
390 | }
|
391 | }
|
392 | }
|
393 | }
|
394 | }
|
395 | }
|
396 | });
|
397 | ```
|
398 |
|
399 | And our corresponding handlers:
|
400 |
|
401 | ```javascript
|
402 | fluid.defaults("examples.uploadConfig.handler", {
|
403 | gradeNames: "kettle.request.http",
|
404 | requestMiddleware: {
|
405 | imageUpload: {
|
406 | middleware: "{server}.imageUpload"
|
407 | }
|
408 | },
|
409 | invokers: {
|
410 | handleRequest: "examples.uploadConfig.handleRequest"
|
411 | }
|
412 | });
|
413 |
|
414 | examples.uploadConfig.handleRequest = function (request) {
|
415 | var uploadedFileDetails = request.req.file;
|
416 | request.events.onSuccess.fire({
|
417 | message: fluid.stringTemplate("POST request received on path /upload; "
|
418 | + "file %originalName uploaded to %uploadedPath",
|
419 | {originalName: uploadedFileDetails.originalname, uploadedPath: uploadedFileDetails.path})
|
420 | });
|
421 | };
|
422 | ```
|