UNPKG

cnn-routeomatic

Version:

A library for web server routing, redirecting and rewriting joy for ExpressJS.

335 lines (302 loc) 12.7 kB
/** * Route-o-matic -- A festival of routing, redirecting, and rewriting joy for ExpressJS. */ 'use strict'; const defaultAllowUndefinedHandler = false, defaultAllowWrite = false, defaultNormalizeUrls = false, defaultRedirectCode = 302, defaultReduceRedirectCode = 301, defaultRetryLimit = 20, defaultRemoveDoubleSlashes = false, defaultTimeout = 20000, HostTable = require('./host-table'), RomRequest = require('./rom-request'), RouteTable = require('./route-table'), utils = require('./utils'); /** * RouteOMatic object constructor * * @constructor * @param {object} envConf - Environment configuration object * @param {object} [envConf.env] - Environment configuration current settings object, optional * @param {object} [envConf.env.conds] - Environment configuration conditionals object, optional * @param {object} [envConf.env.subs] - Environment configuration substitutions object, optional * @param {object} envConf.routeHandlers - Route handlers namespace object * @param {object} [envConf.logger] - Shared logger object, optional * @param {object} [envConf.requestLogger] - Shared logger object for use in request logging, optional * @param {object} [envConf.onSent] - Post response sent function, optional * @param {mixed} [envConf.proxyAgent] - Proxy Agent to use, or false for no agent/default, optional * @param {object} hostConf - Host configuration object * @param {object} hostConf.defaults - Default host settings object * @param {object} [hostConf.defaults.headers] - Default header values, optional * @param {array} hostConf.hosts - Array of host config objects * @param {array} hostConf.routeTables - Array of route tables config objects * @param {object|function} server - Server object (top-level Express object or function) */ function RouteOMatic(envConf, hostConf, server) { // Initialize object values this.hostTable = {}; this.log = envConf.logger || utils.baseLogger; this.logReq = envConf.requestLogger || this.log; this.valid = false; // Verify server and name parameters if (typeof envConf !== 'object' || envConf === null) { throw new Error('Invalid environment config object!'); } if (server === null || (typeof server !== 'object' && typeof server !== 'function')) { throw new Error('Invalid server object!' + typeof server); } // Process the hosts, routes, and config this.hostTable = this.setupAllTheThings(envConf, hostConf); // We have something, so attach it the server this.valid = true; server.all('*', this.handleRouting.bind(this)); } /** * Check to see if this RouteOMatic instance is "valid". * * @memberof RouteOMatic * @public * @returns {boolean} - true if valid, false if not valid */ RouteOMatic.prototype.isValid = function () { return this.valid; }; /** * Get the currently active host table * * @memberof RouteOMatic * @public * @returns {object} - The host table object */ RouteOMatic.prototype.getHostTable = function () { return this.hostTable; }; /** * Setup all the things! * * @memberof RouteOMatic * @private * @param {object} envConf - Environment configuration object * @param {object} hostConf - Host configuration object * @returns {object} - New host table object * @throws {Error} - Throws error on failure */ RouteOMatic.prototype.setupAllTheThings = function (envConf, hostConf) { let config, hostTable = {}, reqSettings = {}, routeTables = {}; // Process the environment config object try { if (typeof envConf !== 'object' || envConf === null) { throw new Error('Invalid or empty configuration'); } if (typeof envConf.routeHandlers !== 'object' || envConf.routeHandlers === null) { throw new Error('Invalid route handler context object'); } config = { routeHandlers: envConf.routeHandlers }; if (typeof envConf.env !== 'undefined') { if (envConf.env === null || typeof envConf.env !== 'object') { throw new Error('Invalid current environment settings object'); } config.env = envConf.env; } if (typeof envConf.logger !== 'undefined') { if (envConf.logger === null || typeof envConf.logger !== 'object') { throw new Error('Invalid logger object'); } config.log = envConf.logger; } if (typeof envConf.requestLogger !== 'undefined') { if (envConf.requestLogger === null || typeof envConf.requestLogger !== 'object') { throw new Error('Invalid requestLogger object'); } reqSettings.requestLogger = envConf.requestLogger; } if (typeof envConf.dnsLookup !== 'undefined') { if (envConf.dnsLookup === null || typeof envConf.dnsLookup !== 'function') { throw new Error('Invalid DNS lookup object'); } config.dnsLookup = envConf.dnsLookup; } if (typeof envConf.onSent === 'function') { reqSettings.onSent = envConf.onSent; } else { reqSettings.onSent = null; } if (typeof envConf.proxyAgent === 'object' && envConf.proxyAgent !== null) { reqSettings.proxyAgent = envConf.proxyAgent; } else { reqSettings.proxyAgent = false; } reqSettings.ports = {}; if (typeof envConf.ports === 'object' && envConf.ports !== null) { let i, k = Object.keys(envConf.ports), p, ps; for (i = 0; i < k.length; i++) { if (typeof k[i] === 'string') { p = parseInt(k[i], 10); } else if (typeof k[i] === 'number') { p = k[i]; } else { p = 0; } if (p > 0 && p < 65536) { ps = p.toString(10); if (envConf.ports[k[i]].origProto !== 'http' && envConf.ports[k[i]].origProto !== 'https') { throw new Error(`Invalid protocol specified for port ${k[i]}`); } reqSettings.ports[ps] = { portNumber: p, origProto: envConf.ports[k[i]].origProto, origProtoVer: envConf.ports[k[i]].origProtoVer || '1.1' }; if (typeof envConf.ports[k[i]].origPort !== 'undefined') { if (typeof envConf.ports[k[i]].origPort !== 'number') { throw new Error(`Invalid original port specified for port ${k[i]}`); } reqSettings.ports[ps].origPortNumber = envConf.ports[k[i]].origPort; reqSettings.ports[ps].origPort = envConf.ports[k[i]].origPort.toString(10); } else { reqSettings.ports[ps].origPortNumber = p; reqSettings.ports[ps].origPort = ps; } } } } } catch (err) { throw new Error(`Error processing Route-O-Matic environment configuration: ${err.message}`); } // Process the host config object try { if (typeof hostConf !== 'object' || hostConf === null) { throw new Error('Invalid or empty configuration!'); } if (typeof hostConf.defaults !== 'undefined' && (typeof hostConf.defaults !== 'object' || hostConf.defaults === null)) { throw new Error('Invalid default host settings object!'); } if (!Array.isArray(hostConf.hosts) || hostConf.hosts.length === 0) { throw new Error('Invalid or empty hosts configuration!'); } if (typeof hostConf.routeTables !== 'object' || hostConf.routeTables === null) { throw new Error('Invalid or empty route table configuration!'); } config.defaults = hostConf.defaults || {}; // Set any unset defaults if (typeof config.defaults.allowWrite !== 'boolean') { config.defaults.allowWrite = defaultAllowWrite; } if (typeof config.defaults.allowUndefinedHandler !== 'boolean') { config.defaults.allowUndefinedHandler = defaultAllowUndefinedHandler; } if (typeof config.defaults.normalizeUrls !== 'boolean') { config.defaults.normalizeUrls = defaultNormalizeUrls; } if (typeof config.defaults.redirectCode !== 'number') { config.defaults.redirectCode = defaultRedirectCode; } if (typeof config.defaults.reduceRedirectCode !== 'number') { config.defaults.reduceRedirectCode = defaultReduceRedirectCode; } if (typeof config.defaults.removeDoubleSlashed !== 'boolean') { config.defaults.removeDoubleSlashes = defaultRemoveDoubleSlashes; } if (typeof config.defaults.retryLimit !== 'number') { config.defaults.retryLimit = defaultRetryLimit; } if (typeof config.defaults.timeout !== 'number') { config.defaults.timeout = defaultTimeout; } } catch (err) { throw new Error(`Error processing Route-O-Matic configuration: ${err.message}`); } reqSettings.allowUndefinedHandler = config.defaults.allowUndefinedHandler; reqSettings.allowWrite = config.defaults.allowWrite; reqSettings.dnsLookup = config.dnsLookup || null; reqSettings.normalizeUrls = config.defaults.normalizeUrls; reqSettings.redirectCode = config.defaults.redirectCode; reqSettings.reduceRedirectCode = config.defaults.reduceRedirectCode; reqSettings.removeDoubleSlashes = config.defaults.removeDoubleSlashes; reqSettings.requestLogger = this.logReq; reqSettings.retryLimit = config.defaults.retryLimit; reqSettings.timeout = config.defaults.timeout; // Process the route table objects try { for (let rt in hostConf.routeTables) { if (hostConf.routeTables.hasOwnProperty(rt)) { if (typeof hostConf.routeTables[rt] !== 'object' || hostConf.routeTables[rt] === null) { throw new Error(`Bad route table configuration (${rt})`); } hostConf.routeTables[rt].id = rt; try { routeTables[rt] = new RouteTable(hostConf.routeTables[rt], config); } catch (e) { throw new Error(`Failure processing route table "${rt}": ${e.message}`); } } } } catch (err) { throw new Error(`Error processing Route-O-Matic route tables: ${err.message}`); } config.routeTables = routeTables; // Process the host objects try { hostTable = new HostTable(hostConf.hosts, config); } catch (err) { throw new Error(`Error processing Route-O-Matic host table: ${err.message}`); } // Successfully setup new routes and hosts this.reqSettings = reqSettings; this.reqSettings.hostTable = hostTable; this.log.debug('Successfully updated hosts and route tables.'); return hostTable; }; /** * Re-configure with new hosts/routes/settings * * @memberof RouteOMatic * @public * @param {object} newEnvConf - New environment configuration object * @param {object} newHostConf - New host configuration object * @returns {boolean} - true if successful, false if not */ RouteOMatic.prototype.reconfigure = function (newEnvConf, newHostConf) { try { let newHostTable; if (typeof newEnvConf !== 'object' || newEnvConf === null) { throw new Error('Invalid environment config object!'); } // Try to re-configure with the new settings newHostTable = this.setupAllTheThings(newEnvConf, newHostConf); if (newHostTable) { delete this.hostTable; this.hostTable = newHostTable; this.valid = true; return true; } } catch (err) { this.log.error(`Failed to reconfigure the routes: ${err.message}`); } return false; }; /** * Handle ExpressJS routing with the Route-O-Matic * * @memberof RouteOMatic * @public * @param {object} req - Request object (Express) * @param {object} res - Response object (HTTP/HTTPS) * @param {function} [next] - Continuation function */ RouteOMatic.prototype.handleRouting = function (req, res, next) { let romReq = new RomRequest(this.reqSettings); romReq.process(req, res, next); }; module.exports = RouteOMatic;