Skip to content

Instantly share code, notes, and snippets.

@vymarkov
Created November 20, 2015 09:13
Show Gist options
  • Save vymarkov/aa79c734a5e7cf6ff41a to your computer and use it in GitHub Desktop.
Save vymarkov/aa79c734a5e7cf6ff41a to your computer and use it in GitHub Desktop.
'use strict';
var os = require('os');
var fs = require('fs');
var path = require('path');
var domain = require('domain');
var util = require('util');
var http = require('http');
var express = require('express');
var session = require('express-session');
var cors = require('cors');
var tv4 = require('tv4');
var passport = require('passport');
var winston = require('winston');
var exception = require('winston/lib/winston/exception');
var async = require('async');
var inflection = require('inflection');
var glob = require('glob');
var moment = require('moment');
var utils = require('momonga-utils');
var env = require('momonga-utils/env');
var _ = require('lodash');
var accepts = require('accepts');
var debug = require('debug')('momonga');
var app = {};
var normalizeConf = function () {
var argv = require('minimist')(process.argv.slice(2));
var conf = {
log: {
colorize: true,
silent: argv.silent || false,
transports: {
console: {
colorize: true,
level: 'debug',
silent: argv.silent || false
}
}
}
};
switch (argv.prod || argv.env || env()) {
case 'production':
conf.log.level = 'info';
break;
default:
conf.log.level = 'debug';
break;
}
conf.log.level = argv.level ? argv.level : conf.log.level;
conf.log.level = argv.verbose ? 'verbose' : conf.log.level;
conf.log.level = argv.debug ? 'debug' : conf.log.level;
conf.log.transports.console.colorize = conf.log.colorize;
conf.log.transports.console.silent = conf.log.silent;
conf.log.transports.console.level = conf.log.level;
return conf;
};
module.exports = function (conf, cb) {
app = this;
app.passport = passport;
if (process.mainModule.filename === path.resolve(process.cwd(), 'app.js')) {
conf = normalizeConf();
}
var initialize = function initialize() {
return new Promise(function (resolve, reject) {
['SIGTERM', 'SIGUSR2'].forEach(function (eventname) {
process.on(eventname, function () {
app.log.verbose('Got %s signal', eventname);
app.log.verbose('Suspending all proceses...');
app.log.verbose('Server closes the connection...');
app.log.verbose('Closes all client connections inside application...');
app.shutdown();
});
});
process.once('SIGINT', function () {
app.log.verbose('Got SIGINT signal.');
app.log.verbose('Finishing all proceses...');
app.log.verbose('Server closes the connection...');
app.log.verbose('Closes all client connections inside application...');
app.log.verbose('Exiting...');
app.shutdown(function () {
setTimeout(function () {
process.exit(0);
}, 100);
});
});
process.once('uncaughtException', function (err) {
app.shutdown(function () {
process.exit(0);
});
});
process.once('exit', function onExit() {
app.shutdown(function () {
process.exit(0);
});
});
app.config.bootstrap(app, function done() {
resolve();
});
});
};
var freezeconfig = function freezeconfig() {
return new Promise(function (resolve, reject) {
_.each(_.without(_.keys(app.config), ['cwd']), function (configKey) {
var oldVal = app.config[configKey];
delete app.config[configKey];
Object.defineProp(app.config, configKey, {
value: oldVal,
enumerable: true,
writable: ['dev', 'test'],
configurable: ['dev', 'test']
});
});
resolve();
});
};
var loadBootScripts = function loadbootscripts() {
return new Promise(function (resolve, reject) {
var dependencies = fs.readdirSync(path.resolve(app.config.cwd, 'node_modules'))
.filter(function (modulename) {
return modulename !== '.bin' && !_.has(app.config.pkg.devDependencies, modulename);
});
try {
async.eachSeries(dependencies, function (pkgname, callback) {
try {
var pkg = require(path.resolve(process.cwd(), 'node_modules', pkgname, 'package.json'));
app.log.silly('Loading a package.json for module "%s"', pkgname, JSON.stringify(pkg, null, 2));
if (_.has(pkg, 'momonga.boot')) {
var pathtoboot = path.resolve(process.cwd(), 'node_modules', pkg.name, pkg.momonga.boot);
// app.log.verbose('Loading a boot script from %s', pathtoboot);
var boot = require(pathtoboot);
boot(app, function (err) {
if (err) {
app.log.warn('An error occurred while trying to load a boot script "%s"', pathtoboot);
}
app.log.debug('Boot script successfully loaded (%s)', pathtoboot);
callback(err);
});
} else {
callback();
}
} catch (e1) {
app.log.warn(e1.message, {
cwd: process.cwd(),
trace: e1.stack
});
callback(e1);
}
}, function done(err) {
if (err) {
reject(err);
}
resolve();
});
} catch (e0) {
console.log(e0);
}
});
};
var prepareHooks = function prepareHooks() {
var hooks = {};
app.controllers = app.controllers || {};
app.controller = function controller(name, controller) {
name = utils.normalizeId(name);
if (arguments.length === 1) {
return app.controllers[name];
} else if (arguments.length === 2) {
if (!_.has(app.controllers, name)) {
app.controllers[name] = controller;
}
}
};
app.models = app.models || {};
app.model = function model(name, model) {
name = utils.normalizeId(name);
if (arguments.length === 1) {
if (_.has(app.models, name)) {
return app.models[name];
}
name = name.toLowerCase();
if (_.has(app.models, name)) {
return app.models[name];
}
name = inflection.camelize(name);
if (_.has(app.models, name)) {
return app.models[name];
}
} else if (arguments.length === 2) {
if (!_.has(app.models, name)) {
app.models[name] = model;
}
}
};
app.configure = function configure(envs, handler) {
if (arguments.length === 1 && _.isFunction(envs)) {
handler = envs;
switch (app.config.env) {
case 'development':
case 'testing':
handler();
break;
}
} else if (_.isArray(envs) && _.isFunction(handler)) {
if (_.contains(envs, app.config.env)) {
handler();
}
} else if (_.isString(envs) && _.isFunction(handler)) {
if (app.config.env === envs) {
handler();
}
}
};
app.hook = function hook(hookname, hook) {
if (arguments.length === 1 && _.isString(hookname)) {
return hooks[hookname] || [];
}
if (_.isString(hookname) && _.isFunction(hook)) {
hookname = hookname.toLowerCase();
if (!_.has(hooks, hookname)) {
Object.defineProp(hooks, hookname, {
value: [],
writable: ['dev', 'test'],
configurable: ['dev', 'test'],
enumerable: true
});
}
hooks[hookname].push(hook);
}
};
return new Promise(function (resolve, reject) {
app.hook('beforetart', function storePidfile(app, cb) {
try {
if (!fs.existsSync('.momonga')) {
fs.mkdirSync('.momonga');
}
fs.writeFileSync('.momonga/momonga.pid', process.pid + '\n');
cb();
} catch (e1) {
app.log.warn('Something went wrong...');
app.log.warn(e1.message);
cb(e1);
}
});
app.hook('beforeshutdown', function clearPidfile(app, cb) {
try {
fs.truncateSync('.momonga/momonga.pid', 0);
cb();
} catch (e1) {
app.log.warn('Something went wrong...');
app.log.warn(e1.message);
cb(e1);
}
});
['beforeStart',
'afterStart',
'beforeRestart',
'beforeShutdown'
].map(function (hookname) {
return app.config.hooks[hookname];
}).filter(function (hooks) {
return !_.isEmpty(hooks);
}).forEach(function (hooks) {
hooks = hooks.map(function (hook) {
return hook.bind(app);
})
});
['beforeStart',
'afterStart',
'beforeRestart',
'beforeShutdown'
].forEach(function (hookname) {
app.config.hooks[hookname].forEach(function (hook) {
app.hook(hookname, hook);
});
});
Object.defineProp(app, 'hooks', {
value: hooks,
writable: true,
configurable: ['dev', 'test'],
enumerable: true
});
app.emit('config:ready');
resolve();
});
};
var beforeStartApplication = function beforeStartApplication() {
return new Promise(function (resolve, reject) {
app.emit('start:before');
app.config.hooks.hookTimeout = app.config.hooks.hookTimeout || 2000;
async.eachSeries(app.hook('beforetart'), function runHook(hook, cb) {
try {
var timer = setTimeout(function () {
app.log.warn('Hook is taking unusually long to execute its callback (%d milliseconds).',
app.config.hooks.hookTimeout);
}, app.config.hooks.hookTimeout);
hook(app, function onComplete() {
clearTimeout(timer);
app.log.debug('Hook \'%s\' successfully completed (beforetart)', _.isEmpty(hook.name) ? 'anonymous' : hook.name);
cb();
});
} catch (e1) {
cb(e1);
}
},
function (err) {
if (err) {
reject(err);
}
resolve();
});
});
};
var setupLogger = function setupLogger() {
return new Promise(function (resolve, reject) {
app.log = new winston.Logger({
transports: [
new winston.transports.Console(app.config.log.transports.console)
]
});
resolve();
})
};
var runApplication = function runApplication() {
return new Promise(function (resolve, reject) {
var server = app.server = http.createServer(app);
server.listen(app.config.port);
server.once('error', function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof app.config.port === 'string' ? 'Pipe ' + app.config.port : 'Port ' + app.config.port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
app.log.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
app.log.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
});
server.once('listening', function onListening() {
var addr = server.address();
var bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port;
debug('Listening on ' + bind);
app.log.info('Server configuration', {
config: JSON.parse(JSON.stringify(app.config)),
process: {
pid: process.pid,
uid: process.getuid ? process.getuid() : null,
gid: process.getgid ? process.getgid() : null,
cwd: process.cwd(),
execPath: process.execPath,
version: process.version,
argv: process.argv,
memoryUsage: process.memoryUsage()
},
os: {
loadavg: os.loadavg(),
uptime: os.uptime()
}
});
app.log.info(moment().format('ddd ll HH:mm:ss Z'));
// app.log.info('Momonga version %s', app.momonga.version);
app.log.info('Starting %s server at http://127.0.0.1:%s', utils.env(), addr.port);
app.log.info('To shut down momonga, press <cntl> + c at any time.');
resolve();
});
});
};
var afterRunApplication = function afterRunApplication() {
return new Promise(function (resolve, reject) {
app.emit('start:after');
async.eachSeries(app.hook('afterstart'), function runHook(hook, cb) {
try {
var timeoutMs = app.config.hooks.hookTimeout || 2000;
var timer = setTimeout(function runTimer() {
app.log.error('Hook is taking unusually long to execute its callback (%d milliseconds).',
timeoutMs);
}, timeoutMs);
hook(app, function onComplete() {
clearTimeout(timer);
cb();
});
} catch (e1) {
reject(err);
}
}, function (err) {
if (err) {
reject(err);
}
resolve();
});
});
};
var execHooks = function execHooks(hookname) {
return new Promise(function (resolve, reject) {
async.eachSeries(app.hook(hookname), function runHook(hook, cb) {
try {
var timeoutMs = app.config.hooks.hookTimeout || 2000;
var timer = setTimeout(function () {
app.log.error('Hook is taking unusually long to execute its callback (%d milliseconds).',
timeoutMs);
}, timeoutMs);
var onComplete = function (err) {
clearTimeout(timer);
cb(err);
};
var prom = hook(app, onComplete);
if (prom instanceof Promise) {
prom.then(onComplete).catch(onComplete);
}
} catch (e1) {
cb(e1);
}
}, function onComplete(err) {
if (err) {
reject(err);
}
resolve();
});
});
};
var runDomain = function runDomain() {
return new Promise(function (resolve, reject) {
var d = domain.create();
d.once('error', reject);
d.run(function onRun() {
try {
var order = [
'logger',
'responses',
'policies',
'services',
'validators',
'controllers',
'models',
'routes',
'auth'
];
async.eachSeries(order, function (hookname, cb) {
app.emit('before:' + hookname);
execHooks('before' + hookname)
.then(function () {
if (_.isString(hookname)) {
try {
return require(path.resolve(__dirname, 'hooks', hookname))(app, cb);
} catch (e1) {
return cb(e1);
}
} else {
execHooks('after' + hookname)
.then(function () {
app.emit('after:' + hookname);
cb();
}).catch(cb);
}
})
.catch(cb);
}, function (err) {
if (err) {
reject(err);
}
resolve();
});
} catch (err) {
reject(err);
}
});
});
};
var trace = function trace() {
return new Promise(function (resolve) {
app.server.on('timeout', function (socket) {});
app.configure('prod', function () {
var timeOut = 2 * 60 * 1000;
app.server.setTimeout(timeOut);
app.log.debug('Currently server timeout is %s', app.server.timeout);
});
app.configure(function () {
app.set('json spaces', 2);
var timeOut = 30 * 1000;
app.server.setTimeout(timeOut);
app.log.debug('Currently server timeout is %s', app.server.timeout);
app.get('/package.json', function (req, res) {
return res.status(200).send(app.config.pkg);
});
app.get('/config.json', function (req, res) {
return res.status(200).send(app.config);
});
app.get('/config/:property.json', function (req, res) {
if (_.has(app.config, req.params.property)) {
return res.status(200).send(app.config[req.params.property]);
} else {
return res.status(404).send();
}
});
app.get('/ping', function (req, res) {
res.set('Connection', 'close');
setTimeout(function () {
return res.send('pong');
}, +req.query.timeout || 0);
});
app.post('/rs', function (req, res) {
app.restart();
return res.status(202).send();
});
app.use(function (req, res, next) {
app.log.warn('Url not found', {
url: req.url,
method: req.method,
headers: req.headers,
body: req.body,
query: req.query,
params: req.params,
wantsJSON: req.wantsJSON
});
next();
});
app.use(function (err, req, res, next) {
app.log.warn('requestException', exception.getAllInfo(err));
next(err);
});
});
resolve();
});
};
cb = cb || function uncaughtException(err) {
if (err) {
console.error(err.stack);
app.log.error('uncaughtException', exception.getAllInfo(err));
}
};
var loadConfig = require('./config/load');
loadConfig(app, conf)
.then(setupLogger)
.then(prepareHooks)
.then(initialize)
.then(beforeStartApplication)
.then(runDomain)
// .then(freezeconfig)
.then(loadBootScripts)
.then(runApplication)
.then(afterRunApplication)
.then(trace)
.then(function () {
cb(null, app);
})
.catch(cb);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment