From 9f0aeacab1a632136f5905a0d03ad04be9f93f51 Mon Sep 17 00:00:00 2001 From: indexzero Date: Thu, 9 Sep 2010 22:30:00 -0400 Subject: [PATCH] [api] Object creation is cheap for HttpProxy, so lets take advantage --- demo.js | 12 +-- lib/node-http-proxy.js | 202 ++++++++++++++++++++++------------------- 2 files changed, 115 insertions(+), 99 deletions(-) diff --git a/demo.js b/demo.js index ac6630d..fcf16f9 100644 --- a/demo.js +++ b/demo.js @@ -45,15 +45,15 @@ 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, proxy){ setTimeout(function(){ proxy.proxyRequest(9000, 'localhost', req, res); }, 200) }).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){ +/*http.createServer(function (req, res){ var proxy = new httpProxy.HttpProxy; proxy.watch(req, res); @@ -61,12 +61,12 @@ http.createServer(function (req, res){ 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); +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){ +http.createServer(function (req, res){ res.writeHead(200, {'Content-Type': 'text/plain'}); res.write('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2)); res.end(); }).listen(9000); -sys.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9000 '.yellow);*/ +sys.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9000 '.yellow); diff --git a/lib/node-http-proxy.js b/lib/node-http-proxy.js index 4dc0a8d..c7c3f0e 100644 --- a/lib/node-http-proxy.js +++ b/lib/node-http-proxy.js @@ -27,6 +27,7 @@ var sys = require('sys'), http = require('http'), eyes = require('eyes'), + pool = require('pool'), events = require('events'), pool = require('pool'), min = 0, @@ -38,9 +39,26 @@ 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, callback, port, host; + args = Array.prototype.slice.call(arguments); + callback = typeof args[args.length - 1] === 'function' && args.pop(); + if (args[0]) port = args[0]; + if (args[1]) host = args[1]; + + var server = http.createServer(function (req, res){ + var proxy = new HttpProxy(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, proxy); + } + else { + proxy.proxyRequest(port, server); + } + }); + + return server; }; exports.setMin = function (value) { @@ -53,14 +71,15 @@ exports.setMax = function (value) { manager.setMaxClients(max); }; -exports.HttpProxy = function () { +var HttpProxy = function (req, res) { this.emitter = new(events.EventEmitter); this.events = {}; - this.listeners = {}; - this.collisions = {}; + this.req = req; + this.res = res; + this.watch(req); }; -exports.HttpProxy.prototype = { +HttpProxy.prototype = { toArray: function (obj){ var len = obj.length, arr = new Array(len); @@ -70,102 +89,42 @@ exports.HttpProxy.prototype = { return arr; }, - createServer: function () { - var self = this, - server, - port, - callback; - - if (typeof(arguments[0]) === "function") { - callback = arguments[0]; - } - else { - port = arguments[0]; - server = arguments[1]; - } - - var proxyServer = http.createServer(function (req, res){ - 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) { + watch: function (req) { + this.events = []; 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))); - } + + this.onData = function () { + self.events.push(['data'].concat(self.toArray(arguments))); + }; + this.onEnd = function () { + self.events.push(['end'].concat(self.toArray(arguments))); }; - req.addListener('data', this.listeners[req.id].onData); - req.addListener('end', this.listeners[req.id].onEnd); - + req.addListener('data', this.onData); + req.addListener('end', this.onEnd); }, - unwatch: function (req, res) { - req.removeListener('data', this.listeners[req.id].onData); - req.removeListener('end', this.listeners[req.id].onEnd); + unwatch: function (req) { + req.removeListener('data', this.onData); + req.removeListener('end', this.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]; + for (var i = 0, len = this.events.length; i < len; ++i) { + req.emit.apply(req, this.events[i]); } }, - proxyRequest: function (port, server, req, res) { + proxyRequest: function (port, server) { // Remark: nodeProxy.body exists solely for testability - this.body = ''; - var self = this; + var self = this, req = this.req, res = this.res; + self.body = ''; // Open new HTTP request to internal resource with will act as a reverse proxy pass - var p = manager.getPool(port, server); - eyes.inspect(req.headers); - // Make request to internal server, passing along the method and headers + var p = manager.getPool(port, server); + p.request(req.method, req.url, req.headers, function (reverse_proxy) { - // Add a listener for the connection timeout event - reverse_proxy.connection.addListener('error', function (err) { + // Create an error handler so we can use it temporarily + var error = function (err) { res.writeHead(200, {'Content-Type': 'text/plain'}); if(req.method !== 'HEAD') { @@ -173,8 +132,9 @@ exports.HttpProxy.prototype = { } res.end(); - }); - + }; + // Add a listener for the connection timeout event + reverse_proxy.connection.addListener('error', error); // Add a listener for the reverse_proxy response event reverse_proxy.addListener('response', function (response) { @@ -182,7 +142,7 @@ exports.HttpProxy.prototype = { if (req.headers.connection) response.headers.connection = req.headers.connection; else response.headers.connection = 'close'; } - + // Set the response headers of the client response res.writeHead(response.statusCode, response.headers); @@ -211,9 +171,65 @@ exports.HttpProxy.prototype = { // At the end of the client request, we are going to stop the proxied request req.addListener('end', function () { reverse_proxy.end(); + reverse_proxy.connection.removeListener('error', error); }); - self.unwatch(req, res); + self.unwatch(req); }); } }; + +exports.HttpProxy = HttpProxy; + +/*// Create an error handler so we can use it temporarily +var 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 connection timeout event +reverse_proxy.connection.addListener('error', error); + +// 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'; + } + + // 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); + + res.end(); + }); +}); + +// 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(); + //reverse_proxy.connection.removeListener('error', error); +}); + +self.unwatch(req);*/