From 1221939accf00467adb25f8908e991e984043c85 Mon Sep 17 00:00:00 2001 From: indexzero Date: Mon, 6 Sep 2010 23:37:00 -0400 Subject: [PATCH] [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(); - }) + }); } } }