Skip to content

Commit 1221939

Browse files
committed
[api] Completely refactored node-http-proxy with help from Mikeal
1 parent c887a75 commit 1221939

File tree

4 files changed

+146
-213
lines changed

4 files changed

+146
-213
lines changed

README.md

+11-20
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ see the [demo](http://github.com/nodejitsu/node-http-proxy/blob/master/demo.js)
5252
httpProxy = require('http-proxy');
5353

5454
// create a proxy server with custom application logic
55-
httpProxy.createServer(function (req, res, proxy) {
55+
httpProxy.createServer(function (req, res, proxyRequest) {
5656
// Put your custom server logic here
57-
proxy.proxyRequest(9000, 'localhost', req, res);
57+
proxyRequest(9000, 'localhost');
5858
}).listen(8000);
5959

6060
http.createServer(function (req, res){
@@ -65,25 +65,16 @@ see the [demo](http://github.com/nodejitsu/node-http-proxy/blob/master/demo.js)
6565

6666
</pre>
6767

68-
### How to proxy requests with a regular http server
69-
<pre>
70-
var http = require('http'),
71-
httpProxy = require('http-proxy');
68+
### How to proxy requests with latent operations (IO, etc.)
7269

73-
// create a regular http server and proxy its handler
74-
http.createServer(function (req, res){
75-
var proxy = new httpProxy.HttpProxy;
76-
proxy.watch(req, res);
77-
// Put your custom server logic here
78-
proxy.proxyRequest(9000, 'localhost', req, res);
79-
}).listen(8001);
70+
node-http-proxy supports event buffering, that means if an event (like 'data', or 'end') is raised by the incoming request before you have a chance to perform your custom server logic, those events will be captured and re-raised when you later proxy the request. Here's a simple example:
8071

81-
http.createServer(function (req, res){
82-
res.writeHead(200, {'Content-Type': 'text/plain'});
83-
res.write('request successfully proxied: ' + req.url +'\n' + JSON.stringify(req.headers, true, 2));
84-
res.end();
85-
}).listen(9000);
86-
72+
<pre>
73+
httpProxy.createServer(function (req, res, proxyRequest) {
74+
setTimeout(function () {
75+
proxyRequest(port, server);
76+
}, latency);
77+
}).listen(8081);
8778
</pre>
8879

8980
### Why doesn't node-http-proxy have more advanced features like x, y, or z?
@@ -96,7 +87,7 @@ If you have a suggestion for a feature currently not supported, feel free to ope
9687

9788
(The MIT License)
9889

99-
Copyright (c) 2010 Charlie Robbins & Marak Squires http://github.com/nodejitsu/
90+
Copyright (c) 2010 Mikeal Rogers, Charlie Robbins & Marak Squires
10091

10192
Permission is hereby granted, free of charge, to any person obtaining
10293
a copy of this software and associated documentation files (the

demo.js

+4-15
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
var sys = require('sys'),
2828
colors = require('colors')
2929
http = require('http'),
30-
httpProxy = require('http-proxy');
30+
httpProxy = require('./lib/node-http-proxy');
3131

3232
// ascii art from http://github.com/marak/asciimo
3333
var welcome = '\
@@ -45,24 +45,13 @@ httpProxy.createServer(9000, 'localhost').listen(8000);
4545
sys.puts('http proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8000'.yellow);
4646

4747
/****** http proxy server with latency******/
48-
httpProxy.createServer(function (req, res, proxy){
48+
httpProxy.createServer(function (req, res, proxyRequest){
4949
setTimeout(function(){
50-
proxy.proxyRequest(9000, 'localhost', req, res);
51-
}, 200)
50+
proxyRequest(9000, 'localhost', req, res);
51+
}, 2000)
5252
}).listen(8001);
5353
sys.puts('http proxy server '.blue + 'started '.green.bold + 'on port '.blue + '8001 '.yellow + 'with latency'.magenta.underline );
5454

55-
/****** http server with proxyRequest handler and latency******/
56-
http.createServer(function (req, res){
57-
var proxy = new httpProxy.HttpProxy;
58-
proxy.watch(req, res);
59-
60-
setTimeout(function(){
61-
proxy.proxyRequest(9000, 'localhost', req, res);
62-
}, 200);
63-
}).listen(8002);
64-
sys.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '8002 '.yellow + 'with proxyRequest handler'.cyan.underline + ' and latency'.magenta);
65-
6655
/****** regular http server ******/
6756
http.createServer(function (req, res){
6857
res.writeHead(200, {'Content-Type': 'text/plain'});

lib/node-http-proxy.js

+96-158
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
2-
node-http-proxy.js: http proxy for node.js
2+
node-http-proxy.js: http proxy for node.js with pooling and event buffering
33
4-
Copyright (c) 2010 Charlie Robbins & Marak Squires http://github.com/nodejitsu/node-http-proxy
4+
Copyright (c) 2010 Mikeal Rogers, Charlie Robbins
55
66
Permission is hereby granted, free of charge, to any person obtaining
77
a copy of this software and associated documentation files (the
@@ -24,172 +24,110 @@
2424
2525
*/
2626

27-
var sys = require('sys'),
28-
http = require('http'),
29-
events = require('events');
30-
31-
exports.HttpProxy = function () {
32-
this.emitter = new(events.EventEmitter);
33-
this.events = {};
34-
this.listeners = {};
35-
this.collisions = {};
36-
};
27+
var sys = require('sys'),
28+
http = require('http'),
29+
pool = require('pool'),
30+
url = require('url'),
31+
events = require('events'),
32+
min = 0,
33+
max = 100;
34+
35+
// Setup the PoolManager
36+
var manager = pool.createPoolManager();
37+
manager.setMinClients(min);
38+
manager.setMaxClients(max);
3739

3840
exports.createServer = function () {
39-
// Initialize the nodeProxy to start proxying requests
40-
var proxy = new (exports.HttpProxy);
41-
return proxy.createServer.apply(proxy, arguments);
42-
};
43-
44-
exports.HttpProxy.prototype = {
45-
toArray: function (obj){
46-
var len = obj.length,
47-
arr = new Array(len);
48-
for (var i = 0; i < len; ++i) {
49-
arr[i] = obj[i];
50-
}
51-
return arr;
52-
},
41+
var args, action, port, host;
42+
args = Array.prototype.slice.call(arguments);
43+
action = typeof args[args.length - 1] === 'function' && args.pop();
44+
if (args[0]) port = args[0];
45+
if (args[1]) host = args[1];
5346

54-
createServer: function () {
55-
var self = this,
56-
server,
57-
port,
58-
callback;
59-
60-
if (typeof(arguments[0]) === "function") {
61-
callback = arguments[0];
62-
}
47+
var proxy = createProxy();
48+
proxy.on('route', function (req, res, callback) {
49+
var uri = url.parse(req.url);
50+
if (action) {
51+
action(req, res, callback);
52+
}
6353
else {
64-
port = arguments[0];
65-
server = arguments[1];
54+
port = port ? port : uri.port ? uri.port : 80;
55+
host = host ? host : uri.hostname;
56+
callback(port, host);
6657
}
67-
68-
var proxyServer = http.createServer(function (req, res){
69-
self.watch(req, res);
70-
71-
// If we were passed a callback to process the request
72-
// or response in some way, then call it.
73-
if(callback) {
74-
callback(req, res, self);
75-
}
76-
else {
77-
self.proxyRequest(port, server, req, res);
78-
}
79-
});
80-
81-
return proxyServer;
82-
},
83-
84-
watch: function (req, res) {
85-
var self = this;
86-
87-
// Create a unique id for this request so
88-
// we can reference it later.
89-
var id = new Date().getTime().toString();
90-
91-
// If we get a request in the same tick, we need to
92-
// append to the id so it stays unique.
93-
if(typeof this.collisions[id] === 'undefined') {
94-
this.collisions[id] = 0;
95-
}
96-
else {
97-
this.collisions[id]++;
98-
id += this.collisions[id];
99-
}
100-
101-
req.id = id;
102-
this.events[req.id] = [];
103-
104-
this.listeners[req.id] = {
105-
onData: function () {
106-
self.events[req.id].push(['data'].concat(self.toArray(arguments)));
107-
},
108-
onEnd: function () {
109-
self.events[req.id].push(['end'].concat(self.toArray(arguments)));
110-
}
111-
};
112-
113-
req.addListener('data', this.listeners[req.id].onData);
114-
req.addListener('end', this.listeners[req.id].onEnd);
115-
116-
},
117-
118-
unwatch: function (req, res) {
119-
req.removeListener('data', this.listeners[req.id].onData);
120-
req.removeListener('end', this.listeners[req.id].onEnd);
121-
122-
// Rebroadcast any events that have been buffered
123-
while(this.events[req.id].length > 0) {
124-
var args = this.events[req.id].shift();
125-
req.emit.apply(req, args);
126-
}
127-
128-
// Remove the data from the event and listeners hashes
129-
delete this.listeners[req.id];
130-
delete this.events[req.id];
131-
132-
// If this request id is a base time, delete it
133-
if (typeof this.collisions[req.id] !== 'undefined') {
134-
delete this.collisions[req.id];
135-
}
136-
},
137-
138-
proxyRequest: function (port, server, req, res) {
139-
// Remark: nodeProxy.body exists solely for testability
140-
this.body = '';
141-
var self = this;
142-
143-
// Open new HTTP request to internal resource with will act as a reverse proxy pass
144-
var c = http.createClient(port, server);
145-
146-
// Make request to internal server, passing along the method and headers
147-
var reverse_proxy = c.request(req.method, req.url, req.headers);
148-
149-
// Add a listener for the connection timeout event
150-
reverse_proxy.connection.addListener('error', function (err) {
151-
res.writeHead(200, {'Content-Type': 'text/plain'});
152-
153-
if(req.method !== 'HEAD') {
154-
res.write('An error has occurred: ' + sys.puts(JSON.stringify(err)));
155-
}
58+
});
59+
return proxy;
60+
};
15661

157-
res.end();
158-
});
62+
exports.setMin = function (value) {
63+
min = value;
64+
manager.setMinClients(min);
65+
};
15966

67+
exports.setMax = function (value) {
68+
max = value;
69+
manager.setMaxClients(max);
70+
}
16071

161-
// Add a listener for the reverse_proxy response event
162-
reverse_proxy.addListener('response', function (response) {
163-
// Set the response headers of the client response
164-
res.writeHead(response.statusCode, response.headers);
72+
var createProxy = function () {
73+
var server = http.createServer(function (req, res) {
74+
var buffers = [],
75+
b = function (chunk) { buffers.push(chunk) },
76+
e = function () { e = false };
77+
78+
req.on('data', b);
79+
req.on('end', e);
80+
81+
server.emit('route', req, res, function (port, hostname) {
82+
var p = manager.getPool(port, hostname);
16583

166-
// Add event handler for the proxied response in chunks
167-
response.addListener('data', function (chunk) {
168-
if(req.method !== 'HEAD') {
169-
res.write(chunk, 'binary');
170-
self.body += chunk;
84+
p.request(req.method, req.url, req.headers, function (reverse_proxy) {
85+
var data = '';
86+
reverse_proxy.on('error', function (err) {
87+
res.writeHead(500, {'Content-Type': 'text/plain'});
88+
89+
if(req.method !== 'HEAD') {
90+
res.write('An error has occurred: ' + sys.puts(JSON.stringify(err)));
91+
}
92+
93+
res.end();
94+
});
95+
96+
buffers.forEach(function (c) {
97+
data += c;
98+
reverse_proxy.write(c);
99+
});
100+
101+
buffers = null;
102+
req.removeListener('data', b);
103+
sys.pump(req, reverse_proxy);
104+
105+
if (e) {
106+
req.removeListener('end', e);
107+
req.addListener('end', function () { reverse_proxy.end() });
108+
}
109+
else {
110+
reverse_proxy.end();
171111
}
172-
});
173112

174-
// Add event listener for end of proxied response
175-
response.addListener('end', function () {
176-
// Remark: Emit the end event for testability
177-
self.emitter.emit('end', null, self.body);
178-
179-
res.end();
113+
// Add a listener for the reverse_proxy response event
114+
reverse_proxy.addListener('response', function (response) {
115+
// These two listeners are for testability and observation
116+
// of what's passed back from the target server
117+
response.addListener('data', function (chunk) {
118+
data += chunk;
119+
});
120+
121+
response.addListener('end', function() {
122+
server.emit('proxy', null, data);
123+
});
124+
125+
// Set the response headers of the client response
126+
res.writeHead(response.statusCode, response.headers);
127+
sys.pump(response, res);
128+
});
180129
});
181130
});
182-
183-
// Chunk the client request body as chunks from the proxied request come in
184-
req.addListener('data', function (chunk) {
185-
reverse_proxy.write(chunk, 'binary');
186-
})
187-
188-
// At the end of the client request, we are going to stop the proxied request
189-
req.addListener('end', function () {
190-
reverse_proxy.end();
191-
});
192-
193-
this.unwatch(req, res);
194-
}
195-
};
131+
})
132+
return server;
133+
};

0 commit comments

Comments
 (0)