UNPKG

15.7 kBMarkdownView Raw
1---
2title: Defining and Working With Middleware
3layout: default
4category: Kettle
5---
6
7### Working with middleware
8
9The most crucial structuring device in the expressjs (or wider pillarjs) community is known as ***[middleware](http://expressjs.com/guide/using-middleware.html)***.
10In its most basic form, a piece of middleware is simply a function with the following signature:
11
12 middleware(req, res, next)
13
14The 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
15by 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 –
16although 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
17by 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
18current 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
19and 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
22In 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)
23as 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
25Middleware 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
27can 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).
28The structure of these two options areas is the same, which we name `middlewareSequence`. When the request begins to be handled, the framework
29will 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
35If 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
39A `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
40records 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
79A 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
80promise 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,
81manually implemented middleware component:
82
83```javascript
84fluid.defaults("examples.customMiddleware", {
85 gradeNames: "kettle.middleware",
86 invokers: {
87 handle: "examples.customMiddleware.handle"
88 }
89});
90
91examples.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
106The 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
107middleware 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
108the component's options. Here is an example from the framework's own `json` middleware grade:
109
110```javascript
111kettle.npm.bodyParser = require("body-parser");
112
113fluid.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
120Consult the Infusion documentation on the [compact format for expanders](http://docs.fluidproject.org/infusion/development/ExpansionOfComponentOptions.html#compact-format-for-expanders) if you
121are unfamiliar with this syntax for designating elements in component options which arise from function calls.
122
123If your middleware may act asynchronously by performing some raw I/O, you must use the grade `kettle.plainAsyncMiddleware`
124instead. This is to ensure that the Kettle [request component](RequestHandlersAndApps.md#request-components)
125is unmarked during the period that the system is not acting on behalf of the currently incoming request. If the code
126for the middleware is under your control, it is recommended that wherever possible you use [dataSources][DataSources.md]
127for 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
132Here 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)
133communities. You can consult the straightforward implementations in [KettleMiddleware.js](https://github.com/fluid-project/kettle/tree/master/lib/KettleMiddleware.js) for suggestions for how
134to 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
197Middleware 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
198context reference `{middlewareHolder}` is recommended for referring to this if required – e.g. `{middlewareHolder}.session`.
199
200#### Using the static middleware
201
202Here 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
203enough 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
204by 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
205named `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
206model of routing and middleware handling. Kettle will not dispatch a request to a handler unless its route matches all of the incoming URL.
207
208Note 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
211Our 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
251Our handler:
252
253```javascript
254fluid.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