diff --git a/demo.js b/demo.js index 7380a71..4e5fbb2 100644 --- a/demo.js +++ b/demo.js @@ -39,7 +39,6 @@ var welcome = '\ # # # # # # # # #### # # # \n'; util.puts(welcome.rainbow.bold); - // // Basic Http Proxy Server // diff --git a/lib/node-http-proxy.js b/lib/node-http-proxy.js index f4146b6..5362ab5 100644 --- a/lib/node-http-proxy.js +++ b/lib/node-http-proxy.js @@ -32,7 +32,13 @@ var util = require('util'), ProxyTable = require('./proxy-table').ProxyTable, maxSockets = 100; - +// +// ### function _getAgent (host, port) +// #### @host {string} Host of the agent to get +// #### @port {number} Port of the agent to get +// Retreives an agent from the `http` module +// and sets the `maxSockets` property appropriately. +// function _getAgent (host, port) { // // TODO (indexzero): Make this configurable for http / https @@ -42,17 +48,33 @@ function _getAgent (host, port) { return agent; } +// +// ### function getMaxSockets () +// Returns the maximum number of sockets +// allowed on __every__ outgoing request +// made by __all__ instances of `HttpProxy` +// exports.getMaxSockets = function () { return maxSockets; }; +// +// ### function setMaxSockets () +// Sets the maximum number of sockets +// allowed on __every__ outgoing request +// made by __all__ instances of `HttpProxy` +// exports.setMaxSockets = function (value) { maxSockets = value; }; +// +// ### function createServer ([port, host, options], handler) +// +// exports.createServer = function () { var args, callback, port, host, forward, - silent, proxyTable, options = {}; + silent, options, proxy, server; args = Array.prototype.slice.call(arguments); callback = typeof args[args.length - 1] === 'function' && args.pop(); @@ -69,9 +91,8 @@ exports.createServer = function () { } } - var proxy = new HttpProxy(options); - - var server = http.createServer(function (req, res) { + proxy = new HttpProxy(options); + server = http.createServer(function (req, res) { winston.verbose('Incoming HTTP request to: ' + req.headers.host + req.url); // If we were passed a callback to process the request @@ -98,13 +119,14 @@ exports.createServer = function () { proxy.on('routes', function (routes) { server.emit('routes', routes); - }) + }); if (!callback) { // WebSocket support: if callback is empty tunnel // websocket request automatically server.on('upgrade', function(req, socket, head) { // Tunnel websocket requests too + proxy.proxyWebSocketRequest(port, host); }); } @@ -112,6 +134,25 @@ exports.createServer = function () { return server; }; +// +// ### function HttpProxy (options) +// #### @options {Object} Options for this instance. +// Constructor function for new instances of HttpProxy responsible +// for managing the life-cycle of streaming reverse proxyied HTTP requests. +// +// Example options: +// +// { +// router: { +// 'foo.com': 'localhost:8080', +// 'bar.com': 'localhost:8081' +// }, +// forward: { +// host: 'localhost', +// port: 9001 +// } +// } +// var HttpProxy = exports.HttpProxy = function (options) { events.EventEmitter.call(this); @@ -127,38 +168,36 @@ var HttpProxy = exports.HttpProxy = function (options) { } }; +// Inherit from events.EventEmitter util.inherits(HttpProxy, events.EventEmitter); -HttpProxy.prototype.close = function () { - if (this.proxyTable) this.proxyTable.close(); -}; - -/** - * Pause `data` and `end` events on the given `obj`. - * Middleware performing async tasks _should_ utilize - * this utility (or similar), to re-emit data once - * the async operation has completed, otherwise these - * events may be lost. - * - * var pause = utils.pause(req); - * fs.readFile(path, function(){ - * next(); - * pause.resume(); - * }); - * - * @param {Object} obj - * @return {Object} - * @api public - */ +// +// ### function pause (obj) +// #### @obj {Object} Object to pause events from +// Pause `data` and `end` events on the given `obj`. +// Consumers of HttpProxy performing async tasks +// __must__ utilize this utility, to re-emit data once +// the async operation has completed, otherwise these +// __events will be lost.__ +// +// var pause = httpProxy.pause(req); +// fs.readFile(path, function(){ +// httpProxy.proxyRequest(req, res, host, port, paused); +// }); +// +// __Attribution:__ This approach is based heavily on +// [Connect](https://github.com/senchalabs/connect/blob/master/lib/utils.js#L157). +// However, this is not a big leap from the implementation in node-http-proxy < 0.4.0. +// This simply chooses to manage the scope of the events on a new Object literal as opposed to +// [on the HttpProxy instance](https://github.com/nodejitsu/node-http-proxy/blob/v0.3.1/lib/node-http-proxy.js#L154). +// HttpProxy.prototype.pause = function (obj) { var onData, onEnd, events = []; - // buffer data obj.on('data', onData = function (data, encoding) { events.push(['data', data, encoding]); }); - // buffer end obj.on('end', onEnd = function (data, encoding) { events.push(['end', data, encoding]); }); @@ -177,8 +216,25 @@ HttpProxy.prototype.pause = function (obj) { }; }; +// +// ### function close () +// Frees the resources associated with this instance, +// if they exist. +// +HttpProxy.prototype.close = function () { + if (this.proxyTable) this.proxyTable.close(); +}; + +// +// ### function proxyRequest (req, res, [port, host, paused]) +// #### @req {ServerRequest} Incoming HTTP Request to proxy. +// #### @res {ServerResponse} Outgoing HTTP Request to write proxied data to. +// #### @port {number} **Optional** Port to use on the proxy target host. +// #### @host {string} **Optional** Host of the proxy target. +// #### @paused {Object} **Optional** Result from `httpProxy.pause(req)` +// HttpProxy.prototype.proxyRequest = function (req, res, port, host, paused) { - var self = this, reverseProxy, location; + var self = this, reverseProxy, location, errState = false; // // Check the proxy table for this instance to see if we need @@ -189,6 +245,10 @@ HttpProxy.prototype.proxyRequest = function (req, res, port, host, paused) { if (this.proxyTable && !host) { location = this.proxyTable.getProxyLocation(req); + // + // If no location is returned from the ProxyTable instance + // then respond with `404` since we do not have a valid proxy target. + // if (!location) { res.writeHead(404); return res.end(); @@ -198,36 +258,34 @@ HttpProxy.prototype.proxyRequest = function (req, res, port, host, paused) { // When using the ProxyTable in conjunction with an HttpProxy instance // only the following arguments are valid: // - // * proxy.proxyRequest(req, res, port, host, paused): This will be skipped - // * proxy.proxyRequest(req, res, paused): Paused will get updated appropriately - // * proxy.proxyRequest(req, res): No effect `undefined = undefined` + // * `proxy.proxyRequest(req, res, port, host, paused)`: This will be skipped + // * `proxy.proxyRequest(req, res, paused)`: Paused will get updated appropriately + // * `proxy.proxyRequest(req, res)`: No effect `undefined = undefined` // paused = port; port = location.port; host = location.host; } + // + // If forwarding is enabled for this instance, foward proxy the + // specified request to the address provided in `this.options.forward` + // if (this.options.forward) { winston.verbose('Forwarding HTTP request to: ' + this.options.forward.host + ':' + this.options.forward.port); this._forwardRequest(req); } - // Create an error handler so we can use it temporarily - function error (obj) { - var fn = function (err) { - res.writeHead(500, {'Content-Type': 'text/plain'}); + function proxyError(err) { + errState = true; + res.writeHead(500, { 'Content-Type': 'text/plain' }); - if (req.method !== 'HEAD') { - res.write('An error has occurred: ' + JSON.stringify(err)); - } - - // Response end may never come so removeListener here - obj.removeListener('error', fn); - res.end(); - }; + if (req.method !== 'HEAD') { + res.write('An error has occurred: ' + JSON.stringify(err)); + } - return fn; - }; + res.end(); + } // Open new HTTP request to internal resource with will act as a reverse proxy pass reverseProxy = http.request({ @@ -263,26 +321,31 @@ HttpProxy.prototype.proxyRequest = function (req, res, port, host, paused) { // Add event listener for end of proxied response response.on('end', function () { - reverseProxy.removeListener('error', reverseProxyError); - res.end(); + if (!errState) { + reverseProxy.removeListener('error', proxyError); + res.end(); + } }); }); // Add a listener for the connection timeout event - var reverseProxyError = error(reverseProxy); - reverseProxy.on('error', reverseProxyError); + reverseProxy.once('error', proxyError); // Chunk the client request body as chunks from the proxied request come in req.on('data', function (chunk) { - reverseProxy.write(chunk); + if (!errState) { + reverseProxy.write(chunk); + } }); // At the end of the client request, we are going to stop the proxied request req.on('end', function () { - reverseProxy.end(); + if (!errState) { + reverseProxy.end(); + } }); - if (paused) { + if (paused && !errState) { paused.resume(); } }; @@ -308,11 +371,12 @@ HttpProxy.prototype._forwardRequest = function (req) { // }); - // Add a listener for the connection timeout event - forwardProxy.on('error', function (err) { - // Remark: Ignoring this error in the event - // forward target doesn't exist. - }); + // Add a listener for the connection timeout event. + // + // Remark: Ignoring this error in the event + // forward target doesn't exist. + // + forwardProxy.on('error', function (err) { }); // Chunk the client request body as chunks from the proxied request come in req.on('data', function (chunk) { diff --git a/lib/proxy-table.js b/lib/proxy-table.js index ce585d2..012320f 100644 --- a/lib/proxy-table.js +++ b/lib/proxy-table.js @@ -26,7 +26,8 @@ var util = require('util'), events = require('events'), - fs = require('fs'); + fs = require('fs'), + winston = require('winston'); // // ### function ProxyTable (router, silent) @@ -109,9 +110,7 @@ ProxyTable.prototype.getProxyLocation = function (req) { host = location[0], port = location.length === 1 ? 80 : location[1]; - if (!this.silent) { - util.log('Proxy Table proxying request to: ' + host + ':' + port); - } + winston.verbose('Proxy Table proxying request to: ' + host + ':' + port); return { port: port,