diff --git a/README.md b/README.md index 4b9d4d5..1ee2a3d 100644 --- a/README.md +++ b/README.md @@ -52,9 +52,9 @@ see the [demo](http://github.com/nodejitsu/node-http-proxy/blob/master/demo.js) httpProxy = require('http-proxy'); // 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 - proxy.proxyRequest(9000, 'localhost', req, res); + proxyRequest(9000, 'localhost'); }).listen(8000); http.createServer(function (req, res){ @@ -65,25 +65,16 @@ see the [demo](http://github.com/nodejitsu/node-http-proxy/blob/master/demo.js) -### How to proxy requests with a regular http server +### How to proxy requests with latent operations (IO, etc.) + +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: +
- var http = require('http'),
- httpProxy = require('http-proxy');
-
- // create a regular http server and proxy its handler
- 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){
- res.writeHead(200, {'Content-Type': 'text/plain'});
- res.write('request successfully proxied: ' + req.url +'\n' + JSON.stringify(req.headers, true, 2));
- res.end();
- }).listen(9000);
-
+ httpProxy.createServer(function (req, res, proxyRequest) {
+ setTimeout(function () {
+ proxyRequest(port, server);
+ }, latency);
+ }).listen(8081);
### 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
(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
a copy of this software and associated documentation files (the
diff --git a/demo.js b/demo.js
index ddfa7ed..77119a2 100644
--- a/demo.js
+++ b/demo.js
@@ -27,7 +27,7 @@
var sys = require('sys'),
colors = require('colors')
http = require('http'),
- httpProxy = require('http-proxy');
+ httpProxy = require('./lib/node-http-proxy');
// ascii art from http://github.com/marak/asciimo
var welcome = '\
@@ -45,24 +45,13 @@ httpProxy.createServer(9000, 'localhost').listen(8000);
sys.puts('http proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8000'.yellow);
/****** http proxy server with latency******/
-httpProxy.createServer(function (req, res, proxy){
+httpProxy.createServer(function (req, res, proxyRequest){
setTimeout(function(){
- proxy.proxyRequest(9000, 'localhost', req, res);
- }, 200)
+ proxyRequest(9000, 'localhost', req, res);
+ }, 2000)
}).listen(8001);
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 ******/
http.createServer(function (req, res){
res.writeHead(200, {'Content-Type': 'text/plain'});
diff --git a/lib/node-http-proxy.js b/lib/node-http-proxy.js
index e9a383e..974b538 100644
--- a/lib/node-http-proxy.js
+++ b/lib/node-http-proxy.js
@@ -1,7 +1,7 @@
/*
- 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
a copy of this software and associated documentation files (the
@@ -24,172 +24,110 @@
*/
-var sys = require('sys'),
- http = require('http'),
- events = require('events');
+var sys = require('sys'),
+ http = require('http'),
+ pool = require('pool'),
+ url = require('url'),
+ events = require('events'),
+ min = 0,
+ max = 100;
-exports.HttpProxy = function () {
- this.emitter = new(events.EventEmitter);
- this.events = {};
- this.listeners = {};
- this.collisions = {};
-};
+// Setup the PoolManager
+var manager = pool.createPoolManager();
+manager.setMinClients(min);
+manager.setMaxClients(max);
exports.createServer = function () {
- // Initialize the nodeProxy to start proxying requests
- var proxy = new (exports.HttpProxy);
- return proxy.createServer.apply(proxy, arguments);
+ var args, action, port, host;
+ args = Array.prototype.slice.call(arguments);
+ action = typeof args[args.length - 1] === 'function' && args.pop();
+ if (args[0]) port = args[0];
+ if (args[1]) host = args[1];
+
+ var proxy = createProxy();
+ proxy.on('route', function (req, res, callback) {
+ var uri = url.parse(req.url);
+ if (action) {
+ action(req, res, callback);
+ }
+ else {
+ port = port ? port : uri.port ? uri.port : 80;
+ host = host ? host : uri.hostname;
+ callback(port, host);
+ }
+ });
+ return proxy;
};
-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 self = this,
- server,
- port,
- callback;
+exports.setMin = function (value) {
+ min = value;
+ manager.setMinClients(min);
+};
+
+exports.setMax = function (value) {
+ max = value;
+ manager.setMaxClients(max);
+}
+
+var createProxy = function () {
+ var server = http.createServer(function (req, res) {
+ var buffers = [],
+ b = function (chunk) { buffers.push(chunk) },
+ e = function () { e = false };
- if (typeof(arguments[0]) === "function") {
- callback = arguments[0];
- }
- else {
- port = arguments[0];
- server = arguments[1];
- }
+ req.on('data', b);
+ req.on('end', e);
- var proxyServer = http.createServer(function (req, res){
- self.watch(req, res);
+ server.emit('route', req, res, function (port, hostname) {
+ var p = manager.getPool(port, hostname);
- // 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)));
- }
- };
+ p.request(req.method, req.url, req.headers, function (reverse_proxy) {
+ var data = '';
+ reverse_proxy.on('error', function (err) {
+ res.writeHead(500, {'Content-Type': 'text/plain'});
- 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];
- }
- },
+ if(req.method !== 'HEAD') {
+ res.write('An error has occurred: ' + sys.puts(JSON.stringify(err)));
+ }
- 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);
+ res.end();
+ });
- // 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();
- });
-
-
- // Add a listener for the reverse_proxy response event
- reverse_proxy.addListener('response', function (response) {
- // Set the response headers of the client response
- res.writeHead(response.statusCode, response.headers);
-
- // Add event handler for the proxied response in chunks
- response.addListener('data', function (chunk) {
- if(req.method !== 'HEAD') {
- res.write(chunk, 'binary');
- self.body += chunk;
- }
- });
-
- // Add event listener for end of proxied response
- response.addListener('end', function () {
- // Remark: Emit the end event for testability
- self.emitter.emit('end', null, self.body);
+ buffers.forEach(function (c) {
+ data += c;
+ reverse_proxy.write(c);
+ });
- res.end();
+ 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 a listener for the reverse_proxy response event
+ reverse_proxy.addListener('response', function (response) {
+ // These two listeners are for testability and observation
+ // of what's passed back from the target server
+ response.addListener('data', function (chunk) {
+ 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
- req.addListener('data', function (chunk) {
- reverse_proxy.write(chunk, 'binary');
- })
-
- // 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);
- }
-};
+ })
+ return server;
+};
\ No newline at end of file
diff --git a/test/node-http-proxy-test.js b/test/node-http-proxy-test.js
index 4880bdf..7bb6d58 100644
--- a/test/node-http-proxy-test.js
+++ b/test/node-http-proxy-test.js
@@ -29,14 +29,14 @@ var vows = require('vows'),
assert = require('assert'),
http = require('http');
-var httpProxy = require('http-proxy');
+var httpProxy = require('./../lib/node-http-proxy');
var testServers = {};
//
// Creates the reverse proxy server
//
-var startProxyServer = function (port, server, proxy) {
- var proxyServer = proxy.createServer(port, server);
+var startProxyServer = function (port, server) {
+ var proxyServer = httpProxy.createServer(port, server);
proxyServer.listen(8080);
return proxyServer;
};
@@ -44,11 +44,11 @@ var startProxyServer = function (port, server, proxy) {
//
// 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
- var proxyServer = proxy.createServer(function (req, res, proxy) {
+ var proxyServer = httpProxy.createServer(function (req, res, proxy) {
setTimeout(function () {
- proxy.proxyRequest(port, server, req, res);
+ proxy(port, server);
}, latency);
});
@@ -73,57 +73,72 @@ var startTargetServer = function (port) {
//
// 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.push(startProxyServer(port, 'localhost', proxy));
- testServers.noLatency.push(startTargetServer(port));
+ testServers.noLatency.push(proxyServer);
+ testServers.noLatency.push(targetServer);
+ return proxyServer;
};
//
// 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.push(startLatentProxyServer(port, 'localhost', proxy, 2000));
- testServers.latency.push(startTargetServer(port));
+ testServers.latency.push(proxyServer);
+ testServers.latency.push(targetServer);
+ return proxyServer;
};
+//var proxy = startTest(8082);
+//var latent = startTestWithLatency(8083);
+
vows.describe('node-http-proxy').addBatch({
"A node-http-proxy": {
"when instantiated directly": {
"and an incoming request is proxied to the helloNode server" : {
"with no latency" : {
topic: function () {
- var proxy = new (httpProxy.HttpProxy);
- startTest(proxy, 8082);
- proxy.emitter.addListener('end', this.callback);
+ var proxyServer = startTest(8082);
+ proxyServer.on('proxy', this.callback);
var client = http.createClient(8080, 'localhost');
var request = client.request('GET', '/');
request.end();
+ },
+ teardown: function () {
+
},
"it should received 'hello world'": function (err, body) {
assert.equal(body, 'hello world');
testServers.noLatency.forEach(function (server) {
server.close();
- })
+ });
}
},
"with latency": {
topic: function () {
- var proxy = new (httpProxy.HttpProxy);
- startTestWithLatency(proxy, 8083);
- proxy.emitter.addListener('end', this.callback);
+ var proxyServer = startTestWithLatency(8083);
+ proxyServer.on('proxy', this.callback);
var client = http.createClient(8081, 'localhost');
var request = client.request('GET', '/');
request.end();
+ },
+ teardown: function () {
+
},
"it should receive 'hello world'": function (err, body) {
assert.equal(body, 'hello world');
testServers.latency.forEach(function (server) {
server.close();
- })
+ });
}
}
}