1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 | "use strict";
|
14 |
|
15 | var fluid = require("infusion"),
|
16 | http = require("http"),
|
17 | https = require("https"),
|
18 | express = require("express"),
|
19 | kettle = fluid.registerNamespace("kettle");
|
20 |
|
21 | fluid.defaults("kettle.server", {
|
22 | gradeNames: ["fluid.component"],
|
23 | mergePolicy: {
|
24 | rootMiddleware: "noexpand"
|
25 | },
|
26 | rootMiddleware: {
|
27 | urlencoded: {
|
28 | middleware: "{middlewareHolder}.urlencoded",
|
29 | priority: "before:json"
|
30 | },
|
31 | json: {
|
32 | middleware: "{middlewareHolder}.json"
|
33 | }
|
34 | },
|
35 | invokers: {
|
36 | stop: {
|
37 | funcName: "kettle.server.stop",
|
38 | args: "{that}"
|
39 | },
|
40 | trackConnections: {
|
41 | funcName: "kettle.server.trackConnections",
|
42 | args: ["{that}.sockets", "{that}.httpServer"]
|
43 | },
|
44 | closeConnections: {
|
45 | funcName: "kettle.server.closeConnections",
|
46 | args: "{that}.sockets"
|
47 | }
|
48 | },
|
49 | components: {
|
50 | middlewareHolder: {
|
51 | type: "kettle.standardMiddleware"
|
52 | },
|
53 | httpRouter: {
|
54 | type: "kettle.router.http"
|
55 | }
|
56 | },
|
57 | members: {
|
58 | expressApp: "@expand:kettle.server.makeExpressApp()",
|
59 | httpServer: "@expand:kettle.server.httpServer({that}.expressApp)",
|
60 | dispatcher: "@expand:kettle.server.getDispatcher({that}, {that}.rootSequence)",
|
61 | rootSequence: "@expand:kettle.middleware.getHandlerSequence({that}, rootMiddleware, {that}.options.rootMiddleware)",
|
62 | apps: [],
|
63 | sockets: []
|
64 | },
|
65 | events: {
|
66 | onContributeMiddleware: null,
|
67 | onContributeRouteHandlers: null,
|
68 | onListen: null,
|
69 | beforeStop: null,
|
70 | onStopped: null
|
71 | },
|
72 | listeners: {
|
73 | "onCreate.setLogging": {
|
74 | funcName: "fluid.setLogging",
|
75 | args: "{that}.options.logging"
|
76 | },
|
77 | "onCreate.contributeMiddleware": {
|
78 | func: "{that}.events.onContributeMiddleware.fire",
|
79 | args: "{that}",
|
80 | priority: "after:setLogging"
|
81 | },
|
82 | "onCreate.contributeRouteHandlers": {
|
83 | func: "{that}.events.onContributeRouteHandlers.fire",
|
84 | args: "{that}",
|
85 | priority: "after:contributeMiddleware"
|
86 | },
|
87 | "onCreate.registerDispatchHandler": {
|
88 | funcName: "kettle.server.registerDispatchHandler",
|
89 | args: "{that}",
|
90 | priority: "after:contributeRouteHandlers"
|
91 | },
|
92 | "onCreate.listen": {
|
93 | funcName: "kettle.server.listen",
|
94 | args: "{that}",
|
95 | priority: "after:registerHandler"
|
96 | },
|
97 | onListen: "{that}.trackConnections",
|
98 | onDestroy: "{that}.stop",
|
99 | beforeStop: "{that}.closeConnections",
|
100 | onStopped: "kettle.server.shred({that})"
|
101 | },
|
102 | port: 8081,
|
103 | logging: true
|
104 | });
|
105 |
|
106 | kettle.server.registerApp = function (server, app) {
|
107 | server.apps.push(app);
|
108 | };
|
109 |
|
110 |
|
111 | kettle.server.mismatchRequestMessages = {
|
112 | "kettle.request.ws": {
|
113 | statusCode: 400,
|
114 | message: "Error: Mismatched request protocol - sent a WebSockets request to an endpoint expecting a plain HTTP request"
|
115 | },
|
116 | "kettle.request.http": {
|
117 | statusCode: 426,
|
118 | message: "Error: Mismatched request protocol - sent an HTTP request to an endpoint expecting a WebSockets request - upgrade required"
|
119 | }
|
120 | };
|
121 |
|
122 | kettle.server.checkCompatibleRequest = function (expectedRequestGrade, handlerGrades) {
|
123 | return fluid.contains(handlerGrades, expectedRequestGrade) ? null : kettle.server.mismatchRequestMessages[expectedRequestGrade];
|
124 | };
|
125 |
|
126 | kettle.server.evaluateRoute = function (server, req, originOptions) {
|
127 | var router = kettle.server.getRouter(server, req);
|
128 | var match = router.match(req);
|
129 | if (match) {
|
130 | var handler = match.handler;
|
131 | if (!handler.type) {
|
132 | fluid.fail("Error in Kettle application definition - handler ", fluid.censorKeys(handler, ["app"]), " must have a request grade name registered as member \"type\"");
|
133 | }
|
134 | fluid.log("Invoking handler " + handler.type + " for route " + handler.route + " with expectedGrade " + originOptions.expectedRequestGrade);
|
135 | var defaults = fluid.getMergedDefaults(handler.type, handler.gradeNames);
|
136 | if (!fluid.hasGrade(defaults, "kettle.request")) {
|
137 | fluid.fail("Error in Kettle application definition - couldn't load handler " + handler.type + " and gradeNames " +
|
138 | JSON.stringify(fluid.makeArray(handler.gradeNames)) + " to a component derived from kettle.request - got defaults of " + JSON.stringify(defaults));
|
139 | }
|
140 | match.output.mismatchError = kettle.server.checkCompatibleRequest(originOptions.expectedRequestGrade, defaults.gradeNames);
|
141 | if (match.output.mismatchError) {
|
142 | handler.type = originOptions.expectedRequestGrade + ".mismatch";
|
143 | handler.gradeNames = [];
|
144 | }
|
145 | }
|
146 | return match;
|
147 | };
|
148 |
|
149 | kettle.server.checkCreateRequest = function (server, req, res, next, originOptions) {
|
150 | var match = kettle.server.evaluateRoute(server, req, originOptions);
|
151 | if (match) {
|
152 | fluid.extend(req, match.output);
|
153 | var handler = match.handler;
|
154 | if (handler.prefix) {
|
155 |
|
156 | if (req.url.indexOf(handler.prefix) !== 0) {
|
157 | fluid.fail("Failure in route matcher - request url " + req.url + " does not start with prefix " + handler.prefix + " even though it has been matched");
|
158 | } else {
|
159 |
|
160 | req.url = req.url.substring(handler.prefix.length);
|
161 | req.baseUrl = handler.prefix;
|
162 | }
|
163 | }
|
164 | var options = fluid.extend({gradeNames: handler.gradeNames}, originOptions);
|
165 | handler.app.requests.events.createRequest.fire({
|
166 | type: handler.type,
|
167 | options: options
|
168 | }, req, res, next);
|
169 | }
|
170 | return req.fluidRequest;
|
171 | };
|
172 |
|
173 | kettle.server.getRouter = function (that /*, req, handler */) {
|
174 | return that.httpRouter;
|
175 | };
|
176 |
|
177 | kettle.server.sequenceRequest = function (fullSequence, request) {
|
178 | var sequence = fluid.promise.sequence(fullSequence, request);
|
179 | var togo = fluid.promise();
|
180 | sequence.then(function () {
|
181 | fluid.promise.follow(request.handlerPromise, togo);
|
182 | }, togo.reject);
|
183 | return togo;
|
184 | };
|
185 |
|
186 | kettle.server.getDispatcher = function (that, rootSequence) {
|
187 | return function (req, res, next, options) {
|
188 | var request = kettle.server.checkCreateRequest(that, req, res, next, options);
|
189 | if (request) {
|
190 | fluid.log("Kettle server allocated request object with type ", request.typeName);
|
191 | var requestSequence = kettle.middleware.getHandlerSequence(request, "requestMiddleware");
|
192 | var fullSequence = rootSequence.concat(requestSequence).concat([kettle.request.handleRequestTask]);
|
193 | var handleRequestPromise = kettle.server.sequenceRequest(fullSequence, request);
|
194 | request.handleFullRequest(request, handleRequestPromise, next);
|
195 | handleRequestPromise.then(kettle.request.clear, kettle.request.clear);
|
196 | } else {
|
197 | fluid.log("Kettle server getDispatcher found no matching handlers, continuing");
|
198 | next();
|
199 | }
|
200 | };
|
201 | };
|
202 |
|
203 | kettle.server.registerOneHandler = function (that, app, handler) {
|
204 | var router = kettle.server.getRouter(that, null, handler);
|
205 | fluid.log("Registering request handler ", handler);
|
206 | var extend = {
|
207 | app: app
|
208 | };
|
209 | if (handler.method) {
|
210 | var methods = fluid.transform(handler.method.split(","), fluid.trim);
|
211 | fluid.each(methods, function (method) {
|
212 | extend.method = method;
|
213 | kettle.router.registerOneHandlerImpl(router, handler, extend);
|
214 | });
|
215 | } else {
|
216 | kettle.router.registerOneHandlerImpl(router, handler, extend);
|
217 | }
|
218 | };
|
219 |
|
220 | kettle.server.registerDispatchHandler = function (that) {
|
221 | fluid.each(that.apps, function (app) {
|
222 | fluid.each(app.options.requestHandlers, function (requestHandler, key) {
|
223 | if (requestHandler) {
|
224 | kettle.server.registerOneHandler(that, app, requestHandler);
|
225 | } else {
|
226 |
|
227 |
|
228 | fluid.log("Skipping empty handler with key " + key + " for app " + fluid.dumpThat(app));
|
229 | }
|
230 | });
|
231 | });
|
232 | that.expressApp.use(function (req, res, next) {
|
233 | that.dispatcher(req, res, next, {expectedRequestGrade: "kettle.request.http"});
|
234 | });
|
235 | };
|
236 |
|
237 |
|
238 | kettle.server.shred = function (that) {
|
239 | delete that.httpServer;
|
240 | delete that.expressApp;
|
241 | };
|
242 |
|
243 | kettle.server.stop = function (that) {
|
244 | if (!that.httpServer) {
|
245 | return;
|
246 | }
|
247 | var port = that.options.port;
|
248 | fluid.log("Stopping Kettle Server " + that.id + " on port ", port);
|
249 | that.events.beforeStop.fire();
|
250 |
|
251 | that.httpServer.close(function () {
|
252 | fluid.log("Kettle Server " + that.id + " on port ", port, " is stopped");
|
253 | that.events.onStopped.fire();
|
254 | });
|
255 | };
|
256 |
|
257 | kettle.server.closeConnections = function (sockets) {
|
258 |
|
259 |
|
260 | fluid.each(sockets, function (socket) {
|
261 | socket.destroy();
|
262 | });
|
263 | };
|
264 |
|
265 |
|
266 |
|
267 |
|
268 |
|
269 |
|
270 | kettle.server.httpServer = function (server) {
|
271 | fluid.log("Initializing the HTTP server");
|
272 | return http.createServer(server);
|
273 | };
|
274 |
|
275 | kettle.server.httpsServer = function (options, app) {
|
276 | fluid.log("Initializing the HTTPS server");
|
277 | return https.createServer(options, app);
|
278 | };
|
279 |
|
280 | kettle.server.makeExpressApp = function () {
|
281 | fluid.log("Initializing the Express app");
|
282 | return express();
|
283 | };
|
284 |
|
285 | kettle.server.listen = function (that) {
|
286 | var port = that.options.port;
|
287 | fluid.log("Opening Kettle Server on port ", port);
|
288 | that.httpServer.listen(port, function () {
|
289 | fluid.log("Kettle Server " + that.id + " is listening on port " + port);
|
290 | that.events.onListen.fire();
|
291 | });
|
292 | };
|
293 |
|
294 | kettle.server.trackConnections = function (sockets, httpServer) {
|
295 |
|
296 | httpServer.on("connection", function (socket) {
|
297 | sockets.push(socket);
|
298 | socket.on("close", function () {
|
299 | sockets.splice(sockets.indexOf(socket), 1);
|
300 | });
|
301 | });
|
302 | };
|
303 |
|
304 | fluid.defaults("kettle.server.config", {
|
305 | gradeNames: ["fluid.component"],
|
306 | listeners: {
|
307 | onCreate: "{that}.configure"
|
308 | },
|
309 | invokers: {
|
310 | configure: "kettle.server.config.configure"
|
311 | }
|
312 | });
|