cloudcms-server
Version:
Cloud CMS Application Server Module
1,393 lines (1,174 loc) • 43.6 kB
JavaScript
var express = require('express');
var http = require('http');
var https = require('https');
var path = require('path');
var fs = require('fs');
var clone = require('clone');
var bytes = require('bytes');
var moment = require("moment");
var async = require('../util/async');
var morgan = require("morgan");
var bodyParser = require("body-parser");
var multipart = require("connect-multiparty");
var session = require('express-session');
var cookieParser = require('cookie-parser');
var flash = require("connect-flash");
const {RedisStore} = require("connect-redis");
// we don't bind a single passport - instead, we get the constructor here by hand
var Passport = require("passport").Passport;
var util = require("../util/util");
var redisHelper = require("../util/redis");
var launchPad = require("../launchpad/index");
var cluster = require("cluster");
var requestParam = require("request-param")();
// cloudcms app server support
var main = require("../index");
var helmet = require("helmet");
var responseTime = require("response-time");
// safely checks for the existence of a path
var safeExists = function(_path)
{
var exists = false;
try
{
exists = fs.existsSync(_path);
}
catch (e)
{
// swallow
}
return exists;
}
var requestCounter = 0;
// holds configuration settings
var SETTINGS = {
"setup": "single", // single, cluster, redis
"name": "Cloud CMS Application Server",
"socketFunctions": [],
"routeFunctions": [],
"errorFunctions": [],
"configureFunctions": {},
"beforeFunctions": [],
"afterFunctions": [],
"reportFunctions": [],
"initFunctions": [],
"filterFunctions": [],
"driverFunctions": [],
"viewEngine": "handlebars",
"storeEngines": {
"app": {
"type": "fs",
"config": {
"basePath": "{appBasePath}"
}
},
"tmp": {
"type": "fs",
"config": {
"basePath": "{tmpdirPath}/hosts/{host}",
"hostsPath": "{tmpdirPath}/hosts"
}
},
"hosts_fs": {
"type": "fs",
"config": {
"basePath": "{hostsPath}/{host}",
"hostsPath": "{hostsPath}"
}
},
"hosts_s3": {
"type": "s3",
"config": {
"accessKey": "",
"secretKey": "",
"bucket": "",
"basePath": "{hostsPath}/{host}",
"hostsPath": "{hostsPath}"
}
},
"hosts_s3fs": {
"type": "s3fs",
"config": {
"accessKey": "",
"secretKey": "",
"bucket": "",
"basePath": "{hostsPath}/{host}",
"hostsPath": "{hostsPath}"
}
}
},
"storeConfigurations": {
"default": {
"root": "app",
"config": "app",
"web": "app",
"content": "tmp",
"templates": "app",
"modules": "app"
},
"virtual": {
"root": "tmp",
"config": "tmp",
"web": "tmp",
"content": "tmp",
"templates": "tmp",
"modules": "tmp"
},
"oneteam": {
"root": "hosts_fs",
"config": "app",
"web": "app",
"content": "hosts_fs",
"templates": "app",
"modules": "hosts_fs"
},
"net-development": {
"root": "hosts_fs",
"config": "hosts_fs",
"web": "hosts_fs",
"content": "hosts_fs",
"templates": "hosts_fs",
"modules": "hosts_fs"
},
"net-production": {
"root": "hosts_s3fs",
"config": "hosts_s3fs",
"web": "hosts_s3fs",
"content": "hosts_s3fs",
"templates": "hosts_s3fs",
"modules": "hosts_s3fs"
},
"net-development-s3": {
"root": "hosts_s3",
"config": "hosts_s3",
"web": "hosts_s3",
"content": "hosts_s3",
"templates": "hosts_s3"
},
"net-development-s3fs": {
"root": "hosts_s3fs",
"config": "hosts_s3fs",
"web": "hosts_s3fs",
"content": "hosts_s3fs",
"templates": "hosts_s3fs",
"modules": "hosts_s3fs"
}
},
"virtualHost": {
"enabled": false
},
"wcm": {
"enabled": false,
"cache": false,
"matchCase": true,
"cacheKey": {
"params": {
"includes": [],
"excludes": [],
"excludeAll": false
}
},
"pageCacheTTL": undefined,
"pageCacheRetryTimeout": undefined
},
"serverTags": {
"enabled": false
},
"insight": {
"enabled": false
},
"perf": {
"enabled": true
},
"driverConfig": {
"enabled": true
},
"virtualDriver": {
"enabled": false
},
"virtualContent": {
"enabled": true
},
"flow": {
"enabled": false
},
"form": {
"enabled": true
},
"auth": {
"enabled": true,
"providers": {
"facebook": {
"enabled": false
},
"twitter": {
"enabled": false
},
"linkedin": {
"enabled": false
}
}
},
"notifications": {
"enabled": false,
"type": null,
"configuration": {
}
},
"broadcast": {
"enabled": true
},
"local": {
"enabled": true
},
"welcome": {
"enabled": true,
"file": "index.html"
},
"config": {
"enabled": true
},
"cache": {
"enabled": true
},
"templates": {
"enabled": true
},
"modules": {
"enabled": true
},
"debug": {
"enabled": false,
"logGlobalTimings": false
},
"cors": {
"enabled": true,
"origin": null,
"methods": "GET, POST, PUT, DELETE, OPTIONS",
"headers": "X-Forwarded-Host, X-Requested-With, Content-Type, Authorization, Origin, X-Requested-With, X-Prototype-Version, Cache-Control, Pragma, X-CSRF-TOKEN, X-XSRF-TOKEN",
"credentials": true
},
"admin": {
"enabled": true,
"username": "admin",
"password": "admin"
},
"bodyParsers": {
"multipart": {
},
"json": {
"limit": "100kb"
},
"urlencoded": {
"extended": true
}
},
"renditions": {
"enabled": true
},
"gitana": {
"httpWorkQueueSize": 5
},
"proxy": {
"enabled": true,
"cache": []
},
"session": {
"enabled": false//,
//"secret": null,
//"type": "file",
//"ttl": -1,
//"reapInterval": -1
},
"awareness": {
"enabled": false
},
"graphql": {
"enabled": true,
"config": {
"anonymous": true
}
}
};
// if SETTINGS.errorFunctions is empty, plug in a default error handler
if (SETTINGS.errorFunctions.length === 0)
{
SETTINGS.errorFunctions.push(main.defaultErrorHandler);
}
else
{
// otherwise, if they plugged in a custom error handler, make sure we at least have a console logger ahead of it
// so that things are sure to get logged out to console
SETTINGS.errorFunctions.unshift(main.consoleErrorLogger);
}
// insert an error handler to handle refresh token failures
SETTINGS.errorFunctions.unshift(main.refreshTokenErrorHandler);
// CLOUDCMS_HOSTS_PATH environment variable
// assume /hosts with optional fallback to /System/Volumes/Data/hosts for MacOS support
if (!process.env.CLOUDCMS_HOSTS_PATH)
{
process.env.CLOUDCMS_HOSTS_PATH = "/hosts";
if (!safeExists(process.env.CLOUDCMS_HOSTS_PATH))
{
if (safeExists("/System/Volumes/Data/hosts"))
{
process.env.CLOUDCMS_HOSTS_PATH = "/System/Volumes/Data/hosts";
}
else
{
const homedir = require('os').homedir();
if (safeExists(homedir + "/hosts"))
{
process.env.CLOUDCMS_HOSTS_PATH = homedir + "/hosts";
}
}
}
}
// runs on 2999 by default
process.env.PORT = process.env.PORT || 2999;
var exports = module.exports;
/**
* Sets a configuration key/value.
*
* @param key
* @param value
*/
exports.set = function (key, value) {
SETTINGS[key] = value;
};
/**
* Gets a configuration key/value.
*
* @param key
* @return {*}
*/
exports.get = function (key) {
return SETTINGS[key];
};
/**
* Registers an express configuration function for a specific environment.
*
* @param env
* @param fn
*/
exports.configure = function (env, fn) {
if (!SETTINGS.configureFunctions[env]) {
SETTINGS.configureFunctions[env] = [];
}
SETTINGS.configureFunctions[env].push(fn);
};
/**
* Registers a socket configuration function.
*
* @param fn
*/
exports.sockets = function (fn) {
SETTINGS.socketFunctions.push(fn);
};
/**
* Registers a route configuration function.
*
* @param fn
*/
exports.routes = function (fn) {
SETTINGS.routeFunctions.push(fn);
};
/**
* Registers an error handler function.
*
* @param fn
*/
exports.error = function (fn) {
SETTINGS.errorFunctions.push(fn);
};
/**
* Registers a function to run before the server starts.
*
* @param fn
*/
var before = exports.before = function (fn) {
SETTINGS.beforeFunctions.push(fn);
};
/**
* Registers a function to run after the server starts.
*
* @param fn
*/
var after = exports.after = function (fn) {
SETTINGS.afterFunctions.push(fn);
};
/**
* Registers a function to run after all server instances have started
*
* @param fn
*/
var report = exports.report = function (fn) {
SETTINGS.reportFunctions.push(fn);
};
/**
* Registers a function to run at init.
*
* @param fn
*/
var init = exports.init = function (fn) {
SETTINGS.initFunctions.push(fn);
};
/**
* Registers a function to run in filters phase.
*
* @param fn
*/
var filters = exports.filters = exports.filter = function (fn) {
SETTINGS.filterFunctions.push(fn);
};
/**
* Registers a function to run in the "driver" phase.
*
* @type {Function}
*/
var driver = exports.driver = function(fn) {
SETTINGS.driverFunctions.push(fn);
};
/*******************************************************************************************************/
/*******************************************************************************************************/
/*******************************************************************************************************/
var runFunctions = function (functions, args, callback) {
// skip out early if nothing to do
if (!functions || functions.length === 0) {
return callback();
}
async.series(functions, args, function (err) {
if (err) {
process.log(err);
throw new Error(err);
}
callback(err);
});
};
/*******************************************************************************************************/
/*******************************************************************************************************/
/*******************************************************************************************************/
/**
* Starts the Cloud CMS server.
*
* @param overrides optional config overrides
* @param callback optional callback function
*/
exports.start = function(overrides, callback) {
setTimeout(function() {
_start(overrides, function(err) {
if (callback) {
callback(err);
}
});
}, 10);
};
var _start = function(overrides, callback) {
if (typeof(overrides) === "function") {
callback = overrides;
overrides = null;
}
if (!callback) {
callback = function() {};
}
// create our master config
var config = clone(SETTINGS);
if (overrides) {
util.merge(overrides, config);
}
// set up modes
process.env.CLOUDCMS_APPSERVER_MODE = "development";
if (process.env.NODE_ENV === "production") {
process.env.CLOUDCMS_APPSERVER_MODE = "production";
}
/*
// set up domain hosting
// if not otherwise specified, we assume hosting at *.cloudcms.net
if (!process.env.CLOUDCMS_VIRTUAL_HOST) {
if (!process.env.CLOUDCMS_VIRTUAL_HOST_DOMAIN) {
process.env.CLOUDCMS_VIRTUAL_HOST_DOMAIN = "cloudcms.net";
}
}
*/
// store config on process instance
process.configuration = config;
// some config overrides can come in through process.configuration
if (process.configuration) {
if (process.configuration.virtualHost && process.configuration.virtualHost.domain) {
if (!process.env.CLOUDCMS_VIRTUAL_HOST) {
if (!process.env.CLOUDCMS_VIRTUAL_HOST_DOMAIN) {
process.env.CLOUDCMS_VIRTUAL_HOST_DOMAIN = process.configuration.virtualHost.domain;
}
}
}
}
if (process.env.CLOUDCMS_VIRTUAL_HOST_DOMAIN) {
process.env.CLOUDCMS_VIRTUAL_HOST_DOMAIN = process.env.CLOUDCMS_VIRTUAL_HOST_DOMAIN.toLowerCase();
}
if (!process.env.CLOUDCMS_STANDALONE_HOST) {
process.env.CLOUDCMS_STANDALONE_HOST = "local";
}
// auto-configuration for HTTPS
if (!process.configuration.https) {
process.configuration.https = {};
}
if (process.env.CLOUDCMS_HTTPS) {
process.configuration.https = JSON.parse(process.env.CLOUDCMS_HTTPS);
}
if (process.env.CLOUDCMS_HTTPS_KEY_FILEPATH) {
process.configuration.https.key = fs.readFileSync(process.env.CLOUDCMS_HTTPS_KEY_FILEPATH);
}
if (process.env.CLOUDCMS_HTTPS_CERT_FILEPATH) {
process.configuration.https.cert = fs.readFileSync(process.env.CLOUDCMS_HTTPS_CERT_FILEPATH);
}
if (process.env.CLOUDCMS_HTTPS_PFX_FILEPATH) {
process.configuration.https.pfx = fs.readFileSync(process.env.CLOUDCMS_HTTPS_PFX_FILEPATH);
}
if (process.env.CLOUDCMS_HTTPS_PASSPHRASE) {
process.configuration.https.passphrase = process.env.CLOUDCMS_HTTPS_PASSPHRASE;
}
if (process.env.CLOUDCMS_HTTPS_REQUEST_CERT === "true") {
process.configuration.https.requestCert = true;
}
if (process.env.CLOUDCMS_HTTPS_CA_FILEPATH) {
process.configuration.https.ca = [ fs.readFileSync(process.env.CLOUDCMS_HTTPS_CA_FILEPATH) ];
}
// if https config is empty, remove it
if (Object.keys(process.configuration.https).length === 0) {
delete process.configuration.https;
}
// auto configuration of session store
if (!process.configuration.session) {
process.configuration.session = {};
}
// auto-configuration for redis?
if (process.env.CLOUDCMS_REDIS_URL || (process.env.CLOUDCMS_REDIS_ENDPOINT && process.env.CLOUDCMS_REDIS_PORT)) {
process.env.CLOUDCMS_SESSION_TYPE = "redis";
}
if (process.env.CLOUDCMS_SESSION_TYPE) {
process.configuration.session.enabled = true;
process.configuration.session.type = process.env.CLOUDCMS_SESSION_TYPE;
}
if (process.env.CLOUDCMS_SESSION_SECRET) {
process.configuration.session.secret = process.env.CLOUDCMS_SESSION_SECRET;
}
// determine the max files
util.maxFiles(function(err, maxFiles) {
process.env.CLOUDCMS_MAX_FILES = maxFiles;
// assume for launchpad
if (!config.setup) {
config.setup = "single";
}
launchPad(config.setup, config, {
"createHttpServer": function(app, done) {
createHttpServer(app, function(err, httpServer) {
done(err, httpServer);
});
},
"startServer": function(config, done) {
startServer(config, function(err, app, httpServer, httpServerPort) {
done(err, app, httpServer, httpServerPort);
});
},
"configureServer": function(config, app, httpServer, done) {
configureServer(config, app, httpServer, function(err) {
done(err);
});
},
"report": function(config) {
runFunctions(config.reportFunctions, [], function(err) {
// todo
});
},
"complete": function(config, err) {
callback(err);
}
});
});
};
var initSession = function(initDone)
{
if (!process.configuration.session) {
return initDone();
}
if (!process.configuration.session.enabled) {
return initDone();
}
var sessionSecret = process.configuration.session.secret;
if (!sessionSecret) {
sessionSecret = "secret";
}
var sessionConfig = {
secret: sessionSecret,
resave: false,
saveUninitialized: false
};
if (process.configuration.session.type) {
process.configuration.session.type = process.configuration.session.type.toLowerCase();
}
if (process.configuration.session.type === "file")
{
var options = {};
if (process.configuration.session.ttl)
{
options.ttl = process.configuration.session.ttl;
}
if (process.configuration.session.reapInterval)
{
options.reapInterval = process.configuration.session.reapInterval;
}
// session file store
var SessionFileStore = require('session-file-store')(session);
sessionConfig.store = new SessionFileStore(options);
return initDone(null, session(sessionConfig));
}
else if (process.configuration.session.type === "redis")
{
var IORedis = require("ioredis");
var redisOptions = redisHelper.redisOptions();
var redisClient = new IORedis(redisOptions.url);
sessionConfig.store = new RedisStore({ client: redisClient });
initDone(null, session(sessionConfig));
}
else if (process.configuration.session.type === "memory" || !process.configuration.session.type)
{
var options = {};
options.checkPeriod = 86400000; // prune expired entries every 24h
// session memory store
var MemoryStore = require('memorystore')(session);
sessionConfig.store = new MemoryStore(options);
return initDone(null, session(sessionConfig));
}
};
//var debugMiddleware = process.debugMiddleware;
var startServer = function(config, startServerFinishedFn)
{
var app = express();
app.disable('x-powered-by');
// customize the app use() method to provide sensible logging
app._use = app.use;
app.use = function(f)
{
if (typeof(f) !== "function")
{
return app._use.apply(app, arguments);
}
return app._use(function(f) {
return function(req, res, next)
{
var functionName = f.name;
if (!functionName) {
functionName = "unknown";
}
var id = req.id;
var startTime = process.hrtime();
f(req, res, function() {
var totalTime = process.hrtime(startTime);
var totalTimeMs = (totalTime[1] / 1000000).toFixed(2);
// if (totalTimeMs > 100)
// {
// if (id)
// {
// console.log("[" + id + "](" + functionName + ") time: " + totalTimeMs);
// }
//
// //console.trace();
// //process.exit(-1);
// }
next();
});
}
}(f));
};
initSession(function(err, initializedSession) {
if (err) {
throw err;
}
// global temp directory
util.createTempDirectory(function(err, tempDirectory) {
process.env.CLOUDCMS_TEMPDIR_PATH = tempDirectory;
// global service starts
main.init(app, function (err) {
app.enable('strict routing');
////////////////////////////////////////////////////////////////////////////
//
// BASE CONFIGURATION
//
// Runs on port 3000 by default
//
////////////////////////////////////////////////////////////////////////////
// all environments
app.set('port', process.env.PORT);
app.set('views', process.env.CLOUDCMS_APPSERVER_BASE_PATH + "/views");
if (config.viewEngine === "handlebars" || config.viewEngine === "hbs")
{
var hbs = require('hbs');
app.set('view engine', 'html');
app.set('view engine', 'hbs');
app.engine('html', hbs.__express);
app.engine('hbs', hbs.__express);
}
////////////////////////////////////////////////////////////////////////////
//
// VIRTUAL SUPPORT
//
// Configure NodeJS to load virtual driver and configure for virtual descriptors
// ahead of anything else running.
//
////////////////////////////////////////////////////////////////////////////
// custom morgan logger
morgan(function (tokens, req, res) {
var status = res.statusCode;
var len = parseInt(res.getHeader('Content-Length'), 10);
var host = req.domainHost;
if (req.virtualHost) {
host = req.virtualHost;
}
len = isNaN(len) ? '0b' : len = bytes(len);
var d = new Date();
var dateString = d.toDateString();
var timeString = d.toTimeString();
// gray color
var grayColor = "\x1b[90m";
// status color
var color = 32;
if (status >= 500) {
color = 31;
}
else if (status >= 400) {
color = 33;
}
else if (status >= 300) {
color = 36;
}
var statusColor = "\x1b[" + color + "m";
// final color
var finalColor = "\x1b[0m";
if (process.env.CLOUDCMS_APPSERVER_MODE === "production")
{
grayColor = "";
statusColor = "";
finalColor = "";
}
var message = '';
message += grayColor + '<' + req.id + '> ';
message += grayColor + '[' + dateString + ' ' + timeString + '] ';
message += grayColor + host + ' ';
//message += grayColor + '(' + req.ip + ') ';
message += statusColor + res.statusCode + ' ';
message += statusColor + (new Date - req._startTime) + ' ms ';
message += grayColor + '"' + req.method + ' ';
message += grayColor + req.originalUrl + '" ';
message += grayColor + len + ' ';
message += finalColor;
return message;
});
/*
// debug headers being set
app.use(function(req, res, next) {
var setHeader = res.setHeader;
res.setHeader = function(a,b) {
console.trace("Writing header: " + a + " = " + b);
setHeader.call(this, a,b);
};
next();
});
*/
// increment and assign request id
app.use(function increment_and_assign_id(req, res, next) {
requestCounter++;
req.id = requestCounter;
next();
});
// APPLY CUSTOM INIT FUNCTIONS
runFunctions(config.initFunctions, [app], function (err) {
// retain originalUrl and originalPath since these can get modified along the way
app.use(function retain_original_url_path(req, res, next) {
req.originalUrl = req.url;
req.originalPath = req.path;
next();
});
// req.param method
app.use(requestParam);
// add req.log function
app.use(function bind_req_log(req, res, next) {
req._log = req.log = function (text/*, warn*/) {
var host = req.domainHost;
if (req.virtualHost)
{
host = req.virtualHost;
}
var timestamp = moment(new Date()).format("MM/DD/YYYY HH:mm:ss Z");
var grayColor = "\x1b[90m";
var finalColor = "\x1b[0m";
// in production, don't use colors
if (process.env.CLOUDCMS_APPSERVER_MODE === "production")
{
grayColor = "";
finalColor = "";
}
var message = '';
message += grayColor + '<' + req.id + '> ';
if (cluster.worker && cluster.worker.id)
{
message += grayColor + '(' + cluster.worker.id + ') ';
}
message += grayColor + '[' + timestamp + '] ';
message += grayColor + host + ' ';
message += grayColor + text + '';
message += finalColor;
/*
if (warn)
{
message = "\r\n**** SLOW RESPONSE ****\r\n" + message + "\r\n";
}
*/
console.log(message);
};
next();
});
// kills immediately based on path, headers or other detections
app.use(function(req, res, next) {
var kill = false;
if (req.path.endsWith("/env"))
{
kill = true;
}
if (kill)
{
var text = "KILL, method: " + req.method + ", url: " + req.url;
if (req.headers)
{
text += ", headers: " + JSON.stringify(req.headers);
}
if (req.query)
{
text += ", query: " + JSON.stringify(req.query);
}
console.log(text);
// are we being spoofed? kill the connection
res.blocked = true;
res.writeHead(503, { 'Content-Type': 'application/json' });
return res.end(JSON.stringify({"error": true, "message": "Bad Request."}));
}
next();
});
// common interceptors and config
main.common1(app);
// general logging of requests
// gather statistics on response time
app.use(responseTime(function (req, res, time) {
var warn = false;
if (time > 1000)
{
warn = true;
}
var requestPath = req.originalPath;
if (requestPath)
{
var filter = false;
if (requestPath.indexOf("/login") > -1)
{
filter = true;
}
if (requestPath.indexOf("/token") > -1)
{
filter = true;
}
if (filter)
{
requestPath = util.stripQueryStringFromUrl(requestPath);
}
}
var m = "";
if (res.blocked)
{
m += "*BLOCKED* ";
}
m += req.method + " " + requestPath + " [" + res.statusCode + "]";
m += " (" + time.toFixed(2) + " ms)";
req.log(m, warn);
}));
// set up CORS allowances
// this lets CORS requests float through the proxy
app.use(main.ensureCORS());
// set up default security headers
app.use(main.ensureHeaders());
// common interceptors and config
main.common2(app);
// APPLY CUSTOM DRIVER FUNCTIONS
runFunctions(config.driverFunctions, [app], function(err) {
// binds gitana driver into place
main.common3(app);
// parse cookies
app.use(cookieParser());
// cloudcms things need to run here
main.common4(app, true);
// APPLY CUSTOM FILTER FUNCTIONS
runFunctions(config.filterFunctions, [app], function (err) {
// PATH BASED PERFORMANCE CACHING
main.perf1(app);
// proxy - anything that goes to /proxy is handled here early and nothing processes afterwards
main.proxy(app);
// MIMETYPE BASED PERFORMANCE CACHING
main.perf2(app);
// DEVELOPMENT BASED PERFORMANCE CACHING
main.perf3(app);
// standard body parsing + a special cloud cms body parser that makes a last ditch effort for anything
// that might be JSON (regardless of content type)
app.use(function (req, res, next) {
multipart(process.configuration.bodyParsers.multipart || {})(req, res, function (err) {
bodyParser.json(process.configuration.bodyParsers.json || {})(req, res, function (err) {
bodyParser.urlencoded(process.configuration.bodyParsers.urlencoded || {})(req, res, function (err) {
main.bodyParser()(req, res, function (err) {
next(err);
});
});
});
});
});
if (initializedSession)
{
app.use(initializedSession);
app.use(flash());
}
// this is the same as calling
// app.use(passport.initialize());
// except we create a new passport each time and store on request to support multitenancy
app.use(function(req, res, next) {
var passport = new Passport();
passport._key = "passport-" + req.virtualHost;
req._passport = {};
req._passport.instance = passport;
if (req.session && req.session[passport._key])
{
// load data from existing session
req._passport.session = req.session[passport._key];
}
// add this in
req.passport = req._passport.instance;
// passport - serialize and deserialize
req.passport.serializeUser(function(user, done) {
done(null, user);
});
req.passport.deserializeUser(function(user, done) {
done(null, user);
});
next();
});
// passport session
if (initializedSession)
{
app.use(function(req, res, next) {
req.passport.session()(req, res, next);
});
}
// welcome files
main.welcome(app);
// configure cloudcms app server command handing
main.interceptors(app, true);
//app.use(app.router);
// healthcheck middleware
main.healthcheck(app);
// APPLY CUSTOM ROUTES
runFunctions(config.routeFunctions, [app], function (err) {
// configure cloudcms app server handlers
main.handlers(app, true);
// register error functions
runFunctions(config.errorFunctions, [app], function (err) {
// APPLY CUSTOM CONFIGURE FUNCTIONS
var allConfigureFunctions = [];
for (var env in config.configureFunctions)
{
var functions = config.configureFunctions[env];
if (functions)
{
for (var i = 0; i < functions.length; i++)
{
allConfigureFunctions.push(functions[i]);
}
}
}
runFunctions(allConfigureFunctions, [app], function (err) {
// create the server (either HTTP or HTTPS)
createHttpServer(app, function(err, httpServer) {
if (err) {
return startServerFinishedFn(err);
}
startServerFinishedFn(null, app, httpServer);
});
});
});
});
});
});
});
});
});
});
};
var createHttpServer = function(app, done)
{
// create the server (either HTTP or HTTPS)
var httpServer = null;
if (process.configuration.https)
{
if (app)
{
// configure helmet to support auto-upgrade of http->https
app.use(helmet());
}
// create https server
httpServer = https.createServer(process.configuration.https, app);
}
else
{
// legacy
httpServer = http.Server(app);
}
// request timeout
var requestTimeout = 120000; // 2 minutes
if (process.configuration && process.configuration.timeout)
{
requestTimeout = process.configuration.timeout;
}
httpServer.setTimeout(requestTimeout, function(socket) {
try { socket.end(); } catch (e) { }
try { socket.destroy(); } catch (e) { }
});
var c = 0;
// socket
httpServer.on("connection", function (socket) {
//console.log("[SOCKET CONNECTION] " + socket);
socket.setNoDelay(true);
socket.setTimeout(requestTimeout, function(socket) {
try { socket.end(); } catch (e) { }
try { socket.destroy(); } catch (e) { }
});
});
done(null, httpServer);
}
var configureServer = function(config, app, httpServer, configureServerFinishedFn)
{
var io = httpServer.io;
if (io)
{
//io.set('transports', config.socketTransports);
io.use(function (socket, next) {
// console.log("New socket being initialized");
// attach _log function
socket._log = function (text) {
var host = socket.handshake.headers.host;
if (socket.handshake.headers["x-forwarded-host"])
{
host = socket.handshake.headers["x-forwarded-host"];
}
var d = new Date();
var dateString = d.toDateString();
var timeString = d.toTimeString();
// gray color
var grayColor = "\x1b[90m";
// final color
var finalColor = "\x1b[0m";
if (process.env.CLOUDCMS_APPSERVER_MODE === "production")
{
grayColor = "";
finalColor = "";
}
var message = '';
message += grayColor + '<socket> ';
message += grayColor + '[' + dateString + ' ' + timeString + '] ';
message += grayColor + host + ' ';
message += grayColor + text + '';
message += finalColor;
console.log(message);
};
/*
socket.on("connect", function () {
console.log("Socket connect()");
});
*/
/*
socket.on("disconnect", function () {
var message = "Socket disconnected";
if (socket && socket.host)
{
message += ", host=" + socket.host;
}
if (socket && socket.gitana && socket.gitana.application && socket.gitana.application())
{
message += ", application=" + socket.gitana.application().title;
}
console.log(message);
});
*/
// APPLY CUSTOM SOCKET.IO CONFIG
runFunctions(config.socketFunctions, [socket], function (err) {
require("../middleware/awareness/awareness").initSocketIO(io, function() {
next();
});
// INSIGHT SERVER
// if (config.insight && config.insight.enabled)
// {
// console.log("Init Insight to Socket");
// require("../insight/insight").init(socket, function () {
// next();
// });
// }
// else
// {
// next();
// }
});
});
}
// SET INITIAL VALUE FOR SERVER TIMESTAMP
process.env.CLOUDCMS_APPSERVER_TIMESTAMP = Date.now();
// APPLY SERVER BEFORE START FUNCTIONS
runFunctions(config.beforeFunctions, [app], function (err) {
// AFTER SERVER START
runFunctions(config.afterFunctions, [app], function (err) {
function cleanup() {
if (cluster.isMaster)
{
console.log("");
console.log("");
console.log("Cloud CMS Module shutting down");
// close server connections as cleanly as we can
console.log(" -> Closing server connections");
}
try
{
httpServer.close();
}
catch (e)
{
console.log("Server.close produced error: " + JSON.stringify(e));
}
if (cluster.isMaster)
{
console.log("");
}
// tell the process to exit
process.exit();
}
// listen for kill or interrupt so that we can shut down cleanly
process.on('SIGINT', cleanup);
process.on('SIGTERM', cleanup);
configureServerFinishedFn();
});
});
};
////////////////////////////////////////////////////////////////////////////
//
// DEFAULT HANDLERS
//
////////////////////////////////////////////////////////////////////////////
// default before function
before(function (app, callback) {
callback();
});
// default after function
after(function (app, callback) {
callback();
});