title | layout | category |
---|---|---|
Defining and Working With Middleware |
default |
Kettle |
The most crucial structuring device in the expressjs (or wider pillarjs) community is known as middleware. In its most basic form, a piece of middleware is simply a function with the following signature:
middleware(req, res, next)
The elements req
and res
have been described in the section on request components. The element next
is a callback provided
by the framework to be invoked when the middleware has completed its task. This could be seen as a form of continuation passing style with 0 arguments –
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
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
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
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
promise sequences which we will return to when we construct middleware components.
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 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.
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
app-level middleware registered with app.use
. Secondly, individual request middleware
can be attached to an individual kettle.request
in its options at requestMiddleware
. This is analogous to express router-level middleware.
The structure of these two options areas is the same, which we name middlewareSequence
. When the request begins to be handled, the framework
will execute the following in sequence:
- The root middleware attached to the
kettle.server
- The request middleware attached to the resolved
kettle.request
component - The actual request handler designated by the request component's invoker
handleRequest
If any of the middleware in this sequence signals an error, the entire sequence will be aborted and an error returned to the client.
A middlewareSequence
is a free hash of keys, considered as namespaces for the purpose of resolving Priorities onto
records of type middlewareEntry
:
{
<middlewareKey> : <middlewareEntry>,
<middlewareKey> : <middlewareEntry>,
...
}
Members of a middlewareEntry entry within the middlewareSequence block of a component (rootMiddleware for kettle.server or requestMiddleware for kettle.request ) |
||
---|---|---|
Member | Type | Description |
middleware |
String (IoC Reference) |
An IoC reference to the middleware component which should be inserted into the handler sequence. Often this will be qualified by the context {middlewareHolder} – e.g. {middlewareHolder}.session – to reference the core
middleware collection attached to the kettle.server but middleware could be resolved from anywhere visible in the component tree. This should be a reference to a component descended from the grade kettle.middleware |
priority (optional) |
String (Priority) |
An encoding of a priority relative to some other piece of middleware within the same group – will typically be before:middlewareKey or after:middlewareKey for the middlewareKey of some
other entry in the group |
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
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,
manually implemented middleware component:
fluid.defaults("examples.customMiddleware", {
gradeNames: "kettle.middleware",
invokers: {
handle: "examples.customMiddleware.handle"
}
});
examples.customMiddleware.handle = function (request) {
var togo = fluid.promise();
if (request.req.params.id === 42) {
togo.resolve();
} else {
togo.reject({
isError: true,
statusCode: 401,
message: "Only the id 42 is authorised"
});
}
return togo;
};
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
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
the component's options. Here is an example from the framework's own json
middleware grade:
kettle.npm.bodyParser = require("body-parser");
fluid.defaults("kettle.middleware.json", {
gradeNames: ["kettle.plainMiddleware"],
middlewareOptions: {}, // see https://github.com/expressjs/body-parser#bodyparserjsonoptions
middleware: "@expand:kettle.npm.bodyParser.json({that}.options.middlewareOptions)"
});
Consult the Infusion documentation on the compact format for expanders if you are unfamiliar with this syntax for designating elements in component options which arise from function calls.
Here we describe the built-in middleware supplied with Kettle, which is mostly sourced from standard middleware in the express and pillarjs communities. You can consult the straightforward implementations in KettleMiddleware.js for suggestions for how to implement your own.
Grade name | Middleware name | Description | Accepted options | Standard IoC Path |
---|---|---|---|---|
kettle.middleware.json |
expressjs/body-parser | Parses JSON request bodies, possibly with compression. | middlewareOptions , forwarded to bodyParser.json(options) |
{middlewareHolder}.json |
kettle.middleware.urlencoded |
expressjs/body-parser | Applies URL decoding to a submitted request body | middlewareOptions , forwarded to bodyParser.urlencoded(options) |
{middlewareHolder}.urlencoded |
kettle.middleware.cookieParser |
expressjs/cookie-parser | Parses the Cookie header as well as signed cookies via req.secret . |
secret and middlewareOptions , forwarded to the two arguments of cookieParser(secret, options) |
none |
kettle.middleware.session |
expressjs/session | Stores and retrieves req.session from various backends |
middlewareOptions , forwarded to session(options) |
{middlewareHolder}.session when using kettle.server.sessionAware server |
kettle.middleware.static |
expressjs/serve-static | Serves static content from the filesystem | root and middlewareOptions , forwarded to the two arguments of serveStatic(root, options) |
none – user must configure on each use |
kettle.middleware.CORS |
Kettle built-in | Adds CORS headers to outgoing HTTP request to enable cross-domain access | allowMethods {String} (default "GET" ), origin {String} (default * ), credentials {Boolean} (default true ) - see CORS response headers |
{middlewareHolder}.CORS |
kettle.middleware.null |
Kettle built-in | No-op middleware, useful for overriding and inactivating undesired middleware | none | {middlewareHolder}.null |
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
context reference {middlewareHolder}
is recommended for referring to this if required – e.g. {middlewareHolder}.session
.
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
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
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
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
model of routing and middleware handling. Kettle will not dispatch a request to a handler unless its route matches all of the incoming URL.
Note that our static middleware can refer symbolically to the path of any module loaded using Infusion's module system
fluid.module.register
by means of interpolated terms such as %infusion
.
Our config:
{
"type": "examples.static.config",
"options": {
"gradeNames": ["fluid.component"],
"components": {
"server": {
"type": "kettle.server",
"options": {
"port": 8081,
"components": {
"infusionStatic": {
"type": "kettle.middleware.static",
"options": {
"root": "%infusion/src/"
}
},
"app": {
"type": "kettle.app",
"options": {
"requestHandlers": {
"staticHandler": {
"type": "examples.static.handler",
"prefix": "/infusion",
"route": "/*",
"method": "get"
}
}
}
}
}
}
}
}
}
}
Our handler:
fluid.defaults("examples.static.handler", {
gradeNames: "kettle.request.http",
requestMiddleware: {
"static": {
middleware: "{server}.infusionStatic"
}
},
invokers: {
handleRequest: {
funcName: "kettle.request.notFoundHandler"
}
}
});