UNPKG

20 kBMarkdownView Raw
1---
2title: Defining and Working With Middleware
3layout: default
4category: Kettle
5---
6# Defining and Working With Middleware
7
8## Working with middleware
9
10The most crucial structuring device in the expressjs (or wider pillarjs) community is known as ***[middleware](http://expressjs.com/guide/using-middleware.html)***.
11In its most basic form, a piece of middleware is simply a function with the following signature:
12
13 middleware(req, res, next)
14
15The 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).
17The element `next` is a callback provided
18by 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 –
20although only in terms of control flow since in general middleware has its effect as a result of side-effects on the
21request and response. In express, middleware are typically accumulated in arrays or groups of arrays by directives such
22as `app.use`. If a piece of middleware completes without error, it will invoke the `next` callback with no argument,
23which will signal that control should pass to the next middleware in the current sequence, or back to the framework if
24the sequence is at an end. Providing an argument to the callback `next` is intended to signal an error and the framework
25will then abort the middleware chain and propagate the argument, conventionally named `err`, to an error handler.
26This creates an analogy with executing
27[promise sequences](http://stackoverflow.com/questions/24586110/resolve-promises-one-after-another-i-e-in-sequence)
28which we will return to when we construct [middleware components](#defining-and-registering-middleware-components).
29
30In Kettle, middleware can be scheduled more flexibly than by simply being accumulated in arrays – the priority of a
31piece of middleware can be freely adjusted by assigning it a
32[Priority](http://docs.fluidproject.org/infusion/development/Priorities.html)
33as seen in many places in the Infusion framework, and so integrators can easily arrange for middleware to be inserted
34in arbitrary positions in already existing applications.
35
36Middleware is accumulated at two levels in a Kettle application – firstly, overall middleware is accumulated at the top
37level 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).
42The structure of these two options areas is the same, which we name `middlewareSequence`. When the request begins to be
43handled, 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
49If any of the middleware in this sequence signals an error, the entire sequence will be aborted and an error returned
50to the client.
51
52### Structure of entries in a `middlewareSequence`
53
54A `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
56records 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
108A piece of Kettle middleware is derived from grade `kettle.middleware`. This is a very simple grade which defines a
109single invoker named `handle` which accepts one argument, a `kettle.request`, and returns a
110promise representing the completion of the middleware. Conveniently a `fluid.promise` implementation is available in the
111framework, but you can return any variety of `thenable` that you please. Here is a skeleton,
112manually implemented middleware component:
113
114```javascript
115fluid.defaults("examples.customMiddleware", {
116 gradeNames: "kettle.middleware",
117 invokers: {
118 handle: "examples.customMiddleware.handle"
119 }
120});
121
122examples.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
137The framework makes it very easy to adapt any standard express middleware into a middleware component by means of the
138adaptor grade `kettle.plainMiddleware`. This accepts any standard express middleware as the option named `middleware`
139and from it fabricates a `handle` method with the semantic we just saw earlier. Any options that the middleware accepts
140can be forwarded to it from the component's options. Here is an example from the framework's own `json` middleware
141grade:
142
143```javascript
144kettle.npm.bodyParser = require("body-parser");
145
146fluid.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
153Consult the Infusion documentation on the [compact format for
154expanders](http://docs.fluidproject.org/infusion/development/ExpansionOfComponentOptions.html#compact-format-for-expanders)
155if you are unfamiliar with this syntax for designating elements in component options which arise from function calls.
156
157If 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
160acting on behalf of the currently incoming request. If the code for the middleware is under your control, it is
161recommended that wherever possible you use [dataSources][DataSources.md] for I/O since their callbacks automatically
162perform the necessary [request marking](DataSources.md#callback-wrapping-in-datasources).
163
164### Built-in standard middleware bundled with Kettle
165
166Here 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
168straightforward implementations in
169[KettleMiddleware.js](https://github.com/fluid-project/kettle/tree/master/lib/KettleMiddleware.js) for suggestions on
170how 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
262Middleware 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
268Here is an example of mounting a section of a module's filesystem path at a particular URL. In this case, we want to
269mount the `src` directory of our Infusion module at the global path `/infusion/`, a common enough requirement. Note
270that this is done by registering a *handler* just as with any other Kettle request handler, even though in this case
271the useful request handling function is actually achieved by the middleware. The only function of the request handler
272is to serve the 404 message in case the referenced file is not found in the mounted image – in this case, it can refer
273to the standard builtin handler named `kettle.request.notFoundHandler`. Note that the request handler must declare
274explicitly that it will handle all URLs under its prefix path by declaring a route of `/*` – this is different to the
275express model of routing and middleware handling. Kettle will not dispatch a request to a handler unless its route
276matches all of the incoming URL.
277
278Note 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-)
280by means of interpolated terms such as `%infusion`.
281
282Our 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
322Our handler:
323
324```javascript
325fluid.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
342This shows a single-file upload that allows image files only and saves them to disk storage; for more examples of
343possible usage, refer to the `kettle.tests.multer.config.json5` configuration file in `tests/configs`, the middleware
344source code, and the Multer documentation.
345
346Code for this example can be found in `/examples/multipartForm`.
347
348```javascript
349fluid.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
399And our corresponding handlers:
400
401```javascript
402fluid.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
414examples.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```