UNPKG

22.1 kBJavaScriptView Raw
1var path = require('path');
2var fs = require('fs');
3var temp = require('temp');
4var url = require('url');
5
6var async = require("async");
7
8var util = require("./util/util");
9var legacy = require("./util/legacy");
10
11var http = require('http');
12var https = require('https');
13
14// by default, set up Gitana driver so that it limits to five concurrent HTTP requests back to Cloud CMS API at at time
15var Gitana = require("gitana");
16
17// default http timeout
18process.defaultHttpTimeoutMs = 120000; // 2 minutes
19
20if (process.env.DEFAULT_HTTP_TIMEOUT_MS)
21{
22 try
23 {
24 process.defaultHttpTimeoutMs = parseInt(process.env.DEFAULT_HTTP_TIMEOUT_MS);
25 }
26 catch (e)
27 {
28
29 }
30}
31
32// default agents
33var HttpKeepAliveAgent = require('agentkeepalive');
34var HttpsKeepAliveAgent = require('agentkeepalive').HttpsAgent;
35http.globalAgent = new HttpKeepAliveAgent({
36 keepAlive: true,
37 keepAliveMsecs: 1000,
38 keepAliveTimeout: 30000,
39 timeout: process.defaultHttpTimeoutMs,
40 maxSockets: 200,
41 maxFreeSockets: 40,
42 rejectUnauthorized: false
43});
44https.globalAgent = new HttpsKeepAliveAgent({
45 keepAlive: true,
46 keepAliveMsecs: 1000,
47 keepAliveTimeout: 30000,
48 timeout: process.defaultHttpTimeoutMs,
49 maxSockets: 200,
50 maxFreeSockets: 40,
51 rejectUnauthorized: false
52});
53
54// disable for now
55/*
56// report http/https socket state every minute
57var socketReportFn = function()
58{
59 setTimeout(function() {
60
61 var http = require("http");
62 var https = require("https");
63
64 console.log("--- START SOCKET REPORT ---");
65 console.log("[http]: " + JSON.stringify(http.globalAgent.getCurrentStatus(), null, " "));
66 console.log("[https]:" + JSON.stringify(https.globalAgent.getCurrentStatus(), null, " "));
67 console.log("--- END SOCKET REPORT ---");
68
69 socketReportFn();
70
71 }, 60 * 1000);
72};
73socketReportFn();
74*/
75
76// root ssl ca's
77require("ssl-root-cas").inject();
78
79/**
80 * Supports the following directory structure:
81 *
82 *
83 * /hosts
84 *
85 * /<host>
86 *
87 * /public
88 index.html
89 *
90 * /content
91 * /local
92 * /en_us
93 * image.jpg
94 *
95 * /cloudcms
96 * /<branchId>
97 * /en_us
98 * image.jpg
99 *
100 * @type {exports}
101 */
102exports = module.exports = function()
103{
104 // track temporary files
105 temp.track();
106
107 // TODO: this is to disable really annoying Express 3.0 deprecated's for multipart() which should hopefully
108 // TODO: be resolved soon
109 console.warn = function() {};
110
111 process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
112
113 // assume app-server base path if none provided
114 if (!process.env.CLOUDCMS_APPSERVER_BASE_PATH) {
115 process.env.CLOUDCMS_APPSERVER_BASE_PATH = process.cwd();
116 }
117
118 // if there is a "gitana.json" file in the app server base path, we load proxy settings from there if they're
119 // not already specified
120 var defaultGitanaProxyScheme = legacy.DEFAULT_GITANA_PROXY_SCHEME;
121 var defaultGitanaProxyHost = legacy.DEFAULT_GITANA_PROXY_HOST;
122 var defaultGitanaProxyPort = legacy.DEFAULT_GITANA_PROXY_PORT;
123
124 var gitanaJsonPath = path.join(process.env.CLOUDCMS_APPSERVER_BASE_PATH, "gitana.json");
125 if (fs.existsSync(gitanaJsonPath))
126 {
127 var gitanaJson = util.jsonParse("" + fs.readFileSync(gitanaJsonPath));
128 if (gitanaJson && gitanaJson.baseURL)
129 {
130 var parsedUrl = url.parse(gitanaJson.baseURL);
131
132 defaultGitanaProxyHost = parsedUrl.hostname;
133 defaultGitanaProxyScheme = parsedUrl.protocol.substring(0, parsedUrl.protocol.length - 1); // remove the :
134
135 if (parsedUrl.port)
136 {
137 defaultGitanaProxyPort = parsedUrl.port;
138 }
139 else
140 {
141 defaultGitanaProxyPort = 80;
142 if (defaultGitanaProxyScheme === "https")
143 {
144 defaultGitanaProxyPort = 443;
145 }
146 }
147 }
148 }
149
150 // init
151 if (!process.env.GITANA_PROXY_SCHEME) {
152 process.env.GITANA_PROXY_SCHEME = defaultGitanaProxyScheme;
153 }
154 if (!process.env.GITANA_PROXY_HOST) {
155 process.env.GITANA_PROXY_HOST = defaultGitanaProxyHost;
156 }
157 if (!process.env.GITANA_PROXY_PORT) {
158 process.env.GITANA_PROXY_PORT = defaultGitanaProxyPort;
159 }
160
161 // auto-upgrade usage of "api.cloudcms.com" to "api1.cloudcms.com"
162 process.env.GITANA_PROXY_HOST = legacy.autoUpgrade(process.env.GITANA_PROXY_HOST, true);
163
164 console.log("Gitana Proxy pointed to: " + util.asURL(process.env.GITANA_PROXY_SCHEME, process.env.GITANA_PROXY_HOST, process.env.GITANA_PROXY_PORT));
165
166 // all web modules are included by default
167 if (!process.includeWebModule) {
168 process.includeWebModule = function(host, moduleId) {
169 return true;
170 };
171 }
172
173 // middleware
174 var admin = require("./middleware/admin/admin");
175 var authentication = require("./middleware/authentication/authentication");
176 var authorization = require("./middleware/authorization/authorization");
177 var cache = require("./middleware/cache/cache");
178 var cloudcms = require("./middleware/cloudcms/cloudcms");
179 var config = require("./middleware/config/config");
180 var debug = require("./middleware/debug/debug");
181 var deployment = require("./middleware/deployment/deployment");
182 var driver = require("./middleware/driver/driver");
183 var driverConfig = require("./middleware/driver-config/driver-config");
184 var final = require("./middleware/final/final");
185 var flow = require("./middleware/flow/flow");
186 var form = require("./middleware/form/form");
187 var healthcheck = require("./middleware/healthcheck/healthcheck");
188 var host = require("./middleware/host/host");
189 var graphql = require("./middleware/graphql/graphql");
190 var libraries = require("./middleware/libraries/libraries");
191 var local = require("./middleware/local/local");
192 var locale = require("./middleware/locale/locale");
193 var modules = require("./middleware/modules/modules");
194 var perf = require("./middleware/perf/perf");
195 var proxy = require("./middleware/proxy/proxy");
196 var registration = require("./middleware/registration/registration");
197 var resources = require("./middleware/resources/resources");
198 var runtime = require("./middleware/runtime/runtime");
199 var serverTags = require("./middleware/server-tags/server-tags");
200 var storeService = require("./middleware/stores/stores");
201 var templates = require("./middleware/templates/templates");
202 var virtualConfig = require("./middleware/virtual-config/virtual-config");
203 var virtualFiles = require("./middleware/virtual-files/virtual-files");
204 var wcm = require("./middleware/wcm/wcm");
205 var welcome = require("./middleware/welcome/welcome");
206 var awareness = require("./middleware/awareness/awareness");
207 var userAgent = require('express-useragent');
208
209 // services
210 var notifications = require("./notifications/notifications");
211 var broadcast = require("./broadcast/broadcast");
212 var locks = require("./locks/locks");
213
214 // cache
215 //process.cache = cache;
216
217 // read the package.json file and determine the build timestamp
218 var packageJsonPath = path.resolve(__dirname, "package.json");
219 if (fs.existsSync(packageJsonPath))
220 {
221 var packageJson = JSON.parse(fs.readFileSync(packageJsonPath).toString());
222
223 process.env.CLOUDCMS_APPSERVER_PACKAGE_NAME = packageJson.name;
224 process.env.CLOUDCMS_APPSERVER_PACKAGE_VERSION = packageJson.version;
225 }
226
227 // object that we hand back
228 var r = {};
229
230 r.init = function(app, callback)
231 {
232 if (process.configuration && process.configuration.gitana)
233 {
234 if (typeof(process.configuration.gitana.httpWorkQueueSize) !== "undefined")
235 {
236 if (process.configuration.gitana.httpWorkQueueSize > -1)
237 {
238 Gitana.HTTP_WORK_QUEUE_SIZE = process.configuration.gitana.httpWorkQueueSize;
239
240 console.log("Limiting Gitana Driver HTTP work queue to size: " + Gitana.HTTP_WORK_QUEUE_SIZE);
241 }
242 }
243 }
244
245 process.cache = app.cache = cache;
246 process.broadcast = app.broadcast = broadcast;
247 process.locks = app.locks = locks;
248 process.authentication = app.authentication = authentication;
249
250 // app specific
251 app.filter = process.authentication.filter;
252
253 var fns = [
254 locks.init,
255 broadcast.start,
256 storeService.init,
257 notifications.start,
258 cache.init,
259 awareness.init
260 ];
261 async.series(fns, function(err) {
262 callback(err);
263 });
264 };
265
266 r.common1 = function(app)
267 {
268 // app config interceptor
269 applyApplicationConfiguration(app);
270
271 // sets locale onto the request
272 app.use(locale.localeInterceptor());
273
274 // sets host onto the request
275 app.use(host.hostInterceptor());
276 };
277
278 r.common2 = function(app)
279 {
280 // bind stores into the request
281 app.use(storeService.storesInterceptor());
282
283 // puts req.descriptor into the request and req.virtualFiles = true
284 app.use(virtualFiles.interceptor());
285
286 // puts req.runtime into the request
287 app.use(runtime.interceptor());
288
289 // if virtual hosting is enabled, loads "gitana.json" from cloud cms and places it into rootStore
290 // for convenience, also populates req.gitanaConfig
291 app.use(virtualConfig.interceptor());
292
293 // general method for finding "gitana.json" in root store and populating req.gitanaConfig
294 app.use(driverConfig.interceptor());
295 };
296
297 r.common3 = function(app)
298 {
299 // binds "req.gitana" into the request for the loaded "req.gitanaConfig"
300 app.use(driver.driverInterceptor());
301 };
302
303 r.common4 = function(app, includeCloudCMS)
304 {
305 var configuration = app.configuration;
306
307 if (includeCloudCMS)
308 {
309 // bind a cache helper
310 app.use(cache.cacheInterceptor());
311
312 // auto-select the application
313 app.use(cloudcms.applicationInterceptor());
314
315 // auto-select the application settings
316 app.use(cloudcms.applicationSettingsInterceptor());
317
318 // auto-select which gitana repository to use
319 app.use(cloudcms.repositoryInterceptor());
320
321 // auto-select which gitana branch to use
322 // allows for branch specification via request parameter
323 app.use(cloudcms.branchInterceptor());
324
325 // auto-select which gitana domain to use
326 app.use(cloudcms.domainInterceptor());
327
328 // enables ICE menu
329 // app.use(cloudcms.iceInterceptor());
330
331 // enables cms logging
332 app.use(cloudcms.cmsLogInterceptor());
333 }
334
335 // graphql
336 app.use(graphql.interceptor());
337 };
338
339 r.perf1 = function(app)
340 {
341 app.use(perf.pathPerformanceInterceptor());
342 };
343
344 r.proxy = function(app)
345 {
346 app.use(proxy.proxy());
347 };
348
349 r.perf2 = function(app)
350 {
351 app.use(perf.mimeTypePerformanceInterceptor());
352 };
353
354 r.perf3 = function(app)
355 {
356 app.use(perf.developmentPerformanceInterceptor());
357 };
358
359 var applyApplicationConfiguration = r.applyApplicationConfiguration = function(app)
360 {
361 // binds req.config describing the proper app config to use for the request's current application
362 app.use(function(req, res, next) {
363
364 var finish = function(configuration)
365 {
366 req.configuration = function(name, callback)
367 {
368 if (typeof(name) === "function")
369 {
370 return callback(null, configuration);
371 }
372
373 if (!name)
374 {
375 return callback();
376 }
377
378 callback(null, configuration[name]);
379 };
380
381 req.isEnabled = function(name)
382 {
383 return (configuration && configuration[name] && configuration[name].enabled);
384 };
385
386 next();
387 };
388
389 var configuration = util.clone(process.configuration);
390
391 if (req.application)
392 {
393 req.application(function(err, application) {
394
395 if (application)
396 {
397 var applicationConfiguration = application.runtime;
398 if (applicationConfiguration)
399 {
400 // merge configs
401 util.merge(applicationConfiguration, configuration);
402 }
403
404 finish(configuration);
405 }
406 else
407 {
408 finish(configuration);
409 }
410
411 });
412 }
413 else
414 {
415 finish(configuration);
416 }
417 });
418 };
419
420 r.welcome = function(app)
421 {
422 // support for "welcome" files (i.e. index.html)
423 app.use(welcome.welcomeInterceptor());
424 };
425
426 r.healthcheck = function(app)
427 {
428 // support for healthcheck urls
429 app.use(healthcheck.handler());
430 };
431
432 r.interceptors = function(app, includeCloudCMS)
433 {
434 var configuration = app.configuration;
435
436 // authentication interceptor (binds helper methods)
437 app.use(authentication.interceptor());
438
439 // authorization interceptor
440 app.use(authorization.authorizationInterceptor());
441
442 // supports user-configured dynamic configuration
443 app.use(config.remoteConfigInterceptor());
444
445 // tag processing, injection of scripts, etc, kind of a catch all at the moment
446 app.use(serverTags.interceptor(configuration));
447
448 if (includeCloudCMS)
449 {
450 // handles retrieval of content from wcm
451 app.use(wcm.wcmInterceptor());
452 }
453 };
454
455 r.handlers = function(app, includeCloudCMS)
456 {
457 if (includeCloudCMS)
458 {
459 // handles /login and /logout for cloudcms principals
460 app.use(cloudcms.authenticationHandler(app));
461 }
462
463 // handles awareness commands
464 app.use(awareness.handler());
465
466 // handles admin commands
467 app.use(admin.handler());
468
469 // handles debug commands
470 app.use(debug.handler());
471
472 // handles deploy/undeploy commands
473 app.use(deployment.handler());
474
475 // serve back static configuration
476 app.use(config.staticConfigHandler());
477
478 // serve back dynamic configuration
479 app.use(config.remoteConfigHandler());
480
481 // handles calls to the templates service
482 app.use(templates.handler());
483
484 // handles calls to the modules service
485 app.use(modules.handler());
486
487 // handles calls to resources
488 app.use(resources.handler());
489
490 // handles thirdparty browser libraries that are included with cloudcms-server
491 app.use(libraries.handler());
492
493 // authentication
494 app.use(authentication.handler(app));
495
496 if (includeCloudCMS)
497 {
498 // handles virtualized content retrieval from cloud cms
499 app.use(cloudcms.virtualNodeHandler());
500
501 // handles virtualized principal retrieval from cloud cms
502 app.use(cloudcms.virtualPrincipalHandler());
503 }
504
505 // registration
506 app.use(registration.handler());
507
508 // handles calls to web flow controllers
509 app.use(flow.handlers());
510
511 // handles calls to form controllers
512 app.use(form.formHandler());
513
514 // handles runtime status calls
515 app.use(runtime.handler());
516
517 // handles virtualized local content retrieval from disk
518 //app.use(local.webStoreHandler());
519
520 // handles default content retrieval from disk (this includes virtualized)
521 app.use(local.defaultHandler());
522
523 // add User-Agent device info to req
524 app.use(userAgent.express());
525
526 if (includeCloudCMS)
527 {
528 // handles retrieval of content from wcm
529 app.use(wcm.wcmHandler());
530 }
531
532 // handles 404
533 app.use(final.finalHandler());
534 };
535
536 r.bodyParser = function()
537 {
538 return function(req, res, next)
539 {
540 if (req._body)
541 {
542 return next();
543 }
544
545 var contentType = req.get("Content-Type");
546 //if (contentType == "application/json" && req.method.toLowerCase() == "post") {
547 if (req.method.toLowerCase() == "post") {
548
549 req._body = true;
550
551 var responseString = "";
552
553 req.on('data', function(data) {
554 responseString += data;
555 });
556
557 req.on('end', function() {
558
559 if (responseString.length > 0) {
560
561 try {
562 var b = JSON.parse(responseString);
563 if (b)
564 {
565 req.body = b;
566 }
567 } catch (e) { }
568 }
569
570 next();
571 });
572 }
573 else
574 {
575 next();
576 }
577 };
578 };
579
580 /**
581 * Ensures that headers are set to enable CORS cross-domain functionality.
582 *
583 * @returns {Function}
584 */
585 r.ensureCORS = function()
586 {
587 return util.createInterceptor("cors", function(req, res, next, stores, cache, configuration) {
588
589 var origin = configuration.origin;
590 if (!origin)
591 {
592 origin = req.headers["origin"];
593 }
594 if (!origin)
595 {
596 origin = req.headers["x-cloudcms-origin"];
597 }
598 if (!origin)
599 {
600 origin = "*";
601 }
602
603 var methods = configuration.methods;
604 var headers = configuration.headers;
605 var credentials = configuration.credentials;
606
607 util.setHeaderOnce(res, "Access-Control-Allow-Origin", origin);
608
609 if (methods)
610 {
611 util.setHeaderOnce(res, "Access-Control-Allow-Methods", methods);
612 }
613
614 if (headers)
615 {
616 util.setHeaderOnce(res, "Access-Control-Allow-Headers", headers);
617 }
618
619 if (credentials)
620 {
621 util.setHeaderOnce(res, "Access-Control-Allow-Credentials", "" + credentials);
622 }
623
624 // res.set('Access-Control-Allow-Max-Age', 3600);
625
626 if ('OPTIONS' === req.method) {
627 return res.sendStatus(200);
628 }
629
630 next();
631 });
632 };
633
634 r.ensureHeaders = function()
635 {
636 return function(req, res, next) {
637
638 // defaults
639 var xFrameOptions = "SAMEORIGIN";
640 var xXssProtection = "1; mode=block";
641
642 // TODO: allow overrides here?
643
644 if (xFrameOptions)
645 {
646 util.setHeaderOnce(res, "X-Frame-Options", xFrameOptions);
647 }
648
649 if (xXssProtection)
650 {
651 util.setHeaderOnce(res, "X-XSS-Protection", xXssProtection)
652 }
653
654 util.setHeaderOnce(res, "X-Powered-By", "Cloud CMS");
655
656 next();
657 };
658 };
659
660 var stringifyError = function(err)
661 {
662 var stack = err.stack;
663
664 if (stack) {
665 return String(stack)
666 }
667
668 return JSON.stringify(err, null, " ");
669 };
670
671
672 r.consoleErrorLogger = function(app, callback)
673 {
674 // generic logger to console
675 app.use(function(err, req, res, next) {
676
677 console.error(stringifyError(err));
678
679 next(err);
680 });
681
682 callback();
683 };
684
685 var errorHandler = require("errorhandler");
686
687 r.refreshTokenErrorHandler = function(app, callback)
688 {
689 app.use(function(err, req, res, next) {
690
691 if (err)
692 {
693 if (req.method.toLowerCase() === "get")
694 {
695 if (err.status === 401)
696 {
697 if (err.message)
698 {
699 if (err.message.toLowerCase().indexOf("expired") > -1)
700 {
701 if (err.message.toLowerCase().indexOf("refresh") > -1)
702 {
703 var url = req.path;
704
705 console.log("Refresh Token Expired, re-requesting resource: " + url);
706
707 var html = "";
708 html += "<html>";
709 html += "<head>";
710 html += "<meta http-equiv='refresh' content='1;URL=" + url + "'>";
711 html += "</head>";
712 html += "<body>";
713 html += "</body>";
714 html += "</html>";
715
716 return res.status(200).type("text/html").send(html);
717 }
718 }
719 }
720 }
721 }
722 }
723
724 next(err);
725 });
726
727 callback();
728 };
729
730 r.defaultErrorHandler = function(app, callback)
731 {
732 app.use(function(err, req, res, next) {
733
734 // use the stock error handler
735 errorHandler()(err, req, res, next);
736 });
737
738 callback();
739 };
740
741 return r;
742}();