actionhero
Version:
actionhero.js is a multi-transport API Server with integrated cluster capabilities and delayed tasks
308 lines (263 loc) • 10.1 kB
JavaScript
var domain = require('domain');
var async = require('async');
module.exports = {
loadPriority: 430,
initialize: function(api, next){
api.actionProcessor = function(connection, callback){
if(!connection){
throw new Error('data.connection is required');
}
this.connection = connection;
this.action = null;
this.toProcess = true;
this.toRender = true;
this.messageCount = connection.messageCount;
this.params = connection.params;
this.callback = callback;
this.missingParams = [];
this.validatorErrors = [];
this.actionStartTime = null;
this.actionTemplate = null;
this.working = false;
this.response = {};
this.duration = null;
this.actionStatus = null;
}
api.actionProcessor.prototype.incrementTotalActions = function(count){
var self = this;
if(!count){ count = 1 }
self.connection.totalActions = self.connection.totalActions + count;
}
api.actionProcessor.prototype.incrementPendingActions = function(count){
var self = this;
if(!count){ count = 1 }
self.connection.pendingActions = self.connection.pendingActions + count;
}
api.actionProcessor.prototype.getPendingActionCount = function(){
var self = this;
return self.connection.pendingActions;
}
api.actionProcessor.prototype.completeAction = function(status){
var self = this;
var error = null;
self.actionStatus = String(status);
if(self.actionDomain){ self.actionDomain.exit(); }
if(status instanceof Error){
error = status;
}else if(status === 'server_error'){
error = api.config.errors.serverErrorMessage();
}else if(status === 'server_shutting_down'){
error = api.config.errors.serverShuttingDown();
}else if(status === 'too_many_requests'){
error = api.config.errors.tooManyPendingActions();
}else if(status === 'unknown_action'){
error = api.config.errors.unknownAction(self.connection.action);
}else if(status === 'unsupported_server_type'){
error = api.config.errors.unsupportedServerType(self.connection.type);
}else if(status === 'missing_params'){
error = api.config.errors.missingParams(self.missingParams) ;
}else if(status === 'validator_errors'){
error = api.config.errors.invalidParams(self.validatorErrors);
}else if(status){
error = status;
}
if(error && typeof error === 'string'){
error = new Error( error );
}
if(error && !self.response.error){
self.response.error = error;
}
self.incrementPendingActions(-1);
api.stats.increment('actions:actionsCurrentlyProcessing', -1);
self.duration = new Date().getTime() - self.actionStartTime;
process.nextTick(function(){
if(typeof self.callback === 'function'){
self.callback(self);
}
});
self.working = false;
self.logAction(error);
}
api.actionProcessor.prototype.logAction = function(error){
var self = this;
// logging
var logLevel = 'info';
if(self.actionTemplate && self.actionTemplate.logLevel){
logLevel = self.actionTemplate.logLevel;
}
var filteredParams = {}
for(var i in self.params){
if(api.config.general.filteredParams && api.config.general.filteredParams.indexOf(i) >= 0){
filteredParams[i] = '[FILTERED]';
}else if(typeof self.params[i] === 'string'){
filteredParams[i] = self.params[i].substring(0,api.config.logger.maxLogStringLength);
}else{
filteredParams[i] = self.params[i]
}
}
var logLine = {
to: self.connection.remoteIP,
action: self.action,
params: JSON.stringify(filteredParams),
duration: self.duration,
};
if(error){
if(error instanceof Error){
logLine.error = String(error);
}else{
try {
logLine.error = JSON.stringify(error);
} catch(e){
logLine.error = String(error);
}
}
}
api.log('[ action @ ' + self.connection.type + ' ]', logLevel, logLine);
}
api.actionProcessor.prototype.preProcessAction = function(callback){
var self = this;
var processors = [];
var processorNames = api.actions.globalMiddleware.slice(0);
if(self.actionTemplate.middleware){
self.actionTemplate.middleware.forEach(function(m){ processorNames.push(m); });
}
processorNames.forEach(function(name){
if(typeof api.actions.middleware[name].preProcessor === 'function'){
processors.push(function(next){ api.actions.middleware[name].preProcessor(self, next); });
}
});
async.series(processors, function(err){
callback(err);
});
}
api.actionProcessor.prototype.postProcessAction = function(callback){
var self = this;
var processors = [];
var processorNames = api.actions.globalMiddleware.slice(0);
if(self.actionTemplate.middleware){
self.actionTemplate.middleware.forEach(function(m){ processorNames.push(m); });
}
processorNames.forEach(function(name){
if(typeof api.actions.middleware[name].postProcessor === 'function'){
processors.push(function(next){ api.actions.middleware[name].postProcessor(self, next); });
}
});
async.series(processors, function(err){
callback(err);
});
}
api.actionProcessor.prototype.reduceParams = function(){
var self = this;
if(api.config.general.disableParamScrubbing !== true){
for(var p in self.params){
if(
api.params.globalSafeParams.indexOf(p) < 0 &&
self.actionTemplate.inputs &&
Object.keys(self.actionTemplate.inputs).indexOf(p) < 0
){
delete self.params[p];
}
}
}
}
api.actionProcessor.prototype.validateParams = function(){
var self = this;
for(var key in self.actionTemplate.inputs){
var props = self.actionTemplate.inputs[key];
// default
if(self.params[key] === undefined && props.default !== undefined){
if(typeof props.default === 'function'){
self.params[key] = props.default(self.params[key], self);
}else{
self.params[key] = props.default;
}
}
// formatter
if(self.params[key] !== undefined && typeof props.formatter === 'function'){
self.params[key] = props.formatter(self.params[key], self);
}
// validator
if(self.params[key] !== undefined && typeof props.validator === 'function'){
var validatorResponse = props.validator(self.params[key], self);
if(validatorResponse !== true){
self.validatorErrors.push(validatorResponse);
}
}
// required
if(props.required === true){
if( api.config.general.missingParamChecks.indexOf(self.params[key]) >= 0){
self.missingParams.push(key);
}
}
}
}
api.actionProcessor.prototype.processAction = function(){
var self = this;
self.actionStartTime = new Date().getTime();
self.working = true;
self.incrementTotalActions();
self.incrementPendingActions();
self.action = self.params.action;
if(api.actions.versions[self.action]){
if(!self.params.apiVersion){
self.params.apiVersion = api.actions.versions[self.action][api.actions.versions[self.action].length - 1];
}
self.actionTemplate = api.actions.actions[self.action][self.params.apiVersion];
}
api.stats.increment('actions:actionsCurrentlyProcessing');
if(api.running !== true){
self.completeAction('server_shutting_down');
} else if(self.getPendingActionCount(self.connection) > api.config.general.simultaneousActions){
self.completeAction('too_many_requests');
} else if(!self.action || !self.actionTemplate){
api.stats.increment('actions:actionsNotFound');
self.completeAction('unknown_action');
} else if(self.actionTemplate.blockedConnectionTypes && self.actionTemplate.blockedConnectionTypes.indexOf(self.connection.type) >= 0){
self.completeAction('unsupported_server_type');
} else {
api.stats.increment('actions:totalProcessedActions');
api.stats.increment('actions:processedActions:' + self.connection.action);
if(api.config.general.actionDomains === true){
self.actionDomain = domain.create();
self.actionDomain.on('error', function(err){
api.exceptionHandlers.action(self.actionDomain, err, self, function(){
self.completeAction('server_error');
});
});
self.actionDomain.run(function(){
self.runAction();
});
}else{
self.runAction();
}
}
}
api.actionProcessor.prototype.runAction = function(){
var self = this;
self.preProcessAction(function(error){
self.reduceParams();
self.validateParams();
if(error){
self.completeAction(error);
}else if(self.missingParams.length > 0){
self.completeAction('missing_params');
}else if(self.validatorErrors.length > 0){
self.completeAction('validator_errors');
}else if(self.toProcess === true && !error){
self.actionTemplate.run(api, self, function(error){
if(error){
self.completeAction(error);
}else{
self.postProcessAction(function(error){
self.completeAction(error);
});
}
});
}else{
self.completeAction();
}
});
}
next();
}
}