diff --git a/README.md b/README.md index 6e45a91..eba5636 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,44 @@ npm install http-proxy-rules --save }).listen(6010, cb); ``` -Given the object we used to initialize the `HttpProxyRules` instance above, here are some [**examples**](test/index.tests.js#L33) of how sample url paths would be translated. +## Example of host-based rule + +```js + var http = require('http'), + httpProxy = require('http-proxy'), + HttpProxyRules = require('http-proxy-rules'); + + // Set up proxy rules instance + var proxyRules = new HttpProxyRules({ + hosts: { + 'foo.com': 'http://bar.com:8080', // Rule (1) + 'foobar.com': 'http://localhost:1234/' // Rule (2) + }, + default: 'http://localhost:8080' // default target + }); + + // Create reverse proxy instance + var proxy = httpProxy.createProxy(); + + // Create http server that leverages reverse proxy instance + // and proxy rules to proxy requests to different targets + http.createServer(function(req, res) { + + // a match method is exposed on the proxy rules instance + // to test a request to see if it matches against one of the specified rules + var target = proxyRules.matchHost(req); + if (target) { + return proxy.web(req, res, { + target: target + }); + } + + res.writeHead(500, { 'Content-Type': 'text/plain' }); + res.end('The request host did not match any of the listed rules!'); + }).listen(6010, cb); +``` + +Given the object we used to initialize the `HttpProxyRules` instance above, here are some [**examples**](test/index.tests.js#L38) of how sample url paths would be translated. ## Options @@ -58,11 +95,14 @@ You can initialize a new `http-proxy-rules` instance with the following options: ```js { rules: {}, // See notes below + hosts: {}, // See notes below default: '' // (optional) if no rules matched, translate url path to specified default } ``` The rules object contains a set of key-value pairs mapping a regex-supported url path to a target route. The module only tries to match the visited url path, and not the entire url, with a specified rule. The target route must include the protocol (e.g., http) and the FQDN. You can use capturing groups when constructing a rule key (e.g. `'/posts/(\d+)/`). In this case, `$1` in the target path will be replaced with the value from the first capturing group, `$2` with the second one, and so on. See the [tests](test/index.tests.js) for examples of how incoming route url paths may be translated with the use of this module. +The hosts object contains a set of key-value pairs mapping a host to a target host:port. + ## Other Notes * `(?:\\W|$)` is appended to the end of the regex-supported url path, so that if there is a key like `.*/test` in the rules, the module matches paths `/test`, `/test/`, `/test?` but not `/testing`. * As long as object keys continued to be ordered in V8, if there are multiple rules that match against a given url path, the module will pick the matching rule listed first for the translation. diff --git a/example/simple.js b/example/simple.js index 3761b4d..dd78c8f 100644 --- a/example/simple.js +++ b/example/simple.js @@ -13,6 +13,9 @@ module.exports = function spawnReverseProxy(cb) { '/posts/([0-9]+)/comments/([0-9]+)': 'http://localhost:8080/p/$1/c/$2', // Rule (3) '/author/([0-9]+)/posts/([0-9]+)/': 'http://localhost:8080/a/$1/p/$2/' // Rule (4) }, + hosts: { + 'testhost': 'http://localhost:8080/testhost' + }, default: 'http://localhost:8080' // default target }); @@ -32,6 +35,13 @@ module.exports = function spawnReverseProxy(cb) { }); } + var target = proxyRules.matchHost(req); + if (target) { + return proxy.web(req, res, { + target: target + }); + } + res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end('The request url and path did not match any of the listed rules!'); }).listen(6010, cb); diff --git a/index.js b/index.js index 651cfa8..600da7e 100644 --- a/index.js +++ b/index.js @@ -1,10 +1,11 @@ /** * This is a constructor for a HttpProxyRules instance. - * @param {Object} options Takes in a `rules` obj, (optional) `default` target + * @param {Object} options Takes in a `rules` obj, a `hosts` obj, (optional) `default` target */ function HttpProxyRules(options) { this.rules = options.rules; + this.hosts = options.hosts; this.default = options.default || null; return this; @@ -55,4 +56,23 @@ HttpProxyRules.prototype.match = function match(req) { return target; } +/** + * This function will modify the `req` object if a matching host is found. + * We also return the new endpoint string if a match is found. + * @param {Object} options Takes in a `req` object. + */ +HttpProxyRules.prototype.matchHost = function matchHost(req) { + var hosts = this.hosts; + var target = this.default; + reqHost = req.headers['host']; + + for (var host in hosts) { + if (host === reqHost) { + target = hosts[host]; + break; + } + } + return target; +} + module.exports = HttpProxyRules; diff --git a/test/index.tests.js b/test/index.tests.js index 094dccb..83d590a 100644 --- a/test/index.tests.js +++ b/test/index.tests.js @@ -19,7 +19,9 @@ describe('Proxy Routes', function () { res.writeHead(200, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ // response includes the url that you tried to access - translatedPath: req.url + translatedPath: req.url, + translatedHost: req.headers['host'], + translatedPort: req.socket.localPort })); }).listen(targetPort, function mockServerReady() { spawnReverseProxy(function proxyServerReady() { @@ -115,4 +117,19 @@ describe('Proxy Routes', function () { }); + it('should translate the port correctly', function (done) { + request({ + url: 'http://127.0.0.1:' + proxyServerPort + '/testhost', + json: true, + headers: { + 'Host': 'testhost' + } + }, function processResp(err, res, body) { + + expect(res.statusCode).to.equal(200); + expect(body.translatedPort + body.translatedPath).to.equal('8080/testhost'); + done(); + }); + }); + });