UNPKG

9.08 kBJavaScriptView Raw
1/*!
2Kettle wrapping for Express Middleware
3
4Copyright 2012-2013 OCAD University
5Copyright 2015 Raising the Floor (International)
6
7Licensed under the New BSD license. You may not use this file except in
8compliance with this License.
9
10You may obtain a copy of the License at
11https://github.com/fluid-project/kettle/blob/master/LICENSE.txt
12*/
13
14"use strict";
15
16var fluid = require("infusion"),
17 kettle = fluid.registerNamespace("kettle");
18
19// TODO: Provide a more declarative scheme for npm modules that also respects version information
20fluid.require("body-parser", require, "kettle.npm.bodyParser");
21fluid.require("cookie-parser", require, "kettle.npm.cookieParser");
22fluid.require("serve-static", require, "kettle.npm.serveStatic");
23
24fluid.defaults("kettle.middlewareHolder", {
25 gradeNames: "fluid.component"
26});
27
28fluid.defaults("kettle.standardMiddleware", {
29 gradeNames: "kettle.middlewareHolder",
30 components: {
31 urlencoded: {
32 type: "kettle.middleware.urlencoded"
33 },
34 json: {
35 type: "kettle.middleware.json"
36 },
37 CORS: {
38 type: "kettle.middleware.CORS"
39 },
40 "null": {
41 type: "kettle.middleware.null"
42 },
43 mismatch: {
44 type: "kettle.middleware.mismatch"
45 }
46 }
47});
48
49fluid.registerNamespace("kettle.middleware");
50
51kettle.middleware.resolveSequence = function (that, name, middlewareSpec) {
52 var middlewares = fluid.hashToArray(middlewareSpec, "namespace", function (newEl, el) {
53 newEl.component = fluid.expandOptions(el.middleware, that);
54 if (!fluid.isComponent(newEl.component) || !fluid.componentHasGrade(newEl.component, "kettle.middleware")) {
55 fluid.fail("Couldn't resolve reference " + el.middleware + " from member \"middleware\" of record ", el, " to a middleware component for a " + name + ": got ", newEl.component);
56 }
57 newEl.priority = fluid.parsePriority(el.priority, 0, false, name);
58 });
59 fluid.sortByPriority(middlewares);
60 return middlewares;
61};
62
63kettle.middleware.getHandlerSequence = function (that, memberName) {
64 var resolved = kettle.middleware.resolveSequence(that, that.typeName + " " + memberName + " entry", that.options[memberName]);
65 var sequence = fluid.getMembers(resolved, "component.handle");
66 return sequence;
67};
68
69// The base middleware grade defines a function accepting the request object and returning a promise
70fluid.defaults("kettle.middleware", {
71 gradeNames: ["fluid.component"],
72 invokers: {
73 handle: {
74 funcName: "fluid.notImplemented"
75 }
76 }
77});
78
79// A grade which accepts a standard piece of express middleware mounted at option `middleware`
80fluid.defaults("kettle.plainMiddleware", {
81 gradeNames: "kettle.middleware",
82 asyncMiddleware: false,
83 invokers: {
84 handle: {
85 funcName: "kettle.plainMiddleware.resolve",
86 args: ["{that}", "{arguments}.0", "{plainMiddleware}.options.asyncMiddleware"]
87 }
88 }
89});
90
91// A grade which accepts a standard piece of express middleware which may operate asynchronously.
92// This is an awkward requirement found from KETTLE-57 and our "request marking". We would like the maximum
93// amount of time owned by a request covered by its marker, but we can't arrange to cover, e.g. the serve-static
94// middleware since it may do I/O unpredictably in an uninstrumentable way.
95fluid.defaults("kettle.plainAsyncMiddleware", {
96 gradeNames: "kettle.plainMiddleware",
97 asyncMiddleware: true
98});
99
100// Definition of the no-op middleware named "null", useful for overriding middleware cleanly
101kettle.middleware.nullHandler = function () {
102 return fluid.promise().resolve();
103};
104
105fluid.defaults("kettle.middleware.null", {
106 gradeNames: "kettle.plainMiddleware",
107 invokers: {
108 handle: {
109 funcName: "kettle.middleware.nullHandler"
110 }
111 }
112});
113
114kettle.middleware.toPromise = function (middleware, request, async) {
115 var togo = fluid.promise();
116 if (async) {
117 kettle.request.clear();
118 }
119 middleware(request.req, request.res, function (err) {
120 if (async) {
121 kettle.markActiveRequest(request);
122 }
123 err ? togo.reject(err) : togo.resolve();
124 });
125 return togo;
126};
127
128kettle.plainMiddleware.resolve = function (that, request, async) {
129 var middleware = that.options.middleware;
130 if (typeof(middleware) !== "function") {
131 fluid.fail("Middleware component ", that, " with type " + that.typeName + " is improperly configured - an option named \"middleware\" of function type is required - got ", middleware);
132 }
133 return kettle.middleware.toPromise(middleware, request, async);
134};
135
136fluid.defaults("kettle.middleware.json", {
137 gradeNames: ["kettle.plainMiddleware"],
138 middlewareOptions: {}, // see https://github.com/expressjs/body-parser#bodyparserjsonoptions
139 middleware: "@expand:kettle.npm.bodyParser.json({that}.options.middlewareOptions)"
140});
141
142fluid.defaults("kettle.middleware.urlencoded", {
143 gradeNames: ["kettle.plainMiddleware"],
144 middlewareOptions: {
145 extended: true
146 }, // see https://github.com/expressjs/body-parser#bodyparserurlencodedoptions
147 middleware: "@expand:kettle.npm.bodyParser.urlencoded({that}.options.middlewareOptions)"
148});
149
150fluid.defaults("kettle.middleware.CORS", {
151 gradeNames: ["kettle.middleware"],
152 allowMethods: "GET",
153 // origin can be a "*" (all domains are allowed) or an array of allowed
154 // domains (including ports).
155 // Docs: https://developer.mozilla.org/en/docs/HTTP/Access_control_CORS#Access-Control-Allow-Origin
156 // TODO: serious security risk here
157 origin: "*",
158 // This is a flag that enables the response exposure to CORS requests with credentials.
159 // Docs: https://developer.mozilla.org/en/docs/HTTP/Access_control_CORS#Access-Control-Allow-Credentials
160 credentials: true,
161 invokers: {
162 handle: {
163 funcName: "kettle.middleware.CORSHandle",
164 args: [
165 "{arguments}.0",
166 "{that}.options.allowMethods",
167 "{that}.options.origin",
168 "{that}.options.credentials"
169 ]
170 }
171 }
172});
173
174/**
175 * A middleware responsible for enabling CORS within the kettle server.
176 * @param {Object} request a request object.
177 * @param {String|Array} allowMethods methods that are enabled with CORS.
178 * @param {String|Array} origin domains that are allowed.
179 * @param {String} credentials response exposure flag.
180 */
181kettle.middleware.CORSHandle = function (request, allowMethods, origin, credentials) {
182 var res = request.res,
183 req = request.req,
184 reqOrigin = req.headers.origin;
185 // Handle a preflight OPTIONS request as well.
186 allowMethods = fluid.makeArray(allowMethods).concat(["OPTIONS", "PUT", "POST"]);
187
188 // Add CORS response headers.
189 res.header("Access-Control-Allow-Origin",
190 origin === "*" || origin.indexOf(reqOrigin) > -1 ? reqOrigin : "null");
191 res.header("Access-Control-Allow-Credentials", credentials);
192 res.header("Access-Control-Allow-Methods", allowMethods.join(","));
193 res.header("Access-Control-Allow-Headers", "X-Requested-With,Content-Type");
194
195 if (req.method === "OPTIONS") {
196 res.sendStatus(204);
197 }
198 return fluid.promise().resolve();
199};
200
201// Middleware which checks for a request type mismatch error and reports it
202fluid.defaults("kettle.middleware.mismatch", {
203 gradeNames: "kettle.middleware",
204 invokers: {
205 handle: "kettle.middleware.mismatch.handle"
206 }
207});
208
209kettle.middleware.mismatch.handle = function (request) {
210 if (request.req.mismatchError) {
211 return fluid.promise().reject(request.req.mismatchError);
212 }
213};
214
215fluid.defaults("kettle.middleware.cookieParser", {
216 gradeNames: ["kettle.plainMiddleware"],
217 secret: null,
218 middlewareOptions: {}, // https://github.com/expressjs/cookie-parser#cookieparsersecret-options
219 middleware: "@expand:kettle.npm.cookieParser.json({that}.options.secret, {that}.options.middlewareOptions)"
220});
221
222fluid.defaults("kettle.middleware.static", {
223 gradeNames: ["kettle.plainAsyncMiddleware"],
224 terms: {
225 },
226 // root: this option must be configured by the implementor
227 middlewareOptions: {}, // https://github.com/expressjs/serve-static#options
228 // Remember that we write this kind of rubbish because of the crummy pre-FLUID-4982 ginger world
229 middleware: "@expand:kettle.middleware.static.createMiddleware({that}.options.root, {that}.options.terms, {that}.options.middlewareOptions)"
230});
231
232kettle.middleware["static"].createMiddleware = function (root, terms, middlewareOptions) {
233 if (!root) {
234 fluid.fail("Static middleware must have a root path configured to serve options - got ", root);
235 }
236 var moduleTerms = fluid.getMembers(fluid.module.modules, "baseDir");
237 var fullTerms = fluid.extend(true, moduleTerms, terms);
238 var expandedRoot = fluid.stringTemplate(root, fullTerms);
239 return kettle.npm.serveStatic(expandedRoot, middlewareOptions);
240};