Commit 1221939a authored by indexzero's avatar indexzero

[api] Completely refactored node-http-proxy with help from Mikeal

parent c887a757
...@@ -52,9 +52,9 @@ see the [demo](http://github.com/nodejitsu/node-http-proxy/blob/master/demo.js) ...@@ -52,9 +52,9 @@ see the [demo](http://github.com/nodejitsu/node-http-proxy/blob/master/demo.js)
httpProxy = require('http-proxy'); httpProxy = require('http-proxy');
// create a proxy server with custom application logic // create a proxy server with custom application logic
httpProxy.createServer(function (req, res, proxy) { httpProxy.createServer(function (req, res, proxyRequest) {
// Put your custom server logic here // Put your custom server logic here
proxy.proxyRequest(9000, 'localhost', req, res); proxyRequest(9000, 'localhost');
}).listen(8000); }).listen(8000);
http.createServer(function (req, res){ http.createServer(function (req, res){
...@@ -65,25 +65,16 @@ see the [demo](http://github.com/nodejitsu/node-http-proxy/blob/master/demo.js) ...@@ -65,25 +65,16 @@ see the [demo](http://github.com/nodejitsu/node-http-proxy/blob/master/demo.js)
</pre> </pre>
### How to proxy requests with a regular http server ### How to proxy requests with latent operations (IO, etc.)
<pre>
var http = require('http'),
httpProxy = require('http-proxy');
// create a regular http server and proxy its handler 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:
http.createServer(function (req, res){
var proxy = new httpProxy.HttpProxy;
proxy.watch(req, res);
// Put your custom server logic here
proxy.proxyRequest(9000, 'localhost', req, res);
}).listen(8001);
http.createServer(function (req, res){ <pre>
res.writeHead(200, {'Content-Type': 'text/plain'}); httpProxy.createServer(function (req, res, proxyRequest) {
res.write('request successfully proxied: ' + req.url +'\n' + JSON.stringify(req.headers, true, 2)); setTimeout(function () {
res.end(); proxyRequest(port, server);
}).listen(9000); }, latency);
}).listen(8081);
</pre> </pre>
### Why doesn't node-http-proxy have more advanced features like x, y, or z? ### 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 ...@@ -96,7 +87,7 @@ If you have a suggestion for a feature currently not supported, feel free to ope
(The MIT License) (The MIT License)
Copyright (c) 2010 Charlie Robbins & Marak Squires http://github.com/nodejitsu/ Copyright (c) 2010 Mikeal Rogers, Charlie Robbins & Marak Squires
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the a copy of this software and associated documentation files (the
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
var sys = require('sys'), var sys = require('sys'),
colors = require('colors') colors = require('colors')
http = require('http'), http = require('http'),
httpProxy = require('http-proxy'); httpProxy = require('./lib/node-http-proxy');
// ascii art from http://github.com/marak/asciimo // ascii art from http://github.com/marak/asciimo
var welcome = '\ var welcome = '\
...@@ -45,24 +45,13 @@ httpProxy.createServer(9000, 'localhost').listen(8000); ...@@ -45,24 +45,13 @@ httpProxy.createServer(9000, 'localhost').listen(8000);
sys.puts('http proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8000'.yellow); sys.puts('http proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8000'.yellow);
/****** http proxy server with latency******/ /****** http proxy server with latency******/
httpProxy.createServer(function (req, res, proxy){ httpProxy.createServer(function (req, res, proxyRequest){
setTimeout(function(){ setTimeout(function(){
proxy.proxyRequest(9000, 'localhost', req, res); proxyRequest(9000, 'localhost', req, res);
}, 200) }, 2000)
}).listen(8001); }).listen(8001);
sys.puts('http proxy server '.blue + 'started '.green.bold + 'on port '.blue + '8001 '.yellow + 'with latency'.magenta.underline ); sys.puts('http proxy server '.blue + 'started '.green.bold + 'on port '.blue + '8001 '.yellow + 'with latency'.magenta.underline );
/****** http server with proxyRequest handler and latency******/
http.createServer(function (req, res){
var proxy = new httpProxy.HttpProxy;
proxy.watch(req, res);
setTimeout(function(){
proxy.proxyRequest(9000, 'localhost', req, res);
}, 200);
}).listen(8002);
sys.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '8002 '.yellow + 'with proxyRequest handler'.cyan.underline + ' and latency'.magenta);
/****** regular http server ******/ /****** regular http server ******/
http.createServer(function (req, res){ http.createServer(function (req, res){
res.writeHead(200, {'Content-Type': 'text/plain'}); res.writeHead(200, {'Content-Type': 'text/plain'});
......
/* /*
node-http-proxy.js: http proxy for node.js node-http-proxy.js: http proxy for node.js with pooling and event buffering
Copyright (c) 2010 Charlie Robbins & Marak Squires http://github.com/nodejitsu/node-http-proxy Copyright (c) 2010 Mikeal Rogers, Charlie Robbins
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the a copy of this software and associated documentation files (the
...@@ -24,172 +24,110 @@ ...@@ -24,172 +24,110 @@
*/ */
var sys = require('sys'), var sys = require('sys'),
http = require('http'), http = require('http'),
events = require('events'); pool = require('pool'),
url = require('url'),
exports.HttpProxy = function () { events = require('events'),
this.emitter = new(events.EventEmitter); min = 0,
this.events = {}; max = 100;
this.listeners = {};
this.collisions = {}; // Setup the PoolManager
}; var manager = pool.createPoolManager();
manager.setMinClients(min);
manager.setMaxClients(max);
exports.createServer = function () { exports.createServer = function () {
// Initialize the nodeProxy to start proxying requests var args, action, port, host;
var proxy = new (exports.HttpProxy); args = Array.prototype.slice.call(arguments);
return proxy.createServer.apply(proxy, arguments); action = typeof args[args.length - 1] === 'function' && args.pop();
}; if (args[0]) port = args[0];
if (args[1]) host = args[1];
exports.HttpProxy.prototype = {
toArray: function (obj){
var len = obj.length,
arr = new Array(len);
for (var i = 0; i < len; ++i) {
arr[i] = obj[i];
}
return arr;
},
createServer: function () { var proxy = createProxy();
var self = this, proxy.on('route', function (req, res, callback) {
server, var uri = url.parse(req.url);
port, if (action) {
callback; action(req, res, callback);
}
if (typeof(arguments[0]) === "function") {
callback = arguments[0];
}
else { else {
port = arguments[0]; port = port ? port : uri.port ? uri.port : 80;
server = arguments[1]; host = host ? host : uri.hostname;
callback(port, host);
} }
});
var proxyServer = http.createServer(function (req, res){ return proxy;
self.watch(req, res); };
// If we were passed a callback to process the request
// or response in some way, then call it.
if(callback) {
callback(req, res, self);
}
else {
self.proxyRequest(port, server, req, res);
}
});
return proxyServer;
},
watch: function (req, res) {
var self = this;
// Create a unique id for this request so
// we can reference it later.
var id = new Date().getTime().toString();
// If we get a request in the same tick, we need to
// append to the id so it stays unique.
if(typeof this.collisions[id] === 'undefined') {
this.collisions[id] = 0;
}
else {
this.collisions[id]++;
id += this.collisions[id];
}
req.id = id;
this.events[req.id] = [];
this.listeners[req.id] = {
onData: function () {
self.events[req.id].push(['data'].concat(self.toArray(arguments)));
},
onEnd: function () {
self.events[req.id].push(['end'].concat(self.toArray(arguments)));
}
};
req.addListener('data', this.listeners[req.id].onData);
req.addListener('end', this.listeners[req.id].onEnd);
},
unwatch: function (req, res) {
req.removeListener('data', this.listeners[req.id].onData);
req.removeListener('end', this.listeners[req.id].onEnd);
// Rebroadcast any events that have been buffered
while(this.events[req.id].length > 0) {
var args = this.events[req.id].shift();
req.emit.apply(req, args);
}
// Remove the data from the event and listeners hashes
delete this.listeners[req.id];
delete this.events[req.id];
// If this request id is a base time, delete it
if (typeof this.collisions[req.id] !== 'undefined') {
delete this.collisions[req.id];
}
},
proxyRequest: function (port, server, req, res) {
// Remark: nodeProxy.body exists solely for testability
this.body = '';
var self = this;
// Open new HTTP request to internal resource with will act as a reverse proxy pass
var c = http.createClient(port, server);
// Make request to internal server, passing along the method and headers
var reverse_proxy = c.request(req.method, req.url, req.headers);
// Add a listener for the connection timeout event
reverse_proxy.connection.addListener('error', function (err) {
res.writeHead(200, {'Content-Type': 'text/plain'});
if(req.method !== 'HEAD') {
res.write('An error has occurred: ' + sys.puts(JSON.stringify(err)));
}
res.end(); exports.setMin = function (value) {
}); min = value;
manager.setMinClients(min);
};
exports.setMax = function (value) {
max = value;
manager.setMaxClients(max);
}
// Add a listener for the reverse_proxy response event var createProxy = function () {
reverse_proxy.addListener('response', function (response) { var server = http.createServer(function (req, res) {
// Set the response headers of the client response var buffers = [],
res.writeHead(response.statusCode, response.headers); b = function (chunk) { buffers.push(chunk) },
e = function () { e = false };
req.on('data', b);
req.on('end', e);
server.emit('route', req, res, function (port, hostname) {
var p = manager.getPool(port, hostname);
// Add event handler for the proxied response in chunks p.request(req.method, req.url, req.headers, function (reverse_proxy) {
response.addListener('data', function (chunk) { var data = '';
if(req.method !== 'HEAD') { reverse_proxy.on('error', function (err) {
res.write(chunk, 'binary'); res.writeHead(500, {'Content-Type': 'text/plain'});
self.body += chunk;
if(req.method !== 'HEAD') {
res.write('An error has occurred: ' + sys.puts(JSON.stringify(err)));
}
res.end();
});
buffers.forEach(function (c) {
data += c;
reverse_proxy.write(c);
});
buffers = null;
req.removeListener('data', b);
sys.pump(req, reverse_proxy);
if (e) {
req.removeListener('end', e);
req.addListener('end', function () { reverse_proxy.end() });
}
else {
reverse_proxy.end();
} }
});
// Add event listener for end of proxied response // Add a listener for the reverse_proxy response event
response.addListener('end', function () { reverse_proxy.addListener('response', function (response) {
// Remark: Emit the end event for testability // These two listeners are for testability and observation
self.emitter.emit('end', null, self.body); // of what's passed back from the target server
response.addListener('data', function (chunk) {
res.end(); data += chunk;
});
response.addListener('end', function() {
server.emit('proxy', null, data);
});
// Set the response headers of the client response
res.writeHead(response.statusCode, response.headers);
sys.pump(response, res);
});
}); });
}); });
})
// Chunk the client request body as chunks from the proxied request come in return server;
req.addListener('data', function (chunk) { };
reverse_proxy.write(chunk, 'binary'); \ No newline at end of file
})
// At the end of the client request, we are going to stop the proxied request
req.addListener('end', function () {
reverse_proxy.end();
});
this.unwatch(req, res);
}
};
...@@ -29,14 +29,14 @@ var vows = require('vows'), ...@@ -29,14 +29,14 @@ var vows = require('vows'),
assert = require('assert'), assert = require('assert'),
http = require('http'); http = require('http');
var httpProxy = require('http-proxy'); var httpProxy = require('./../lib/node-http-proxy');
var testServers = {}; var testServers = {};
// //
// Creates the reverse proxy server // Creates the reverse proxy server
// //
var startProxyServer = function (port, server, proxy) { var startProxyServer = function (port, server) {
var proxyServer = proxy.createServer(port, server); var proxyServer = httpProxy.createServer(port, server);
proxyServer.listen(8080); proxyServer.listen(8080);
return proxyServer; return proxyServer;
}; };
...@@ -44,11 +44,11 @@ var startProxyServer = function (port, server, proxy) { ...@@ -44,11 +44,11 @@ var startProxyServer = function (port, server, proxy) {
// //
// Creates the reverse proxy server with a specified latency // Creates the reverse proxy server with a specified latency
// //
var startLatentProxyServer = function (port, server, proxy, latency) { var startLatentProxyServer = function (port, server, latency) {
// Initialize the nodeProxy and start proxying the request // Initialize the nodeProxy and start proxying the request
var proxyServer = proxy.createServer(function (req, res, proxy) { var proxyServer = httpProxy.createServer(function (req, res, proxy) {
setTimeout(function () { setTimeout(function () {
proxy.proxyRequest(port, server, req, res); proxy(port, server);
}, latency); }, latency);
}); });
...@@ -73,57 +73,72 @@ var startTargetServer = function (port) { ...@@ -73,57 +73,72 @@ var startTargetServer = function (port) {
// //
// The default test bootstrapper with no latency // The default test bootstrapper with no latency
// //
var startTest = function (proxy, port) { var startTest = function (port) {
var proxyServer = startProxyServer(port, 'localhost'),
targetServer = startTargetServer(port);
testServers.noLatency = []; testServers.noLatency = [];
testServers.noLatency.push(startProxyServer(port, 'localhost', proxy)); testServers.noLatency.push(proxyServer);
testServers.noLatency.push(startTargetServer(port)); testServers.noLatency.push(targetServer);
return proxyServer;
}; };
// //
// The test bootstrapper with some latency // The test bootstrapper with some latency
// //
var startTestWithLatency = function (proxy, port) { var startTestWithLatency = function (port) {
var proxyServer = startLatentProxyServer(port, 'localhost', 2000),
targetServer = startTargetServer(port);
testServers.latency = []; testServers.latency = [];
testServers.latency.push(startLatentProxyServer(port, 'localhost', proxy, 2000)); testServers.latency.push(proxyServer);
testServers.latency.push(startTargetServer(port)); testServers.latency.push(targetServer);
return proxyServer;
}; };
//var proxy = startTest(8082);
//var latent = startTestWithLatency(8083);
vows.describe('node-http-proxy').addBatch({ vows.describe('node-http-proxy').addBatch({
"A node-http-proxy": { "A node-http-proxy": {
"when instantiated directly": { "when instantiated directly": {
"and an incoming request is proxied to the helloNode server" : { "and an incoming request is proxied to the helloNode server" : {
"with no latency" : { "with no latency" : {
topic: function () { topic: function () {
var proxy = new (httpProxy.HttpProxy); var proxyServer = startTest(8082);
startTest(proxy, 8082); proxyServer.on('proxy', this.callback);
proxy.emitter.addListener('end', this.callback);
var client = http.createClient(8080, 'localhost'); var client = http.createClient(8080, 'localhost');
var request = client.request('GET', '/'); var request = client.request('GET', '/');
request.end(); request.end();
},
teardown: function () {
}, },
"it should received 'hello world'": function (err, body) { "it should received 'hello world'": function (err, body) {
assert.equal(body, 'hello world'); assert.equal(body, 'hello world');
testServers.noLatency.forEach(function (server) { testServers.noLatency.forEach(function (server) {
server.close(); server.close();
}) });
} }
}, },
"with latency": { "with latency": {
topic: function () { topic: function () {
var proxy = new (httpProxy.HttpProxy); var proxyServer = startTestWithLatency(8083);
startTestWithLatency(proxy, 8083); proxyServer.on('proxy', this.callback);
proxy.emitter.addListener('end', this.callback);
var client = http.createClient(8081, 'localhost'); var client = http.createClient(8081, 'localhost');
var request = client.request('GET', '/'); var request = client.request('GET', '/');
request.end(); request.end();
},
teardown: function () {
}, },
"it should receive 'hello world'": function (err, body) { "it should receive 'hello world'": function (err, body) {
assert.equal(body, 'hello world'); assert.equal(body, 'hello world');
testServers.latency.forEach(function (server) { testServers.latency.forEach(function (server) {
server.close(); server.close();
}) });
} }
} }
} }
......
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