From 1221939accf00467adb25f8908e991e984043c85 Mon Sep 17 00:00:00 2001 From: indexzero Date: Mon, 6 Sep 2010 23:37:00 -0400 Subject: [PATCH 1/5] [api] Completely refactored node-http-proxy with help from Mikeal --- README.md | 33 ++--- demo.js | 19 +-- lib/node-http-proxy.js | 252 +++++++++++++---------------------- test/node-http-proxy-test.js | 55 +++++--- 4 files changed, 146 insertions(+), 213 deletions(-) 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(); - }) + }); } } } From f291efbaa4360d6e7ff4004cc11f8df0d737c1d0 Mon Sep 17 00:00:00 2001 From: indexzero Date: Mon, 6 Sep 2010 23:43:57 -0400 Subject: [PATCH 2/5] [doc] Updated README.md --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1ee2a3d..6af5441 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# node-http-proxy - v0.1.5 +# node-http-proxy - v0.2.0 @@ -81,8 +81,7 @@ node-http-proxy supports event buffering, that means if an event (like 'data', o 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. -




- +
### License (The MIT License) From 9715ebd40bdbbe883eb383676d5b0df24968dd72 Mon Sep 17 00:00:00 2001 From: indexzero Date: Tue, 7 Sep 2010 12:48:08 -0400 Subject: [PATCH 3/5] [debug] Added some debugging to figure out why AB wont complete a test with v0.2.0 --- lib/node-http-proxy.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/node-http-proxy.js b/lib/node-http-proxy.js index 974b538..49c9b5b 100644 --- a/lib/node-http-proxy.js +++ b/lib/node-http-proxy.js @@ -103,8 +103,12 @@ var createProxy = function () { sys.pump(req, reverse_proxy); if (e) { + sys.puts('end outgoing request'); req.removeListener('end', e); - req.addListener('end', function () { reverse_proxy.end() }); + req.addListener('end', function () { + sys.puts('request ended'); + reverse_proxy.end() + }); } else { reverse_proxy.end(); @@ -121,6 +125,7 @@ var createProxy = function () { response.addListener('end', function() { server.emit('proxy', null, data); }); + sys.puts('response'); // Set the response headers of the client response res.writeHead(response.statusCode, response.headers); From 6d08f24c863e071eb4a0d3ede15656e5e7c27c4b Mon Sep 17 00:00:00 2001 From: indexzero Date: Tue, 7 Sep 2010 15:28:06 -0400 Subject: [PATCH 4/5] [api minor debug] Remove debug code, set Connection header if not set --- lib/node-http-proxy.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/node-http-proxy.js b/lib/node-http-proxy.js index 49c9b5b..c7b0e80 100644 --- a/lib/node-http-proxy.js +++ b/lib/node-http-proxy.js @@ -81,6 +81,7 @@ var createProxy = function () { server.emit('route', req, res, function (port, hostname) { var p = manager.getPool(port, hostname); + req.headers.Connection = req.headers.Connection || 'close'; p.request(req.method, req.url, req.headers, function (reverse_proxy) { var data = ''; reverse_proxy.on('error', function (err) { @@ -103,12 +104,8 @@ var createProxy = function () { sys.pump(req, reverse_proxy); if (e) { - sys.puts('end outgoing request'); req.removeListener('end', e); - req.addListener('end', function () { - sys.puts('request ended'); - reverse_proxy.end() - }); + req.addListener('end', function () { reverse_proxy.end() }); } else { reverse_proxy.end(); @@ -125,10 +122,10 @@ var createProxy = function () { response.addListener('end', function() { server.emit('proxy', null, data); }); - sys.puts('response'); // Set the response headers of the client response res.writeHead(response.statusCode, response.headers); + sys.pump(response, res); }); }); From eb39018fd0b5751dd90fabce905997e52f2ffecd Mon Sep 17 00:00:00 2001 From: indexzero Date: Tue, 7 Sep 2010 16:15:04 -0400 Subject: [PATCH 5/5] [api] Integrated a little more from Mikeal to make our return headers consistent --- lib/node-http-proxy.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/node-http-proxy.js b/lib/node-http-proxy.js index c7b0e80..0505031 100644 --- a/lib/node-http-proxy.js +++ b/lib/node-http-proxy.js @@ -81,7 +81,6 @@ var createProxy = function () { server.emit('route', req, res, function (port, hostname) { var p = manager.getPool(port, hostname); - req.headers.Connection = req.headers.Connection || 'close'; p.request(req.method, req.url, req.headers, function (reverse_proxy) { var data = ''; reverse_proxy.on('error', function (err) { @@ -113,6 +112,11 @@ var createProxy = function () { // Add a listener for the reverse_proxy response event reverse_proxy.addListener('response', function (response) { + if (response.headers.connection) { + if (req.headers.connection) response.headers.connection = req.headers.connection; + else response.headers.connection = 'close'; + } + // These two listeners are for testability and observation // of what's passed back from the target server response.addListener('data', function (chunk) {