Skip to content

Commit c06f4bf

Browse files
committed
[test doc api] Added forward proxy functionality with tests
1 parent bedc7a3 commit c06f4bf

7 files changed

+242
-142
lines changed

README.md

+20-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
- reverse-proxies incoming http.Server requests
1010
- can be used as a CommonJS module in node.js
1111
- uses event buffering to support application latency in proxied requests
12+
- can proxy based on simple JSON-based configuration
13+
- forward proxying based on simple JSON-based configuration
1214
- minimal request overhead and latency
1315
- fully-tested
1416
- 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
3537

3638
1. Standalone HTTP Proxy server
3739
2. Inside of another HTTP server (like Connect)
38-
3. From the command-line as a proxy daemon
39-
4. In conjunction with a Proxy Routing Table
40+
3. In conjunction with a Proxy Routing Table
41+
4. As a forward-proxy with a reverse proxy
42+
5. From the command-line as a proxy daemon
4043

4144
### Setup a basic stand-alone proxy server
4245
<pre>
@@ -132,8 +135,23 @@ The above route table will take incoming requests to 'foo.com' and forward them
132135
<pre>
133136
var proxyServer = httpProxy.createServer(options);
134137
proxyServer.listen(80);
138+
</pre>
139+
140+
### Proxy requests with an additional forward proxy
141+
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:
142+
<pre>
143+
var proxyServerWithForwarding = httpProxy.createServer(9000, 'localhost', {
144+
forward: {
145+
port: 9000,
146+
host: 'staging.com'
147+
}
148+
});
149+
proxyServerWithForwarding.listen(80);
135150
</pre>
136151

152+
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'.
153+
154+
<br/>
137155
### Why doesn't node-http-proxy have more advanced features like x, y, or z?
138156

139157
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.

demo.js

+24-2
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,17 @@ httpProxy.createServer(function (req, res, proxy) {
6666
}).listen(8002);
6767
sys.puts('http proxy server '.blue + 'started '.green.bold + 'on port '.blue + '8002 '.yellow + 'with latency'.magenta.underline);
6868

69+
//
70+
//
71+
//
72+
httpProxy.createServer(9000, 'localhost', {
73+
forward: {
74+
port: 9001,
75+
host: 'localhost'
76+
}
77+
}).listen(8003);
78+
sys.puts('http proxy server '.blue + 'started '.green.bold + 'on port '.blue + '8003 '.yellow + 'with forward proxy'.magenta.underline)
79+
6980
//
7081
// Http Server with proxyRequest Handler and Latency
7182
//
@@ -75,8 +86,8 @@ http.createServer(function (req, res) {
7586
setTimeout(function() {
7687
proxy.proxyRequest(9000, 'localhost');
7788
}, 200);
78-
}).listen(8003);
79-
sys.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '8003 '.yellow + 'with proxyRequest handler'.cyan.underline + ' and latency'.magenta);
89+
}).listen(8004);
90+
sys.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '8004 '.yellow + 'with proxyRequest handler'.cyan.underline + ' and latency'.magenta);
8091

8192
//
8293
// Target Http Server
@@ -87,3 +98,14 @@ http.createServer(function (req, res) {
8798
res.end();
8899
}).listen(9000);
89100
sys.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9000 '.yellow);
101+
102+
//
103+
// Target Http Forwarding Server
104+
//
105+
http.createServer(function (req, res) {
106+
sys.puts('Receiving forward for: ' + req.url)
107+
res.writeHead(200, {'Content-Type': 'text/plain'});
108+
res.write('request successfully forwarded to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2));
109+
res.end();
110+
}).listen(9001);
111+
sys.puts('http forward server '.blue + 'started '.green.bold + 'on port '.blue + '9001 '.yellow);

lib/node-http-proxy.js

+38-11
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ exports.createServer = function () {
6060
var server = http.createServer(function (req, res) {
6161
var proxy = new HttpProxy(req, res);
6262

63+
if (options && options.forward) {
64+
var forward = new HttpProxy(req, res);
65+
forward.forwardRequest(options.forward.port, options.forward.host);
66+
}
67+
6368
// If we were passed a callback to process the request
6469
// or response in some way, then call it.
6570
if (callback) {
@@ -154,10 +159,8 @@ HttpProxy.prototype = {
154159
},
155160

156161
proxyRequest: function (port, server) {
157-
// Remark: nodeProxy.body exists solely for testability
158162
var self = this, req = this.req, res = this.res;
159-
self.body = '';
160-
163+
161164
// Open new HTTP request to internal resource with will act as a reverse proxy pass
162165
var p = manager.getPool(port, server);
163166

@@ -167,7 +170,6 @@ HttpProxy.prototype = {
167170
// should be emitting this event.
168171
});
169172

170-
var client = http.createClient(port, server);
171173
p.request(req.method, req.url, req.headers, function (reverse_proxy) {
172174
// Create an error handler so we can use it temporarily
173175
function error (obj) {
@@ -187,11 +189,8 @@ HttpProxy.prototype = {
187189
};
188190

189191
// Add a listener for the connection timeout event
190-
var reverseProxyError = error(reverse_proxy),
191-
clientError = error(client);
192-
192+
var reverseProxyError = error(reverse_proxy);
193193
reverse_proxy.addListener('error', reverseProxyError);
194-
client.addListener('error', clientError);
195194

196195
// Add a listener for the reverse_proxy response event
197196
reverse_proxy.addListener('response', function (response) {
@@ -235,9 +234,37 @@ HttpProxy.prototype = {
235234
reverse_proxy.end();
236235
});
237236

238-
// On 'close' event remove 'error' listener
239-
client.addListener('close', function() {
240-
client.removeListener('error', clientError);
237+
self.unwatch(req);
238+
});
239+
},
240+
241+
forwardRequest: function (port, server) {
242+
var self = this, req = this.req;
243+
244+
// Open new HTTP request to internal resource with will act as a reverse proxy pass
245+
var p = manager.getPool(port, server);
246+
247+
p.on('error', function (err) {
248+
// Remark: We should probably do something here
249+
// but this is a hot-fix because I don't think 'pool'
250+
// should be emitting this event.
251+
});
252+
253+
p.request(req.method, req.url, req.headers, function (forward_proxy) {
254+
// Add a listener for the connection timeout event
255+
forward_proxy.addListener('error', function (err) {
256+
// Remark: Ignoring this error in the event
257+
// forward target doesn't exist.
258+
});
259+
260+
// Chunk the client request body as chunks from the proxied request come in
261+
req.addListener('data', function (chunk) {
262+
forward_proxy.write(chunk, 'binary');
263+
})
264+
265+
// At the end of the client request, we are going to stop the proxied request
266+
req.addListener('end', function () {
267+
forward_proxy.end();
241268
});
242269

243270
self.unwatch(req);

test/forward-proxy-test.js

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* forward-proxy-test.js: Tests for node-http-proxy forwarding functionality.
3+
*
4+
* (C) 2010, Charlie Robbins
5+
*
6+
*/
7+
8+
var fs = require('fs'),
9+
vows = require('vows'),
10+
sys = require('sys'),
11+
path = require('path'),
12+
request = require('request'),
13+
assert = require('assert'),
14+
helpers = require('./helpers'),
15+
TestRunner = helpers.TestRunner;
16+
17+
var runner = new TestRunner(),
18+
assertProxiedWithTarget = helpers.assertProxiedWithTarget,
19+
assertProxiedWithNoTarget = helpers.assertProxiedWithNoTarget;
20+
21+
var forwardOptions = {
22+
forward: {
23+
port: 8300,
24+
host: 'localhost'
25+
}
26+
};
27+
28+
var badForwardOptions = {
29+
forward: {
30+
port: 9000,
31+
host: 'localhost'
32+
}
33+
};
34+
35+
vows.describe('node-http-proxy').addBatch({
36+
"When using server created by httpProxy.createServer()": {
37+
"with forwarding enabled": {
38+
topic: function () {
39+
runner.startTargetServer(8300, 'forward proxy');
40+
return null;
41+
},
42+
"with no latency" : {
43+
"and a valid target server": assertProxiedWithTarget(runner, 'localhost', 8120, 8121, function () {
44+
runner.startProxyServerWithForwarding(8120, 8121, 'localhost', forwardOptions);
45+
}),
46+
"and without a valid forward server": assertProxiedWithTarget(runner, 'localhost', 8122, 8123, function () {
47+
runner.startProxyServerWithForwarding(8122, 8123, 'localhost', badForwardOptions);
48+
})
49+
}
50+
}
51+
}
52+
}).addBatch({
53+
"When the tests are over": {
54+
topic: function () {
55+
return runner.closeServers();
56+
},
57+
"the servers should clean up": function () {
58+
assert.isTrue(true);
59+
}
60+
}
61+
}).export(module);

test/helpers.js

+65
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,63 @@
66
*/
77

88
var http = require('http'),
9+
vows = require('vows'),
10+
assert = require('assert'),
11+
request = require('request'),
912
httpProxy = require('./../lib/node-http-proxy');
1013

14+
exports.assertProxiedWithTarget = function (runner, host, proxyPort, port, createProxy) {
15+
var assertion = "should receive 'hello " + host + "'",
16+
output = 'hello ' + host;
17+
18+
var test = {
19+
topic: function () {
20+
var options = {
21+
method: 'GET',
22+
uri: 'http://localhost:' + proxyPort,
23+
headers: {
24+
host: host
25+
}
26+
};
27+
28+
if (createProxy) createProxy();
29+
if (port) runner.startTargetServer(port, output);
30+
request(options, this.callback);
31+
}
32+
};
33+
34+
test[assertion] = function (err, res, body) {
35+
assert.equal(body, output);
36+
};
37+
38+
return test;
39+
};
40+
41+
exports.assertProxiedWithNoTarget = function (runner, proxyPort, statusCode, createProxy) {
42+
var assertion = "should receive " + statusCode + " responseCode";
43+
44+
var test = {
45+
topic: function () {
46+
var options = {
47+
method: 'GET',
48+
uri: 'http://localhost:' + proxyPort,
49+
headers: {
50+
host: 'unknown.com'
51+
}
52+
};
53+
54+
if (createProxy) createProxy();
55+
request(options, this.callback);
56+
}
57+
};
58+
59+
test[assertion] = function (err, res, body) {
60+
assert.equal(res.statusCode, statusCode);
61+
};
62+
63+
return test;
64+
}
65+
1166
var TestRunner = function () {
1267
this.testServers = [];
1368
}
@@ -70,6 +125,16 @@ TestRunner.prototype.startProxyServerWithTableAndLatency = function (port, laten
70125
return proxyServer;
71126
};
72127

128+
//
129+
// Creates proxy server forwarding to the specified options
130+
//
131+
TestRunner.prototype.startProxyServerWithForwarding = function (port, targetPort, host, options) {
132+
var proxyServer = httpProxy.createServer(targetPort, host, options);
133+
proxyServer.listen(port);
134+
this.testServers.push(proxyServer);
135+
return proxyServer;
136+
};
137+
73138
//
74139
// Creates the 'hellonode' server
75140
//

0 commit comments

Comments
 (0)