worker.js
* Single-Threading Application Worker
* @author Hermann Mayer <hermann.mayer92@gmail.com>
var cluster = require('cluster');
var winston = require('winston');
var expressWinston = require('express-winston');
var moment = require('moment');
var colors = require('colors');
var extend = require('extend');
var getopt = require('node-getopt');
var ControllerLoader = require('../http/mvc/loader');
* @param {Object} options - Options object
var Worker = function(options)
greppy.db = new (greppy.get('store.db'))(
greppy.config.get('app').get('database')
this.controllerLoader = new ControllerLoader(this);
process.title = this.options.title || 'greppy-worker';
// Annotate the worker context
greppy.context = this.cliArgs.options.context;
greppy.logger = global.logger = this.logger;
* Get the winston logger instance.
Worker.prototype.getLogger = function()
* Get the worker commandline interface arguments.
Worker.prototype.getCliArgs = function()
* Get the concrete worker implementation.
Worker.prototype.getContext = function()
Worker.prototype.configureCliArguments = function()
['h', 'help', 'Display this help'],
['c', 'context=CONCRETE_WORKER', 'Start the concrete worker implementation'],
['d', 'debug', 'Start the worker in debug mode']
"This is a Greppy framework cluster worker implementation.\n\n" +
// Parse given arguments and prepare them for the master process
var cliConf = extend(true, defaultCliConf, this.options.cli || {});
this.cli = getopt.create(cliConf.args);
this.cli.setHelp(cliConf.help);
this.cliArgs = this.cli.parseSystem();
if (0 === Object.keys(this.cliArgs.options).length ||
this.cliArgs.options.hasOwnProperty('help')) {
// The worker needs the concrete worker switch, so we check it
if (!this.cliArgs.options.hasOwnProperty('context')) {
'The '.red + '--context='.red.bold + ' switch was not given. ' +
'You need a concrete worker implementation.'.red + '\n'
Worker.prototype.configureLogger = function()
new (winston.transports.Console)({
var part = moment().format().yellow.bold;
part += ' [Worker]'.white.bold;
this.logger = new winston.Logger(loggerConf);
// Setup winston request logger
var defaultRequestLoggerConf = {
new (winston.transports.File)({
filename: '%s/var/log/%s.access.log'.format(
var requestLoggerConf = extend(
false, {}, defaultRequestLoggerConf,
this.options.requestLogger || {}
this.requestLogger = expressWinston.logger(requestLoggerConf);
Worker.prototype.configureContext = function()
this.context = new (require('%s/app/context/%s'.format(
process.cwd(), this.cliArgs.options.context
logger.info('Using the %s worker context', this.context.name.red.bold);
// Configure the app with Greppy enhancements
this.baseApp = new (require('./worker/app'))(this);
* @param {Object} controllers - Configuration of controllers to load
* @param {Function} callback - Function to call on finish
Worker.prototype.loadControllers = function(controllers, callback)
logger.info('Loading enabled' + ' %s '.red + 'controllers', 'default');
async.each(Object.keys(controllers), function(name, callback) {
var controller = controllers[name];
self.controllerLoader.loadController(
controller.path, name, controller.module, callback
* @param {Array} modules - Modules to load
* @param {Function} callback - Function to call on finish
Worker.prototype.loadModules = function(modules, callback)
this.controllerLoader.load(modules, callback);
* @param {Function} callback - Function to call on finish
Worker.prototype.configureDatabases = function(callback)
greppy.db.configure(this.context.backends, function(err) {
// We got an error while configuring the database-backends
// so we should die and allow the master to bootstrap another
logger.error('An error occurred while configuring the backend.');
'It is safer to shutdown the worker ' +
'to give the master the opportunity to reboot this worker - which ' +
'could be successful. Shutting down..'
* Setup Greppy default controllers.
* @param {Function} callback - Function to call on finish
Worker.prototype.configureDefaultControllers = function(callback)
// Setup default controller configuration
var defaultControllersConf = {
path: __dirname + '/cluster/worker/ipc/controller.js',
path: __dirname + '/cluster/worker/icc/controller.js',
// First step - merge generic worker config
true, defaultControllersConf, this.options.controllers || {}
// Second step - merge worker context config
true, controllersConf, this.context.controllers
this.loadControllers(controllersConf, callback);
* @param {Function} callback - Function to call on finish
Worker.prototype.configureContextModules = function(callback)
var modules = this.options.modules || [];
modules = Array.uniq(modules.concat(this.context.modules));
return callback && callback();
this.loadModules(modules, function(err) {
* Setup the application stack.
* @param {Object} app - The application object
* @param {Object} server - Server object
* @param {Function} callback - Function to call on finish
Worker.prototype.configureApplicationStack = function(app, server, callback)
// Push the request logger to the middleware stack
// Bind listening event to http server
server.on('listening', function() {
var address = self.server.address();
'HTTP server is listening on %s:%s (%s)',
('' + address.port).green.bold,
// Load configured context modules
self.configureContextModules(callback);
* @param {Object} app - Express application object
* @param {Object} server - HTTP(S) server object
* @param {Function} callback - Function to call on finish
Worker.prototype.configure = function(app, server, callback)
throw new Error('No application object was given');
throw new Error('No server instance was given');
'Error occured while loading the '.red +
self.cliArgs.options.context.red.bold +
// Setup Greppy default controllers
self.configureDefaultControllers(callback);
// Configure database connections
self.configureDatabases(function(err) {
self.configureApplicationStack(
self.baseApp.rebuildLinkingCache();
// Load all routes into the application
self.controllerLoader.configure(app);
self.bootDuration = self.bootEnd.getTime() - self.bootStart.getTime();