wns-http-package
Version:
HTTP package/bundle for WNS Middleware
424 lines (361 loc) • 9.96 kB
JavaScript
/**
* WNS's HTTP Package
* @copyright © 2013- Pedro Nasser ®
* @license: MIT
* @see http://github.com/pedronasser/wns-http-package
*/
/**
* @class wnHttp
* @classdesc Create a HTTP Server
* @author Pedro Nasser
*/
module.exports = {
/**
* Class dependencies
*/
extend: ['wnComponent'],
/**
* NPM dependencies
*/
dependencies: ['http','express','formidable'],
/**
* PRIVATE
*/
private: {
_modules: {},
/**
* @default
* @type {object}
*/
_config: {
listen: [80],
envPort: true,
keepAlive: false,
autoListen: true,
express: false
}
},
/**
* PUBLIC
*/
public: {
/**
* HTTPObject connection instance
* @type {object}
*/
connection: null,
/**
* object events to be preloaded.
* @type {object}
*/
defaultEvents: {
'open': {},
'resolve': {},
'close': {}
},
/**
* a small cache to fast resolve the request to the right application
* @type {object}
*/
urlShortcut: {}
},
/**
* Methods
*/
methods: {
/**
* Initializer
*/
init: function (config,c)
{
this.prepareServer();
this.prepareEvents();
if (_config.envPort && process.env.PORT)
{
self.debug('Change HTTP listening port to '+process.env.PORT+'...',0);
_config.listen[0]=process.env.PORT;
};
if (_config.autoListen)
this.listen();
},
/**
* Prepare HTTP Server
*/
prepareServer: function ()
{
self.debug('Preparing HTTP server...',1);
this.connection=http.createServer(self.e.open);
},
/**
* Prepare all events
*/
prepareEvents: function ()
{
self.debug('Preparing HTTP events...',1);
this.addListener('open',this.onConnection);
this.addListener('resolve',this.onResolve);
this.getParent().prependListener('loadModule',function (e,moduleName,module) {
self.attachModule(moduleName,module);
});
},
/**
* Prepare all modules to receive an HTTP request.
* @param {string} moduleName
* @param {wnModule} module
*/
attachModule: function (moduleName,module)
{
if (!_.isString(module.getConfig('servername')))
return false;
module.setEvents({
'newRequest': { async:true },
'readyRequest': { async:true },
'runRequest': { async:true },
'sendRequest': { async:true }
});
module.getEvent('newRequest');
module.getEvent('readyRequest');
module.getEvent('runRequest');
module.getEvent('sendRequest');
if (_config.express)
{
module.express = express();
module.express.express = express;
module.express.once('ready',function () {
module.express.use(function (req,resp,next) {
self.debug(req.url+' - Received from Express',3);
module.e.readyRequest(req,resp,next);
});
});
}
_modules[moduleName]=module;
},
/**
* Start listening the HTTP server.
* @param {function} [cb] callback
*/
listen: function (cb)
{
var config = this.getConfig();
try {
this.connection.listen(config.listen[0] || 80,config.listen[1],cb);
self.debug('Listening HTTP server (port '+config.listen[0]+')...',0);
} catch (e) {
this.getParent().e.exception
&&this.getParent().e.exception(e);
}
},
/**
* Default 'OPEN' handler
* @param {ServerRequest} req
* @param {ServerResponse} resp
*/
onConnection:function (e,req,resp) {
Object.defineProperty(resp,'http',{ value: self, enumerable: false });
Object.defineProperty(resp,'info',{ value: req, enumerable: false });
Object.defineProperty(resp,'request',{ value: req, enumerable: false });
Object.defineProperty(resp,'data',{ value: [], enumerable: false, writable:true });
Object.defineProperty(resp,'_end',{ value: resp.end, enumerable: false });
Object.defineProperty(resp,'_write',{ value: resp.write, enumerable: false });
Object.defineProperty(req,'send',{ value: resp.send, enumerable: false });
Object.defineProperty(req,'response',{ value: resp, enumerable: false });
self.handleRequest(req,resp);
},
/**
* HTTP Request Handler
* @param {ServerRequest} req
* @param {ServerResponse} resp
*/
handleRequest: function (req,resp)
{
var serverConfig = this.getParent().getModulesConfig();
var modules = _modules;
self.debug(req.url+' - Resolving request...',2);
var hostName = req.headers.host.split(':')[0]+'';
var shortcut = self.urlShortcut[hostName];
if (!_.isUndefined(shortcut))
{
self.e.resolve(_modules[shortcut],req,resp);
return;
}
for (m in serverConfig)
{
var module=_modules[m];
var moduleConfig = module.getConfig();
var servername = moduleConfig.servername;
if (_.isUndefined(module) || _.isUndefined(servername))
continue;
var nameMatch = new RegExp(servername.replace(/\,/g,'|').replace(/\*/g,'(.*)'),'gi');
var names = servername.split(',');
if (names.indexOf(hostName) != -1 || !_.isNull(hostName.match(nameMatch)))
{
self.urlShortcut[hostName]=m;
this.e.resolve(module,req,resp);
return;
}
}
self.urlShortcut[servername]=null;
this.e.resolve(null,req,resp);
},
/**
* Default 'RESOLVE' handler
* @param {wnModule} module
* @param {ServerRequest} req
* @param {ServerResponse} resp
*/
onResolve: function (e,module,req,resp) {
if (!module)
{
self.warn(req.url+' - Invalid hostname ('+req.headers['Host']+') access from '+req.connection.remoteAddress)
resp.statusCode = 502;
resp.end('<h1>Bad Gateway</h1>');
}
else
{
self.debug(req.url+' - Found the target module: '+module.getConfig('id'),2);
module.once('newRequest',function (e,req,resp) {
self.prepareRequest(module,req,resp);
});
module.e.newRequest(req,resp);
}
},
/**
* Handles a new httpRequest and sends it to the builder
* @param {wnModule} module - parent module
* @param {object} req - http request
* @param {object} resp - http response
*/
prepareRequest: function (module,req,resp)
{
self.debug(req.url+' - Preparing request',2);
_.extend(resp,module.c.wnHttpResponse.build.methods);
resp.getParent = function () { return module };
resp.debug = function () {
arguments[0] = req.url+' - '+arguments[0];
module.debug.apply(module,arguments);
};
resp.err=[];
Object.defineProperty(resp,'module',{ value: module, enumerable: false });
resp.once('send',function () {
self.debug(req.url+' - Response was sent.',2);
module.e.sendRequest(req,resp);
});
resp.once('end',function () {
self.debug(req.url+' - Response is dead',2);
self.e.close(module,req,resp);
module.e.closeRequest(req,resp);
});
self.parseCookies(req);
self.parsePost(req,function (body) {
if (_.isObject(body))
req.body = body;
self.parseQuery(req);
module.once('readyRequest',function (e,req,resp) {
self.debug(req.url+' - Request is READY.',3);
module.once('runRequest',function (e,req,resp) {
self.debug(req.url+' - Request is RUNNED.',3);
resp.statusCode = 404;
resp.end('');
});
module.e.runRequest(req,resp);
});
if (_config.express)
{
self.debug(req.url+' - Sent to Express',3);
module.express(req,resp);
} else {
module.e.readyRequest(req,resp);
}
});
},
/**
* Parse request's POST
* @param {object} req - http request
* @param {function} cb
*/
parsePost: function (req,cb)
{
self.debug(req.url+' - Parsing POST',5);
if (req.method != 'GET' && req.method != 'HEAD' && req.headers['content-type'] != 'multipart/form-data')
{
var form = new formidable;
form.parse(req, function (err, fields, files) {
cb&&cb({
fields: fields,
files: files
});
});
}
else
cb&&cb(null);
},
/**
* Parse request's cookies
* @param {object} req - http request
*/
parseCookies: function (req)
{
self.debug(req.url+' - Parsing cookies',3);
if (req.headers && req.headers.cookie)
{
var cookieData = req.headers.cookie ? req.headers.cookie.split(';') : [],
cookies = {};
for (c in cookieData)
{
var parts = cookieData[c].split('=');
cookies[parts[0].trim()]=(parts[1]||'').trim();
}
req.cookies=cookies;
}
},
/**
* Format request's query
* @param {object} obj - query object
*/
formatQuery: function (obj)
{
var self = this;
if (_.isObject(obj))
{
for (o in obj)
{
var path=o.replace(/\[\w+\]/gi,function (match) { return match.replace('[','.').replace(']','') });
var paths = path.split('.');
var name = paths[0];
if (paths.length>1)
{
if (obj[name]===undefined)
obj[name]={};
paths.shift();
obj[name][paths.join('.')]=obj[o];
delete obj[o];
self.formatQuery(obj[name]);
}
}
}
return obj;
},
/**
* Parse request's query
* @param {ServerRequest} req
*/
parseQuery: function (req)
{
self.debug(req.url+' - Parsing queries',3);
req.query = {};
req.query.GET = {};
req.query.POST = { fields: {}, files: {} };
req.query.REQUEST = {};
_.merge(req.query.POST, req.body);
for (g in req.query.GET)
{
req.query.GET[g]=req.query.GET[g].replace(/\+/gi,' ')
}
self.formatQuery(req.query.POST.fields);
self.formatQuery(req.query.POST.files);
self.formatQuery(req.query.GET);
_.merge(req.query.REQUEST, req.query.GET);
_.merge(req.query.REQUEST, req.query.POST);
}
}
};