UNPKG

connect-modrewrite

Version:

connect-modrewrite adds modrewrite functionality to connect/express server

249 lines (209 loc) 5.58 kB
/** * Module dependencies */ var url = require('url'); var qs = require('qs'); var httpReq = require('http').request; var httpsReq = require('https').request; var defaultVia = '1.1 ' + require('os').hostname(); /** * Syntaxes */ var noCaseSyntax = /NC/; var lastSyntax = /L/; var proxySyntax = /P/; var redirectSyntax = /R=?(\d+)?/; var forbiddenSyntax = /F/; var goneSyntax = /G/; var typeSyntax = /T=([\w|\/]+,?)/; var hostSyntax = /H=([^,]+)/; var flagSyntax = /\[([^\]]+)]$/; var partsSyntax = /\s+|\t+/g; var httpsSyntax = /^https/; var querySyntax = /\?(.*)/; /** * Export `API` */ module.exports = function(rules) { // Parse the rules to get flags, replace and match pattern rules = _parse(rules); return function(req, res, next) { var protocol = req.connection.encrypted || req.headers['x-forwarded-proto'] === 'https' ? 'https' : 'http'; var callNext = true; rules.some(function(rule) { if(rule.host) { if(!rule.host.test(req.headers.host)) { return false; } } var location; if(/\:\/\//.test(rule.replace)) { location = req.url.replace(rule.regexp, rule.replace); } else { location = protocol + '://' + req.headers.host + req.url.replace(rule.regexp, rule.replace); } var match = rule.regexp.test(req.url); // If not match if(!match) { // Inverted rewrite if(rule.inverted) { req.url = rule.replace; return rule.last; } return false; } // Type if(rule.type) { res.setHeader('Content-Type', rule.type); } // Gone if(rule.gone) { res.writeHead(410); res.end(); callNext = false; return true; } // Forbidden if(rule.forbidden) { res.writeHead(403); res.end(); callNext = false; return true; } // Proxy if(rule.proxy) { _proxy(rule, { protocol : protocol, req : req, res : res, next : next }); callNext = false; return true; } // Redirect if(rule.redirect) { res.writeHead(rule.redirect, { Location : location }); res.end(); callNext = false; return true; } // Rewrite if(!rule.inverted) { if (rule.replace !== '-') { req.url = req.url.replace(rule.regexp, rule.replace); } return rule.last; } }); // Add to query object var queryValue = querySyntax.exec(req.url); if(queryValue) { req.params = req.query = qs.parse(queryValue[1]); } if(callNext) { next(); } }; }; /** * Get flags from rule rules * * @param {Array.<rules>} rules * @return {Object} * @api private */ function _parse(rules) { return (rules || []).map(function(rule) { // Reset all regular expression indexes lastSyntax.lastIndex = 0; proxySyntax.lastIndex = 0; redirectSyntax.lastIndex = 0; forbiddenSyntax.lastIndex = 0; goneSyntax.lastIndex = 0; typeSyntax.lastIndex = 0; hostSyntax.lastIndex = 0; var parts = rule.replace(partsSyntax, ' ').split(' '), flags = ''; if(flagSyntax.test(rule)) { flags = flagSyntax.exec(rule)[1]; } // Check inverted urls var inverted = parts[0].substr(0, 1) === '!'; if(inverted) { parts[0] = parts[0].substr(1); } var redirectValue = redirectSyntax.exec(flags); var typeValue = typeSyntax.exec(flags); var hostValue = hostSyntax.exec(flags); return { regexp: typeof parts[2] !== 'undefined' && noCaseSyntax.test(flags) ? new RegExp(parts[0], 'i') : new RegExp(parts[0]), replace: parts[1], inverted: inverted, last: lastSyntax.test(flags), proxy: proxySyntax.test(flags), redirect: redirectValue ? (typeof redirectValue[1] !== 'undefined' ? redirectValue[1] : 301) : false, forbidden: forbiddenSyntax.test(flags), gone: goneSyntax.test(flags), type: typeValue ? (typeof typeValue[1] !== 'undefined' ? typeValue[1] : 'text/plain') : false, host: hostValue ? new RegExp(hostValue[1]) : false }; }); } /** * Proxy the request * * @param {Object} rule * @param {Object} metas * @return {void} * @api private */ function _proxy(rule, metas) { var opts = _getRequestOpts(metas.req, rule); var request = httpsSyntax.test(rule.replace) ? httpsReq : httpReq; var pipe = request(opts, function (res) { res.headers.via = opts.headers.via; metas.res.writeHead(res.statusCode, res.headers); res.on('error', function (err) { metas.next(err); }); res.pipe(metas.res); }); pipe.on('error', function (err) { metas.next(err); }); if(!metas.req.readable) { pipe.end(); } else { metas.req.pipe(pipe); } } /** * Get request options * * @param {HTTPRequest} req * @param {Object} rule * @return {Object} * @api private */ function _getRequestOpts(req, rule) { var opts = url.parse(req.url.replace(rule.regexp, rule.replace), true); var query = (opts.search != null) ? opts.search : ''; if(query) { opts.path = opts.pathname + query; } opts.method = req.method; opts.headers = req.headers; opts.agent = false; opts.rejectUnauthorized = false; opts.requestCert = false; var via = defaultVia; if(req.headers.via) { via = req.headers.via + ', ' + via; } opts.headers.via = via; delete opts.headers['host']; return opts; }