Commit c06f4bf7 authored by indexzero's avatar indexzero

[test doc api] Added forward proxy functionality with tests

parent bedc7a3a
......@@ -9,6 +9,8 @@
- reverse-proxies incoming http.Server requests
- can be used as a CommonJS module in node.js
- uses event buffering to support application latency in proxied requests
- can proxy based on simple JSON-based configuration
- forward proxying based on simple JSON-based configuration
- minimal request overhead and latency
- fully-tested
- battled-hardened through production usage @ [nodejitsu.com][0]
......@@ -35,8 +37,9 @@ There are several ways to use node-http-proxy; the library is designed to be fle
1. Standalone HTTP Proxy server
2. Inside of another HTTP server (like Connect)
3. From the command-line as a proxy daemon
4. In conjunction with a Proxy Routing Table
3. In conjunction with a Proxy Routing Table
4. As a forward-proxy with a reverse proxy
5. From the command-line as a proxy daemon
### Setup a basic stand-alone proxy server
<pre>
......@@ -132,8 +135,23 @@ The above route table will take incoming requests to 'foo.com' and forward them
<pre>
var proxyServer = httpProxy.createServer(options);
proxyServer.listen(80);
</pre>
### Proxy requests with an additional forward proxy
Sometimes in addition to a reverse proxy, you may want your front-facing server to forward traffic to another location. For example, if you wanted to load test your staging environment. This is possible when using node-http-proxy using similar JSON-based configuration to a proxy table:
<pre>
var proxyServerWithForwarding = httpProxy.createServer(9000, 'localhost', {
forward: {
port: 9000,
host: 'staging.com'
}
});
proxyServerWithForwarding.listen(80);
</pre>
The forwarding option can be used in conjunction with the proxy table options by simply including both the 'forward' and 'router' properties in the options passed to 'createServer'.
<br/>
### Why doesn't node-http-proxy have more advanced features like x, y, or z?
If you have a suggestion for a feature currently not supported, feel free to open a [support issue](http://github.com/nodejitsu/node-http-proxy/issues). node-http-proxy is designed to just proxy http requests from one server to another, but we will be soon releasing many other complimentary projects that can be used in conjunction with node-http-proxy.
......
......@@ -66,6 +66,17 @@ httpProxy.createServer(function (req, res, proxy) {
}).listen(8002);
sys.puts('http proxy server '.blue + 'started '.green.bold + 'on port '.blue + '8002 '.yellow + 'with latency'.magenta.underline);
//
//
//
httpProxy.createServer(9000, 'localhost', {
forward: {
port: 9001,
host: 'localhost'
}
}).listen(8003);
sys.puts('http proxy server '.blue + 'started '.green.bold + 'on port '.blue + '8003 '.yellow + 'with forward proxy'.magenta.underline)
//
// Http Server with proxyRequest Handler and Latency
//
......@@ -75,8 +86,8 @@ http.createServer(function (req, res) {
setTimeout(function() {
proxy.proxyRequest(9000, 'localhost');
}, 200);
}).listen(8003);
sys.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '8003 '.yellow + 'with proxyRequest handler'.cyan.underline + ' and latency'.magenta);
}).listen(8004);
sys.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '8004 '.yellow + 'with proxyRequest handler'.cyan.underline + ' and latency'.magenta);
//
// Target Http Server
......@@ -87,3 +98,14 @@ http.createServer(function (req, res) {
res.end();
}).listen(9000);
sys.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9000 '.yellow);
//
// Target Http Forwarding Server
//
http.createServer(function (req, res) {
sys.puts('Receiving forward for: ' + req.url)
res.writeHead(200, {'Content-Type': 'text/plain'});
res.write('request successfully forwarded to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2));
res.end();
}).listen(9001);
sys.puts('http forward server '.blue + 'started '.green.bold + 'on port '.blue + '9001 '.yellow);
......@@ -60,6 +60,11 @@ exports.createServer = function () {
var server = http.createServer(function (req, res) {
var proxy = new HttpProxy(req, res);
if (options && options.forward) {
var forward = new HttpProxy(req, res);
forward.forwardRequest(options.forward.port, options.forward.host);
}
// If we were passed a callback to process the request
// or response in some way, then call it.
if (callback) {
......@@ -154,10 +159,8 @@ HttpProxy.prototype = {
},
proxyRequest: function (port, server) {
// Remark: nodeProxy.body exists solely for testability
var self = this, req = this.req, res = this.res;
self.body = '';
// Open new HTTP request to internal resource with will act as a reverse proxy pass
var p = manager.getPool(port, server);
......@@ -167,7 +170,6 @@ HttpProxy.prototype = {
// should be emitting this event.
});
var client = http.createClient(port, server);
p.request(req.method, req.url, req.headers, function (reverse_proxy) {
// Create an error handler so we can use it temporarily
function error (obj) {
......@@ -187,11 +189,8 @@ HttpProxy.prototype = {
};
// Add a listener for the connection timeout event
var reverseProxyError = error(reverse_proxy),
clientError = error(client);
var reverseProxyError = error(reverse_proxy);
reverse_proxy.addListener('error', reverseProxyError);
client.addListener('error', clientError);
// Add a listener for the reverse_proxy response event
reverse_proxy.addListener('response', function (response) {
......@@ -235,9 +234,37 @@ HttpProxy.prototype = {
reverse_proxy.end();
});
// On 'close' event remove 'error' listener
client.addListener('close', function() {
client.removeListener('error', clientError);
self.unwatch(req);
});
},
forwardRequest: function (port, server) {
var self = this, req = this.req;
// Open new HTTP request to internal resource with will act as a reverse proxy pass
var p = manager.getPool(port, server);
p.on('error', function (err) {
// Remark: We should probably do something here
// but this is a hot-fix because I don't think 'pool'
// should be emitting this event.
});
p.request(req.method, req.url, req.headers, function (forward_proxy) {
// Add a listener for the connection timeout event
forward_proxy.addListener('error', function (err) {
// Remark: Ignoring this error in the event
// forward target doesn't exist.
});
// Chunk the client request body as chunks from the proxied request come in
req.addListener('data', function (chunk) {
forward_proxy.write(chunk, 'binary');
})
// At the end of the client request, we are going to stop the proxied request
req.addListener('end', function () {
forward_proxy.end();
});
self.unwatch(req);
......
/*
* forward-proxy-test.js: Tests for node-http-proxy forwarding functionality.
*
* (C) 2010, Charlie Robbins
*
*/
var fs = require('fs'),
vows = require('vows'),
sys = require('sys'),
path = require('path'),
request = require('request'),
assert = require('assert'),
helpers = require('./helpers'),
TestRunner = helpers.TestRunner;
var runner = new TestRunner(),
assertProxiedWithTarget = helpers.assertProxiedWithTarget,
assertProxiedWithNoTarget = helpers.assertProxiedWithNoTarget;
var forwardOptions = {
forward: {
port: 8300,
host: 'localhost'
}
};
var badForwardOptions = {
forward: {
port: 9000,
host: 'localhost'
}
};
vows.describe('node-http-proxy').addBatch({
"When using server created by httpProxy.createServer()": {
"with forwarding enabled": {
topic: function () {
runner.startTargetServer(8300, 'forward proxy');
return null;
},
"with no latency" : {
"and a valid target server": assertProxiedWithTarget(runner, 'localhost', 8120, 8121, function () {
runner.startProxyServerWithForwarding(8120, 8121, 'localhost', forwardOptions);
}),
"and without a valid forward server": assertProxiedWithTarget(runner, 'localhost', 8122, 8123, function () {
runner.startProxyServerWithForwarding(8122, 8123, 'localhost', badForwardOptions);
})
}
}
}
}).addBatch({
"When the tests are over": {
topic: function () {
return runner.closeServers();
},
"the servers should clean up": function () {
assert.isTrue(true);
}
}
}).export(module);
\ No newline at end of file
......@@ -6,8 +6,63 @@
*/
var http = require('http'),
vows = require('vows'),
assert = require('assert'),
request = require('request'),
httpProxy = require('./../lib/node-http-proxy');
exports.assertProxiedWithTarget = function (runner, host, proxyPort, port, createProxy) {
var assertion = "should receive 'hello " + host + "'",
output = 'hello ' + host;
var test = {
topic: function () {
var options = {
method: 'GET',
uri: 'http://localhost:' + proxyPort,
headers: {
host: host
}
};
if (createProxy) createProxy();
if (port) runner.startTargetServer(port, output);
request(options, this.callback);
}
};
test[assertion] = function (err, res, body) {
assert.equal(body, output);
};
return test;
};
exports.assertProxiedWithNoTarget = function (runner, proxyPort, statusCode, createProxy) {
var assertion = "should receive " + statusCode + " responseCode";
var test = {
topic: function () {
var options = {
method: 'GET',
uri: 'http://localhost:' + proxyPort,
headers: {
host: 'unknown.com'
}
};
if (createProxy) createProxy();
request(options, this.callback);
}
};
test[assertion] = function (err, res, body) {
assert.equal(res.statusCode, statusCode);
};
return test;
}
var TestRunner = function () {
this.testServers = [];
}
......@@ -70,6 +125,16 @@ TestRunner.prototype.startProxyServerWithTableAndLatency = function (port, laten
return proxyServer;
};
//
// Creates proxy server forwarding to the specified options
//
TestRunner.prototype.startProxyServerWithForwarding = function (port, targetPort, host, options) {
var proxyServer = httpProxy.createServer(targetPort, host, options);
proxyServer.listen(port);
this.testServers.push(proxyServer);
return proxyServer;
};
//
// Creates the 'hellonode' server
//
......
......@@ -28,81 +28,30 @@ var vows = require('vows'),
sys = require('sys'),
request = require('request'),
assert = require('assert'),
TestRunner = require('./helpers').TestRunner;
helpers = require('./helpers'),
TestRunner = helpers.TestRunner;
var runner = new TestRunner();
var runner = new TestRunner(),
assertProxiedWithTarget = helpers.assertProxiedWithTarget,
assertProxiedWithNoTarget = helpers.assertProxiedWithNoTarget;
vows.describe('node-http-proxy').addBatch({
"When using server created by httpProxy.createServer()": {
"an incoming request to the helloNode server": {
"with no latency" : {
"and a valid target server": {
topic: function () {
this.output = 'hello world';
var options = {
method: 'GET',
uri: 'http://localhost:8080'
};
runner.startProxyServer(8080, 8081, 'localhost'),
runner.startTargetServer(8081, this.output);
request(options, this.callback);
},
"should received 'hello world'": function (err, res, body) {
assert.equal(body, this.output);
}
},
"and without a valid target server": {
topic: function () {
runner.startProxyServer(8082, 9000, 'localhost');
var options = {
method: 'GET',
uri: 'http://localhost:8082'
};
request(options, this.callback);
},
"should receive 500 response code": function (err, res, body) {
assert.equal(res.statusCode, 500);
}
}
},
"with latency": {
"and a valid target server": {
topic: function () {
this.output = 'hello world';
var options = {
method: 'GET',
uri: 'http://localhost:8083'
};
runner.startLatentProxyServer(8083, 8084, 'localhost', 1000),
runner.startTargetServer(8084, this.output);
request(options, this.callback);
},
"should receive 'hello world'": function (err, res, body) {
assert.equal(body, this.output);
}
},
"and without a valid target server": {
topic: function () {
runner.startLatentProxyServer(8085, 9000, 'localhost', 1000);
var options = {
method: 'GET',
uri: 'http://localhost:8085'
};
request(options, this.callback);
},
"should receive 500 response code": function (err, res, body) {
assert.equal(res.statusCode, 500);
}
}
}
"with no latency" : {
"and a valid target server": assertProxiedWithTarget(runner, 'localhost', 8080, 8081, function () {
runner.startProxyServer(8080, 8081, 'localhost');
}),
"and without a valid target server": assertProxiedWithNoTarget(runner, 8082, 500, function () {
runner.startProxyServer(8082, 9000, 'localhost');
})
},
"with latency": {
"and a valid target server": assertProxiedWithTarget(runner, 'localhost', 8083, 8084, function () {
runner.startLatentProxyServer(8083, 8084, 'localhost', 1000);
}),
"and without a valid target server": assertProxiedWithNoTarget(runner, 8085, 500, function () {
runner.startLatentProxyServer(8085, 9000, 'localhost', 1000);
})
}
}
}).addBatch({
......
......@@ -11,10 +11,13 @@ var fs = require('fs'),
path = require('path'),
request = require('request'),
assert = require('assert'),
TestRunner = require('./helpers').TestRunner;
helpers = require('./helpers'),
TestRunner = helpers.TestRunner;
var runner = new TestRunner(),
routeFile = path.join(__dirname, 'config.json');
routeFile = path.join(__dirname, 'config.json'),
assertProxiedWithTarget = helpers.assertProxiedWithTarget,
assertProxiedWithNoTarget = helpers.assertProxiedWithNoTarget;
var fileOptions = {
router: {
......@@ -30,51 +33,6 @@ var defaultOptions = {
}
};
function createTargetTest (host, proxyPort, port) {
var assertion = "should receive 'hello " + host + "'",
output = 'hello ' + host;
var test = {
topic: function () {
var options = {
method: 'GET',
uri: 'http://localhost:' + proxyPort,
headers: {
host: host
}
};
if (port) runner.startTargetServer(port, output);
request(options, this.callback);
}
};
test[assertion] = function (err, res, body) {
assert.equal(body, output);
};
return test;
};
function createNoTargetTest (proxyPort) {
return {
topic: function () {
var options = {
method: 'GET',
uri: 'http://localhost:' + proxyPort,
headers: {
host: 'unknown.com'
}
};
request(options, this.callback);
},
"should receive 404 response code": function (err, res, body) {
assert.equal(res.statusCode, 404);
}
};
}
vows.describe('proxy-table').addBatch({
"When using server created by httpProxy.createServer()": {
"when passed a routing table": {
......@@ -82,9 +40,9 @@ vows.describe('proxy-table').addBatch({
this.server = runner.startProxyServerWithTable(8090, defaultOptions);
return null;
},
"an incoming request to foo.com": createTargetTest('foo.com', 8090, 8091),
"an incoming request to bar.com": createTargetTest('bar.com', 8090, 8092),
"an incoming request to unknown.com": createNoTargetTest(8090)
"an incoming request to foo.com": assertProxiedWithTarget(runner, 'foo.com', 8090, 8091),
"an incoming request to bar.com": assertProxiedWithTarget(runner, 'bar.com', 8090, 8092),
"an incoming request to unknown.com": assertProxiedWithNoTarget(runner, 8090, 404)
},
"when passed a routing file": {
topic: function () {
......@@ -95,9 +53,9 @@ vows.describe('proxy-table').addBatch({
return null;
},
"an incoming request to foo.com": createTargetTest('foo.com', 8100, 8101),
"an incoming request to bar.com": createTargetTest('bar.com', 8100, 8102),
"an incoming request to unknown.com": createNoTargetTest(8100),
"an incoming request to foo.com": assertProxiedWithTarget(runner, 'foo.com', 8100, 8101),
"an incoming request to bar.com": assertProxiedWithTarget(runner, 'bar.com', 8100, 8102),
"an incoming request to unknown.com": assertProxiedWithNoTarget(runner, 8100, 404),
"an incoming request to dynamic.com": {
"after the file has been modified": {
topic: function () {
......@@ -138,9 +96,9 @@ vows.describe('proxy-table').addBatch({
});
return null;
},
"an incoming request to foo.com": createTargetTest('foo.com', 8110, 8111),
"an incoming request to bar.com": createTargetTest('bar.com', 8110, 8112),
"an incoming request to unknown.com": createNoTargetTest(8110)
"an incoming request to foo.com": assertProxiedWithTarget(runner, 'foo.com', 8110, 8111),
"an incoming request to bar.com": assertProxiedWithTarget(runner, 'bar.com', 8110, 8112),
"an incoming request to unknown.com": assertProxiedWithNoTarget(runner, 8110, 404)
}
}).addBatch({
"When the tests are over": {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment