zetta-app
Version:
Zetta Toolkit - Application Framework
1,148 lines (941 loc) • 38.9 kB
JavaScript
//
// -- Zetta Toolkit - Application Framework
//
// Copyright (c) 2011-2014 ASPECTRON Inc.
// All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
var _ = require('underscore');
var fs = require('fs');
var os = require('os');
var util = require('util');
var events = require('events');
var cluster = require('cluster');
var http = require('http');
var https = require('https');
var connect = require('connect');
var express = require('express');
var socketio = require("socket.io");
var path = require('path');
var UUID = require('node-uuid');
var crypto = require('crypto');
var CookieSignature = require('cookie-signature');
var Cookie = require('cookie');
// var Cookies = require('cookies');
var zutils = require('zetta-utils');
var zstats = require('zetta-stats');
var zrpc = require('zetta-rpc');
var exec = require('child_process').exec;
var getmac = require('getmac');
var nodemailer = require('nodemailer');
var pickupTransport = require('nodemailer-pickup-transport');
var mongo = require('mongodb');
var os = require('os');
var child_process = require('child_process');
var Translator = require('zetta-translator');
var ClientRPC = require('./lib/client-rpc');
var Login = require('./lib/login');
var _log_module_enable_ = process.argv.join(' ').match(/--log-module/ig);
var _log_module_ = null;
var __cluster_worker_id = process.env['ZETTA_CLUSTER_ID'];
var _cl = console.log;
console.log = function() {
var args = Array.prototype.slice.call(arguments, 0);
if(__cluster_worker_id !== undefined)
args.unshift('['+__cluster_worker_id+'] ');
if(_log_module_enable_ && _log_module_)
args.unshift('['+_log_module_+'] ');
args.unshift(zutils.tsString()+' ');
return _cl.apply(console, args);
}
function merge(dst, src) {
_.each(src, function(v, k) {
if(_.isArray(v)) { dst[k] = [ ]; merge(dst[k], v); }
else if(_.isObject(v)) { if(!dst[k] || _.isString(dst[k]) || !_.isObject(dst[k])) dst[k] = { }; merge(dst[k], v); }
else { if(_.isArray(src)) dst.push(v); else dst[k] = v; }
})
}
function getConfig(name) {
var filename = name+'.conf';
var host_filename = name+'.'+os.hostname()+'.conf';
var local_filename = name+'.local.conf';
var data = [ ]; // undefined;
fs.existsSync(filename) && data.push(fs.readFileSync(filename) || null);
fs.existsSync(host_filename) && data.push(fs.readFileSync(host_filename) || null);
fs.existsSync(local_filename) && data.push(fs.readFileSync(local_filename) || null);
if(!data[0] && !data[1]) {
console.error("Unable to read config file:"+(filename+'').magenta.bold);
throw new Error("Unable to read config file:"+(filename+''));
}
var o = { }
_.each(data, function(conf) {
if(!conf || !conf.toString('utf-8').length)
return;
var layer = eval('('+conf.toString('utf-8')+')');
merge(o, layer);
})
return o;
}
function flash(options) {
options = options || {};
var safe = (options.unsafe === undefined) ? true : !options.unsafe;
return function(req, res, next) {
if (req.flash && safe) { return next(); }
req.flash = _flash;
req.redirectWithMsg = function(path, type, msg){
req.flash(type, msg);
res.redirect(path);
}
next();
}
}
function _flash(type, msg) {
if (this.session === undefined) throw Error('req.flash() requires sessions');
var msgs = this.session.flash = this.session.flash || {};
if (type && msg) {
// util.format is available in Node.js 0.6+
if (arguments.length > 2 && util.format) {
var args = Array.prototype.slice.call(arguments, 1);
msg = util.format.apply(undefined, args);
} else if (util.isArray(msg)) {
msg.forEach(function(val){
(msgs[type] = msgs[type] || []).push(val);
});
return msgs[type].length;
}
return (msgs[type] = msgs[type] || []).push(msg);
} else if (type) {
var arr = msgs[type];
delete msgs[type];
return arr || [];
} else {
this.session.flash = {};
return msgs;
}
}
function readJSON(filename) {
if(!fs.existsSync(filename))
return undefined;
var text = fs.readFileSync(filename, { encoding : 'utf-8' });
if(!text)
return undefined;
try {
return JSON.parse(text);
} catch(ex) {
console.log("Error parsing file:",filename);
console.log(ex);
console.log('Offending content follows:',text);
}
return undefined;
}
function writeJSON(filename, data) {
fs.writeFileSync(filename, JSON.stringify(data, null, '\t'));
}
GLOBAL.dpc = function(t,fn) { if(typeof(t) == 'function') setImmediate(t); else setTimeout(fn,t); }
function asyncMap(_list, fn, callback) {
if(!_list || !_.isArray(_list))
return callback(new Error("asyncMap() supplied argument is not array"));
var list = _list.slice();
var result = [ ]
digest();
function digest() {
var item = list.shift();
if(!item)
return callback(null, result);
fn(item, function(err, data) {
if(err)
return callback(err);
data && result.push(data);
dpc(digest);
})
}
}
function asyncLoop(fn, callback) {
digest();
function digest() {
fn(function(err, done) {
if(err)
return callback(err);
if(done === true)
return callback();
dpc(digest);
})
}
}
function asyncSteps() {
var self = this;
self.steps = [ ]
self.push = function(fn) {
self.steps.push(fn);
}
self.run = function(callback) {
run_step();
function run_step() {
var step = self.steps.shift();
if(!step)
return callback();
step.call(this, function(err) {
if(err)
return callback(err);
run_step();
});
}
}
}
function Application(appFolder, appConfig) {
var self = this;
events.EventEmitter.call(this);
self.appFolder = appFolder;
self.pkg = readJSON(path.join(appFolder,'package.json'));
if(!self.pkg)
throw new Error("Application Unable to read package.json");
if(!self.pkg.name)
throw new Error("package.json must contain module 'name' field");
_log_module_ = self.pkg.name;
self.getConfig = function(name) { return getConfig(path.join(appFolder,'config', name)) }
self.readJSON = readJSON;
self.writeJSON = writeJSON;
self.asyncLoop = asyncLoop;
self.asyncMap = asyncMap;
self.asyncSteps = asyncSteps;
self.config = self.getConfig(self.pkg.name);
self.settings = { }
http.globalAgent.maxSockets = self.config.maxHttpSockets || 1024; // 1024;
https.globalAgent.maxSockets = self.config.maxHttpSockets || 1024;
if(process.platform != 'win32' && self.config.maxSockets) {
if(fs.existsSync('node_modules/posix')) {
try { require('posix').setrlimit('nofile', self.config.maxSockets); } catch(ex) {
console.error(ex.stack);
}
}
else
console.log("WARNING - Please install POSIX module (npm install posix)".red.bold);
}
self.pingDataObject = { }
self.isCluster = self.config.http && self.config.http.cluster;
self.isMaster = (!self.isCluster) || (self.isCluster && cluster.isMaster);
self.isWorker = (!self.isCluster) || (self.isCluster && cluster.isWorker);
// ---
self.restoreDefaultSettings = function(name, force) {
var filename = path.join(self.appFolder,'config', name+'.settings');
if(!fs.existsSync(filename)) {
self.settings = { }
return;
}
var data = fs.readFileSync(filename);
self.settings = eval('('+data.toString('utf-8')+')');
}
self.restoreSettings = function(name) {
self.restoreDefaultSettings(name);
var host_filename = path.join(self.appFolder,'config', name+'.'+os.hostname().toLowerCase()+'.settings');
if(!fs.existsSync(host_filename))
return;
var data = fs.readFileSync(host_filename);
var settings = eval('('+data.toString('utf-8')+')');
merge(self.settings, settings);
}
self.storeSettings = function(name) {
var host_filename = path.join(self.appFolder,'config', name+'.'+os.hostname().toLowerCase()+'.settings');
fs.writeFileSync(host_filename, JSON.stringify(self.settings, null, '\t'));
}
// ---
self.initTranslator = function(callback) {
if(self.config.translator) {
var options = {
storagePath: path.join(appFolder,'config'),
rootFolderPath: appFolder
};
options = _.extend(self.config.translator, options);
self.translator = new Translator(options, function() {
self.translator.separateEditor();
});
}
callback();
}
self.initCertificates = function(callback) {
if(self.verbose)
console.log('zetta-app: loading certificates from ',appFolder+'/'+self.config.certificates);
if(self.certificates) {
console.error("Warning! initCertificates() is called twice!".redBG.bold);
callback && callback();
return;
}
if(typeof(self.config.certificates) == 'string') {
self.certificates = {
key: fs.readFileSync(path.join(appFolder,self.config.certificates)+'.key').toString(),
cert: fs.readFileSync(path.join(appFolder,self.config.certificates)+'.crt').toString(),
ca: [ ]
}
}
else
{
self.certificates = {
key: fs.readFileSync(path.join(appFolder,self.config.certificates.key)).toString(),
cert: fs.readFileSync(path.join(appFolder,self.config.certificates.crt)).toString(),
ca: [ ]
}
var cert = [ ]
var chain = fs.readFileSync(path.join(appFolder, self.config.certificates.ca)).toString().split('\n');
_.each(chain, function(line) {
cert.push(line);
if(line.match('/-END CERTIFICATE-/')) {
self.certificates.ca.push(cert.join('\n'));
cert = [ ]
}
})
}
callback && callback();
}
self.initMonitoringInterfaces = function(callback) {
self.stats = new zstats.StatsD(self.config.statsd, self.uuid, self.pkg.name);
// self.stats = new zstats.StatsD(self.config.statsd, self.uuid, self.config.application);
self.profiler = new zstats.Profiler(self.stats);
self.monitor = new zstats.Monitor(self.stats, self.config.monitor);
callback();
}
/* var databaseConfig = [
{
config: 'main',
collections: [
{collection: 'resources', indexes: 'owner'},
{collection: 'users', indexes: 'email->unique|google.id|facebook.id'}
]
}
];
*/
self.initMailer = function(callback) {
var pickupFolder = path.join(self.appFolder,"mailer");
if(self.config.mailer.pickup) {
self.mailer = nodemailer.createTransport( pickupTransport({
directory: pickupFolder
}))
}
else
{
self.mailer = nodemailer.createTransport(self.config.mailer);
}
callback();
}
self.initDatabaseConfig = function(callback) {
var dbconf = self.databaseConfig;
console.log("Connecting to database...".bold);
self.db = { }
self.databases = { }
initDatabaseConnection();
function initDatabaseConnection() {
var config = dbconf.shift();
if (!config)
return callback();
var name = config.config;
var db = self.config.mongodb[name];
if(!db)
throw new Error("Config missing database configuration for '"+name+"'");
mongo.MongoClient.connect(db, function (err, database) {
if (err)
return callback(err);
self.databases[name] = database;
var lp = self.config.mongodb[name].indexOf('@');
console.log("DB '" + (name) + "' connected", lp == -1 ? self.config.mongodb[name].bold : self.config.mongodb[name].substring(lp+1).bold );
zutils.bind_database_config(database, config.collections, function (err, db) {
if (err)
return callback(err);
_.extend(self.db, db);
initDatabaseConnection();
})
})
}
}
self.initDatabaseCollections = function(callback) {
console.log("Connecting to database...".bold);
self.db = { }
self.databases = { }
mongo.MongoClient.connect(self.config.mongodb, function (err, database) {
if (err)
return callback(err);
self.database = database;
console.log("Database connected", self.config.mongodb);
zutils.bind_database_config(database, self.databaseCollections, function (err, db) {
if (err)
return callback(err);
_.extend(self.db, db);
callback();
})
})
}
self.getHttpSessionSecret = function() {
if(self._httpSessionSecret)
return self._httpSessionSecret;
self._httpSessionSecret = crypto.createHash('sha256').update(self.uuid+self.config.http.session.secret).digest('hex');
return self._httpSessionSecret;
}
self.getBaseUrl = function(req, locale){
var _locale = '';
if (locale === true) {
_locale = '/'+req._T.locale;
}else if (locale) {
_locale = '/'+locale;
};
return req.protocol + '://' + req.get('host')+_locale+'/';
}
self.initExpressConfig = function(callback) {
var ExpressSession = require('express-session');
self.express = express;
self.app = express();
self.app.locals.getBaseUrl = self.getBaseUrl;
self.app.sessionSecret = self.getHttpSessionSecret();
self.app.set('views', path.join(appFolder,'views'));
self.app.set('view engine', self.config.http.engine || 'ejs');
(self.config.http.engine == 'ejs') && self.app.engine('html', require('ejs').renderFile);
if(self.config.http.engine == 'ect') {
var ECT = require('ect');
self.ectRenderer = ECT({ watch : true, root: path.join(appFolder,'views'), ect : '.ect' });
self.app.engine('ect', self.ectRenderer.render);
}
//self.app.use(require('body-parser')());//express.json());
self.app.use(require('body-parser').urlencoded({ extended: true }));
self.app.use(require('body-parser').json());
self.app.use(require('method-override')());
self.app.use(require('cookie-parser')(self.app.sessionSecret));
self.app.use(flash({unsafe: false}));
if(self.config.mongodb) {
var MongoStore = require('connect-mongo')(ExpressSession);
self.app.sessionStore = new MongoStore({url: self.config.mongodb.sessionStore || self.config.mongodb.main || self.config.mongodb});
self.app.use(ExpressSession({
secret: self.app.sessionSecret,
key: self.config.http.session.key,
cookie: self.config.http.session.cookie,
store: self.app.sessionStore,
saveUninitialized: true,
resave: true
}));
return callback();
}
else
if(self.config.http && self.config.http.session) {
// self.app.sessionStore = new Cookies();
self.app.sessionStore = new ExpressSession.MemoryStore;
self.expressSession = ExpressSession({
secret: self.app.sessionSecret,
key: self.config.http.session.key,
cookie: self.config.http.session.cookie,
store: self.app.sessionStore,
saveUninitialized: true,
resave: true
})
//console.log(self.expressSession);
self.app.use(self.expressSession);
/*
var CookieSession = require('cookie-session');
self.app.use(CookieSession({
secret: self.app.sessionSecret,
key: self.config.http.session.key,
}));
*/
return callback();
}
}
self.initExpressHandlers = function(callback) {
var ErrorHandler = require('errorhandler')();
var isErrorView = fs.existsSync(path.join(self.appFolder,'views','error.ejs'));
self.handleHttpError = function(response, req, res, next) {
if(req.xhr) {
res.json({errors: _.isArray(response.errors) ? response.errors : [response.errors]});
return;
}
else
if(isErrorView) {
res.render('error', { error : response.error });
return;
}
else {
res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.end("Server Error");
return;
}
}
if(self.config.translator)
self.app.use(self.translator.useSession);
/**
* response = {
* status: {Number}
* errors: {String | Array}
* }
*/
function HttpError(response) {
res.status(response.status);
self.handleHttpError(respo)
}
self.app.use(function(req, res, next) {
res.sendHttpError = function (response) {
self.handleHttpError(response, req, res, next);
}
next();
});
if(self.router)
self.router.init(self.app);
self.emit('init::express', self.app);
if(self.config.http.static) {
var ServeStatic = require('serve-static');
_.each(self.config.http.static, function(dst, src) {
console.log('HTTP serving '+src.cyan.bold+' -> '+dst.cyan.bold);
self.app.use(src, ServeStatic(path.join(appFolder, dst)));
})
}
self.emit('init::express::error', self.app);
self.emit('init::express::error-handlers', self.app);
/**
* Handles errors were sent via next() method
*
* following formats are supported:
* next(new Error('Something blew up'));
* next(400);
* next({status: 400, errors: 'Activation code is wrong'});
* next({status: 400, errors: ['Activation code is wrong']});
*
*/
self.app.use(function (err, req, res, next) {
if (typeof err == 'number') {
err = {
status: err,
errors: http.STATUS_CODES[err] || "Error"
};
}
else
if (typeof err == 'string') {
console.error(err);
err = {
status: 500,
errors: 'Internal Server Error'
};
}
else
if (err instanceof Error) {
if (self.config.development) {
err.status = 500;
return ErrorHandler(err, req, res, next);
}
else
{
console.error(err.stack);
err = {
status: 500,
errors: 'Internal Server Error'
};
}
}
res.sendHttpError(err);
});
self.emit('init::express::done', self.app);
finish();
function finish() {
callback();
}
};
self.initACME = function(callback) {
var desiredIdentifier = self.config.acme.fqdn;
var acmeServer = self.config.acme.authServer || "www.letsencrypt-demo.org";
var authzURL = "https://" + acmeServer + "/acme/new-authz";
var certURL = "https://" + acmeServer + "/acme/new-cert";
console.log("Obtaining ACME Certificates..".bold);
acme.getMeACertificate(authzURL, certURL, desiredIdentifier, function(x) {
console.log("Result of getMeACertificate:");
console.log(x);
callback();
});
}
self.secureUnderUser = function(callback) {
var username = self.config.secureUnderUser;
if(process.platform != 'win32') {
exec('id -u '+username, function(err, stdout, stderr) {
if(!err) {
var uid = parseInt(stdout);
if(uid) {
console.log('Setting process UID to:',uid);
process.setuid(uid);
}
}
callback();
});
}
else {
callback();
}
}
self.initHttpServer = function(callback) {
var CERTIFICATES = (self.config.http.ssl && self.config.certificates) ? self.certificates : null;
var server = CERTIFICATES ? https.createServer(CERTIFICATES, self.app) : http.createServer(self.app);
self.io = socketio.listen(server, { 'log level': 0, 'secure': CERTIFICATES ? true : false });
if(self.router && self.router.initWebSocket)
self.router.initWebSocket(self.io);
self.config.websocket && self.initWebsocket_v1(function(){});
/*if(self.config.http.rpc) {
if(typeof(self.config.http.rpc == 'string'))
self.clientRPC = new ClientRPC(self, { path : self.config.http.rpc })
else
self.clientRPC = new ClientRPC(self, self.config.http.rpc);
}*/
self.emit('init::websockets');
self.emit('init::http::done');
var args = [ ]
args.push(self.config.http.port);
self.config.http.host && args.push(self.config.http.host);
args.push(function (err) {
if (err) {
console.error("Unable to start HTTP(S) server on port " + self.config.http.port + (self.config.http.host ? " host '"+self.config.http.host+"'" : ''));
return callback(err);
}
console.log('HTTP server listening on port ' + (self.config.http.port+'').bold + (self.config.http.host ? " host '"+self.config.http.host+"'" : ''));
if (!CERTIFICATES)
console.log(("WARNING - SSL is currently disabled").yellow.bold);
self.emit('init::http-server')
callback();
})
server.listen.apply(server, args);
};
self.initRedirectToSSL = function(callback) {
var port = 80;
var http_app = express();
http_app.get('*', function (req, res) {
res.redirect("https://" + req.headers.host + req.url);
})
http.createServer(http_app).listen(port, function(err) {
if (err) {
console.error(("Unable to start HTTP redirector on port" + port).magenta.bold);
return callback(err);
}
console.log("HTTP redirector listening on port " + (port+'').bold);
callback && callback();
});
}
self.initCluster = function(callback) {
if(cluster.isMaster) {
self.workers = []
var nWorkers = os.cpus().length;
if(self.config.http.cluster && self.config.http.cluster.workers)
nWorkers = self.config.http.cluster.workers;
else
if(parseInt(self.config.http.cluster))
nWorkers = parseInt(self.config.http.cluster);
console.log("Cluster: Spawning "+nWorkers+" workers");
var runWorker = function(i) {
var worker = self.workers[i] = cluster.fork({ ZETTA_CLUSTER_ID : i });
// Optional: Restart worker on exit
worker.on('exit', function(worker, code, signal) {
console.log('respawning worker', i);
spawn(i);
});
worker.on('message', function(msg) {
if(!msg.op) {
console.log("Unknown message from worker:",msg);
return;
}
// msg.worker = worker;
// msg.worker_id = i;
self.emit(msg.op,msg,worker);
});
};
// Spawn workers.
for (var i = 0; i < nWorkers; i++)
runWorker(i);
}
else
if(cluster.isWorker)
{
self.__cluster_worker_id = parseInt(process.env['ZETTA_CLUSTER_ID']);
process.on('message', function(msg) {
if(!msg.op) {
console.log("Unknown message from master:", msg);
return;
}
self.emit(msg.op,msg);
})
}
callback();
}
self.initSupervisors = function(callback) {
// console.log("initSupervisors");
if(!self.certificates)
throw new Error("Application supervisor requires configured certificates");
console.log("Connecting to supervisor(s)...".bold, self.config.supervisor.address);
self.supervisor = new zrpc.Client({
address: self.config.supervisor.address,
auth: self.config.supervisor.auth,
certificates: self.certificates,
node: self.mac,
mac: self.mac,
uuid : self.uuid,
designation: self.pkg.name, // self.config.application,
pingDataObject : self.pingDataObject
});
self.supervisor.registerListener(self);
callback();
self.on('package::info::get', function(msg) {
console.log(msg.op.yellow.bold);
self.supervisor.dispatch({ op : 'package::info::data', uuid : self.uuid, pkg : self.pkg })
})
}
self.initWebsocket_v1 = function(callback) {
self.webSocketMap = [ ]
self.webSockets = self.io.of(self.config.websocket.path).on('connection', function(socket) {
console.log("websocket "+socket.id+" connected");
self.emit('websocket::connect', socket);
self.webSocketMap[socket.id] = socket;
socket.on('disconnect', function() {
self.emit('websocket::disconnect', socket);
delete self.webSocketMap[socket.id];
console.log("websocket "+socket.id+" disconnected");
})
socket.on('rpc::request', function(msg) {
try {
var listeners = self.listeners('ws::'+msg.req.op);
if(listeners.length == 1) {
listeners[0].call(socket, msg.req, function(err, resp) {
socket.emit('rpc::response', {
_resp : msg._req,
err : err,
resp : resp,
});
})
}
else
if(listeners.length)
{
socket.emit('rpc::response', {
_resp : msg._req,
err : { error : "Too many handlers for '"+msg.req.op+"'" }
});
}
else
{
socket.emit('rpc::response', {
_resp : msg._req,
err : { error : "No such handler '"+msg.req.op+"'" }
});
}
}
catch(ex) { console.error(ex.stack); }
});
socket.on('message', function(msg) {
try {
self.emit('ws::'+msg.op, msg, socket);
}
catch(ex) { console.error(ex.stack); }
});
});
callback();
}
self.getClientIp = function(req) {
var ipAddress = req.query.ip;
if(!ipAddress) {
ipAddress = req.header('CF-Connecting-IP');
}
if(!ipAddress) {
// This is cloud-flare based
var forwardedIpsStr = req.header('X-Forwarded-For');
if (forwardedIpsStr) {
var forwardedIps = forwardedIpsStr.split(',');
ipAddress = forwardedIps.pop();
}
}
// If using nginx proxy, one must add the following directive to location
// proxy_set_header X-Real-IP $remote_addr;
if(!ipAddress)
ipAddress = req.header('X-Real-IP');
if (!ipAddress)
ipAddress = req.connection.remoteAddress;
return ipAddress;
}
function unsignCookies(obj, secret){
var ret = {};
Object.keys(obj).forEach(function(key){
var val = obj[key];
if (0 == val.indexOf('s:')) {
val = CookieSignature.unsign(val.slice(2), secret);
if (val) {
ret[key] = val;
delete obj[key];
}
}
});
return ret;
};
self.getSocketSession = function(socket, callback) {
var cookies = null;
try {
cookies = unsignCookies(Cookie.parse(socket.handshake.headers.cookie || ''), self.getHttpSessionSecret());
} catch(ex) {
console.log(ex.stack);
return callback(ex, null);
}
var cookieId = (self.config.http && self.config.http.session && self.config.http.session.key)? self.config.http.session.key : 'connect.sid';
var sid = cookies[ cookieId ];
if(self.app.sessionStore)
return self.app.sessionStore.get(sid, function(err, session){
if (!session)
return callback(err);
session.id = sid;
callback(err, session);
});
else
callback(null, cookies);
};
self.getSocketSessionId = function(socket) {
//console.log("HANDSHAKE HEADERS COOKIE:".blueBG.bold, socket.handshake.headers.cookie);
//console.log("HTTP SECRET:".blueBG.bold,self.config.http.session.secret);
var cookies = unsignCookies(Cookie.parse(socket.handshake.headers.cookie), core.getHttpSessionSecret());
// console.log("COOKIES:".blueBG.bold,cookies);
var sid = cookies['connect.sid'];
return sid;
};
// --
var defaultRouting_ = {}
self.defaultRouting = function(src, dest) {
defaultRouting_.src = src;
defaultRouting_.dest = dest;
}
self.route = function(rec) {
console.log(("INSTALLING ROUTE..."+rec.op).green.bold);
var src = rec.src || defaultRouting_.src;
var dest = rec.dest || defaultRouting_.dest;
src.on(rec.op, function(args, callback) {
console.log("PROCESSING ROUTE".magenta.bold,args.op);
if(rec.private && !args.token)
return callback({ error : "User must be logged in." })
if(rec.filter) {
var src = args;
var dst = { }
//_.each(rec.filter, function(info, ident) {
for(var ident in args) {
var arg = args[ident];
//if(info === true && !arg)
// return callback({ error : "Validation filter failure '"+ident+"'"});
dst[ident] = arg;
}
args = dst;
}
dest.dispatch(args, callback);
})
}
// --
function updateServerStats() {
self.pingDataObject.loadAvg = self.monitor.stats.loadAvg;
self.pingDataObject.memory = self.monitor.stats.memory;
dpc(5 * 1000, updateServerStats)
}
// --
var initStepsBeforeHttp_ = [ ]
var initSteps_ = [ ]
self.initBeforeHttp = function(fn) {
initStepsBeforeHttp_.push(fn);
}
self.init = function(fn) {
initSteps_.push(fn);
}
self.run = function(callback) {
var steps = new zutils.Steps();
self.emit('init::begin', steps);
self.config.acme && steps.push(self.initACME);
// self.config.translator && steps.push(self.initTranslator);
self.config.certificates && steps.push(self.initCertificates);
self.config.statsd && steps.push(self.initMonitoringInterfaces);
self.config.mongodb && self.databaseConfig && steps.push(self.initDatabaseConfig);
self.config.mongodb && self.databaseCollections && steps.push(self.initDatabaseCollections);
self.emit('init::database', steps);
_.each(initStepsBeforeHttp_, function(fn) {
steps.push(fn);
})
if(self.config.http) {
if(self.isCluster)
steps.push(self.initCluster);
if(self.isWorker) {
steps.push(self.initExpressConfig);
steps.push(self.initExpressHandlers);
steps.push(self.initHttpServer);
}
self.config.http.redirectToSSL && steps.push(self.initRedirectToSSL);
}
self.config.mailer && steps.push(self.initMailer);
self.isMaster && self.config.supervisor && self.config.supervisor.address && steps.push(self.initSupervisors);
if(self.config.secure_under_username) {
self.config.secureUnderUser = self.config.secure_under_username;
console.log("WARNING: config.secure_under_username is DEPRECATED, please use config.secureUnderUser".yellow.bold);
}
self.config.secureUnderUser && steps.push(self.secureUnderUser);
getmac.getMac(function (err, mac) {
if (err) return callback(err);
self.mac = mac.split(process.platform == 'win32' ? '-' : ':').join('').toLowerCase();
self.macBytes = _.map(self.mac.match(/.{1,2}/g), function(v) { return parseInt(v, 16); })
var uuid = self.appFolder.replace(/\\/g,'/').split('/').pop();
if(!uuid || uuid.length != 36) {
var local = readJSON('uuid');
if(local && local.uuid)
uuid = local.uuid;
else {
uuid = UUID.v1({ node : self.macBytes });
Application.writeJSON("uuid", { uuid : uuid });
}
}
self.uuid = uuid;
self.isMaster && console.log("App UUID:".bold,self.uuid.bold);
_.each(initSteps_, function(fn) {
steps.push(fn);
})
self.emit('init::build', steps);
steps.run(function (err) {
if (err)
throw err;
self.config.statsd && updateServerStats();
console.log("init OK".bold);
self.emit('init::done');
callback && callback();
})
})
return self;
}
self.caption = self.pkg.name;
dpc(function() {
if(self.isMaster && self.caption) {
zutils.render(self.caption.replace('-',' '), null, function(err, caption) {
console.log('\n'+caption);
dpc(function() {
self.run();
})
})
}
else {
self.run();
}
})
}
util.inherits(Application, events.EventEmitter);
// -----------------------------------------------------------
function Authenticator(core) {
var self = this;
}
Application.getConfig = getConfig;
Application.readJSON = readJSON;
Application.writeJSON = writeJSON;
module.exports = {
Application : Application,
getConfig : getConfig,
inherits : util.inherits,
ClientRPC : ClientRPC,
Login : Login
}