UNPKG

wns-http-package

Version:

HTTP package/bundle for WNS Middleware

424 lines (361 loc) 9.96 kB
/** * WNS's HTTP Package * @copyright &copy; 2013- Pedro Nasser &reg; * @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); } } };