From db10c4af918c3e4bc448163f4b9e9b9267145d47 Mon Sep 17 00:00:00 2001 From: indexzero Date: Sun, 28 Aug 2011 01:46:08 -0400 Subject: [PATCH 01/29] [test] Updates for readability --- test/helpers.js | 42 +++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/test/helpers.js b/test/helpers.js index 23a29e6..b326e94 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -59,7 +59,10 @@ TestRunner.prototype.assertProxied = function (host, proxyPort, port, createProx var test = { topic: function () { - var that = this, options = { + var that = this, + options; + + options = { method: 'GET', uri: self.protocol + '://localhost:' + proxyPort, headers: { @@ -172,9 +175,9 @@ TestRunner.prototype.webSocketTestWithTable = function (options) { options.onListen(socket); } - self.startProxyServerWithTable( - options.ports.proxy, - {router: options.router}, + self.startProxyServerWithTable( + options.ports.proxy, + { router: options.router }, function (err, proxy) { if (options.onServer) { options.onServer(proxy) } @@ -213,7 +216,10 @@ TestRunner.prototype.startProxyServer = function (port, targetPort, host, callba // TestRunner.prototype.startLatentProxyServer = function (port, targetPort, host, latency, callback) { // Initialize the nodeProxy and start proxying the request - var that = this, proxyServer = httpProxy.createServer(function (req, res, proxy) { + var that = this, + proxyServer; + + proxyServer = httpProxy.createServer(function (req, res, proxy) { var buffer = proxy.buffer(req); setTimeout(function () { @@ -235,7 +241,9 @@ TestRunner.prototype.startLatentProxyServer = function (port, targetPort, host, // Creates the reverse proxy server with a ProxyTable // TestRunner.prototype.startProxyServerWithTable = function (port, options, callback) { - var that = this, proxyServer = httpProxy.createServer(merge({}, options, this.options)); + var that = this, + proxyServer = httpProxy.createServer(merge({}, options, this.options)); + proxyServer.listen(port, function () { that.testServers.push(proxyServer); callback(); @@ -248,10 +256,12 @@ TestRunner.prototype.startProxyServerWithTable = function (port, options, callba // Creates a latent reverse proxy server using a ProxyTable // TestRunner.prototype.startProxyServerWithTableAndLatency = function (port, latency, options, callback) { + // // Initialize the nodeProxy and start proxying the request - var proxyServer, - that = this, - proxy = new httpProxy.HttpProxy(merge({}, options, that.options)); + // + var that = this, + proxy = new httpProxy.HttpProxy(merge({}, options, that.options)), + proxyServer; var handler = function (req, res) { var buffer = proxy.buffer(req); @@ -263,8 +273,8 @@ TestRunner.prototype.startProxyServerWithTableAndLatency = function (port, laten }; proxyServer = that.options.https - ? https.createServer(that.options.https, handler, that.options) - : http.createServer(handler, that.options); + ? https.createServer(that.options.https, handler, that.options) + : http.createServer(handler, that.options); proxyServer.listen(port, function () { that.testServers.push(proxyServer); @@ -278,7 +288,9 @@ TestRunner.prototype.startProxyServerWithTableAndLatency = function (port, laten // Creates proxy server forwarding to the specified options // TestRunner.prototype.startProxyServerWithForwarding = function (port, targetPort, host, options, callback) { - var that = this, proxyServer = httpProxy.createServer(targetPort, host, merge({}, options, this.options)); + var that = this, + proxyServer = httpProxy.createServer(targetPort, host, merge({}, options, this.options)); + proxyServer.listen(port, function () { that.testServers.push(proxyServer); callback(null, proxyServer); @@ -289,7 +301,11 @@ TestRunner.prototype.startProxyServerWithForwarding = function (port, targetPort // Creates the 'hellonode' server // TestRunner.prototype.startTargetServer = function (port, output, callback) { - var that = this, targetServer, handler = function (req, res) { + var that = this, + targetServer, + handler; + + handler = function (req, res) { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.write(output); res.end(); From d2b0e4399e8026d3e2ece78ac8fdb1def6649950 Mon Sep 17 00:00:00 2001 From: indexzero Date: Sun, 28 Aug 2011 03:38:39 -0400 Subject: [PATCH 02/29] [api breaking] Begin refactor to optimize node-http-proxy by managing one instance of HttpProxy per `host:port` location --- lib/node-http-proxy.js | 792 +++-------------------- lib/node-http-proxy/http-proxy.js | 624 ++++++++++++++++++ lib/{ => node-http-proxy}/proxy-table.js | 4 +- lib/node-http-proxy/routing-proxy.js | 4 + 4 files changed, 726 insertions(+), 698 deletions(-) create mode 100644 lib/node-http-proxy/http-proxy.js rename lib/{ => node-http-proxy}/proxy-table.js (98%) create mode 100644 lib/node-http-proxy/routing-proxy.js diff --git a/lib/node-http-proxy.js b/lib/node-http-proxy.js index e0a27e0..825d053 100644 --- a/lib/node-http-proxy.js +++ b/lib/node-http-proxy.js @@ -28,7 +28,6 @@ var util = require('util'), http = require('http'), https = require('https'), events = require('events'), - ProxyTable = require('./proxy-table').ProxyTable, maxSockets = 100; // @@ -37,110 +36,11 @@ var util = require('util'), require('pkginfo')(module, 'version'); // -// Track our own list of agents internal to `node-http-proxy` +// ### Export the relevant objects exposed by `node-http-proxy` // -var _agents = {}; - -// -// ### function _getAgent (host, port, secure) -// #### @host {string} Host of the agent to get -// #### @port {number} Port of the agent to get -// #### @secure {boolean} Value indicating whether or not to use HTTPS -// Retreives an agent from the `http` or `https` module -// and sets the `maxSockets` property appropriately. -// -function _getAgent (host, port, secure) { - var Agent, id = [host, port].join(':'); - - if (!port) { - port = secure ? 443 : 80; - } - - if (!_agents[id]) { - Agent = secure ? https.Agent : http.Agent; - - _agents[id] = new Agent({ - host: host, - port: port - }); - - _agents[id].maxSockets = maxSockets; - } - - return _agents[id]; -} - -// -// ### function _getProtocol (secure, outgoing) -// #### @secure {Object|boolean} Settings for `https` -// #### @outgoing {Object} Outgoing request options -// Returns the appropriate protocol based on the settings in -// `secure`. If the protocol is `https` this function will update -// the options in `outgoing` as appropriate by adding `ca`, `key`, -// and `cert` if they exist in `secure`. -// -function _getProtocol (secure, outgoing) { - var protocol = secure ? https : http; - - if (typeof secure === 'object') { - outgoing = outgoing || {}; - ['ca', 'cert', 'key'].forEach(function (prop) { - if (secure[prop]) { - outgoing[prop] = secure[prop]; - } - }) - } - - return protocol; -} - -// -// ### 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 stack (middlewares, proxy) -// adapted from https://github.com/creationix/stack -// -exports.stack = function stack (middlewares, proxy) { - var handle; - middlewares.reverse().forEach(function (layer) { - var child = handle; - handle = function (req, res) { - var next = function (err) { - if (err) { - throw err; - // - // TODO: figure out where to send errors. - // return error(req, res, err); - // - } - child(req, res); - } - - next.__proto__ = proxy; - layer(req, res, next); - }; - }); - - return handle; -} +var HttpProxy = exports.HttpProxy = require('./node-http-proxy/http-proxy').HttpProxy, + ProxyTable = exports.ProxyTable = require('./node-http-proxy/proxy-table').ProxyTable, + RoutingProxy = exports.RoutingProxy = require('./node-http-proxy/routing-proxy').RoutingProxy; // // ### function createServer ([port, host, options, handler]) @@ -255,59 +155,6 @@ 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); - - var self = this; - options = options || {}; - - // - // Setup basic proxying options - // - this.https = options.https; - this.forward = options.forward; - this.target = options.target || {}; - - // - // Setup additional options for WebSocket proxying. When forcing - // the WebSocket handshake to change the `sec-websocket-location` - // and `sec-websocket-origin` headers `options.source` **MUST** - // be provided or the operation will fail with an `origin mismatch` - // by definition. - // - this.source = options.source || { host: 'localhost', port: 8000 }; - this.changeOrigin = options.changeOrigin || false; - - if (options.router) { - this.proxyTable = new ProxyTable(options.router, options.silent, options.hostnameOnly); - this.proxyTable.on('routes', function (routes) { - self.emit('routes', routes); - }); - } -}; - -// Inherit from events.EventEmitter -util.inherits(HttpProxy, events.EventEmitter); - // // ### function buffer (obj) // #### @obj {Object} Object to pause events from @@ -328,8 +175,10 @@ util.inherits(HttpProxy, events.EventEmitter); // 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.buffer = function (obj) { - var onData, onEnd, events = []; +exports.buffer = function (obj) { + var events = [], + onData, + onEnd; obj.on('data', onData = function (data, encoding) { events.push(['data', data, encoding]); @@ -354,567 +203,116 @@ HttpProxy.prototype.buffer = function (obj) { }; // -// ### function close () -// Frees the resources associated with this instance, -// if they exist. +// ### function getMaxSockets () +// Returns the maximum number of sockets +// allowed on __every__ outgoing request +// made by __all__ instances of `HttpProxy` // -HttpProxy.prototype.close = function () { - if (this.proxyTable) { - this.proxyTable.close(); - } +exports.getMaxSockets = function () { + return maxSockets; }; // -// ### function proxyRequest (req, res, [port, host, paused]) -// #### @req {ServerRequest} Incoming HTTP Request to proxy. -// #### @res {ServerResponse} Outgoing HTTP Request to write proxied data to. -// #### @options {Object} Options for the outgoing proxy request. +// ### function setMaxSockets () +// Sets the maximum number of sockets +// allowed on __every__ outgoing request +// made by __all__ instances of `HttpProxy` // -// options.port {number} Port to use on the proxy target host. -// options.host {string} Host of the proxy target. -// options.buffer {Object} Result from `httpProxy.buffer(req)` -// options.https {Object|boolean} Settings for https. -// options.enableXForwarded {boolean} Don't clobber x-forwarded headers to allow layered proxies. -// -HttpProxy.prototype.proxyRequest = function (req, res, options) { - var self = this, errState = false, location, outgoing, protocol, reverseProxy; - - // - // Create an empty options hash if none is passed. - // If default options have been passed to the constructor - // of this instance, use them by default. - // - options = options || {}; - options.host = options.host || this.target.host; - options.port = options.port || this.target.port; - options.enableXForwarded = - (undefined === options.enableXForwarded ? true : options.enableXForwarded); - - // - // Check the proxy table for this instance to see if we need - // to get the proxy location for the request supplied. We will - // always ignore the proxyTable if an explicit `port` and `host` - // arguments are supplied to `proxyRequest`. - // - if (this.proxyTable && !options.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(); - } - - // - // When using the ProxyTable in conjunction with an HttpProxy instance - // only the following arguments are valid: - // - // * `proxy.proxyRequest(req, res, { host: 'localhost' })`: This will be skipped - // * `proxy.proxyRequest(req, res, { buffer: buffer })`: Buffer will get updated appropriately - // * `proxy.proxyRequest(req, res)`: Options will be assigned appropriately. - // - options.port = location.port; - options.host = location.host; - } - - // - // Add common proxy headers to the request so that they can - // be availible to the proxy target server: - // - // * `x-forwarded-for`: IP Address of the original request - // * `x-forwarded-proto`: Protocol of the original request - // * `x-forwarded-port`: Port of the original request. - // - if (options.enableXForwarded === true) { - req.headers['x-forwarded-for'] = req.connection.remoteAddress || req.connection.socket.remoteAddress; - req.headers['x-forwarded-port'] = req.connection.remotePort || req.connection.socket.remotePort; - req.headers['x-forwarded-proto'] = res.connection.pair ? 'https' : 'http'; - } - - // - // Emit the `start` event indicating that we have begun the proxy operation. - // - this.emit('start', req, res, options); - - // - // If forwarding is enabled for this instance, foward proxy the - // specified request to the address provided in `this.forward` - // - if (this.forward) { - this.emit('forward', req, res, this.forward); - this._forwardRequest(req); - } - - // - // #### function proxyError (err) - // #### @err {Error} Error contacting the proxy target - // Short-circuits `res` in the event of any error when - // contacting the proxy target at `host` / `port`. - // - function proxyError(err) { - errState = true; - - // - // Emit an `error` event, allowing the application to use custom - // error handling. The error handler should end the response. - // - if (self.emit('proxyError', err, req, res)) { - return; - } - - res.writeHead(500, { 'Content-Type': 'text/plain' }); - - if (req.method !== 'HEAD') { - // - // This NODE_ENV=production behavior is mimics Express and - // Connect. - // - if (process.env.NODE_ENV === 'production') { - res.write('Internal Server Error'); - } - else { - res.write('An error has occurred: ' + JSON.stringify(err)); - } - } - - res.end(); - } - - outgoing = { - host: options.host, - port: options.port, - agent: _getAgent(options.host, options.port, options.https || this.target.https), - method: req.method, - path: req.url, - headers: req.headers - }; - - protocol = _getProtocol(options.https || this.target.https, outgoing); - - // Open new HTTP request to internal resource with will act as a reverse proxy pass - reverseProxy = protocol.request(outgoing, function (response) { - - // Process the `reverseProxy` `response` when it's received. - if (response.headers.connection) { - if (req.headers.connection) response.headers.connection = req.headers.connection; - else response.headers.connection = 'close'; - } - - // Set the headers of the client response - res.writeHead(response.statusCode, response.headers); - - // `response.statusCode === 304`: No 'data' event and no 'end' - if (response.statusCode === 304) { - return res.end(); - } - - // For each data `chunk` received from the `reverseProxy` - // `response` write it to the outgoing `res`. - // If the res socket has been killed already, then write() - // will throw. Nevertheless, try our best to end it nicely. - response.on('data', function (chunk) { - if (req.method !== 'HEAD' && res.writable) { - try { - res.write(chunk); - } catch (er) { - try { - res.end(); - } catch (er) {} - } - } - }); - - // When the `reverseProxy` `response` ends, end the - // corresponding outgoing `res` unless we have entered - // an error state. In which case, assume `res.end()` has - // already been called and the 'error' event listener - // removed. - response.on('end', function () { - if (!errState) { - reverseProxy.removeListener('error', proxyError); - res.end(); - - // Emit the `end` event now that we have completed proxying - self.emit('end', req, res); - } - }); - }); - - // Handle 'error' events from the `reverseProxy`. - reverseProxy.once('error', proxyError); - - // For each data `chunk` received from the incoming - // `req` write it to the `reverseProxy` request. - req.on('data', function (chunk) { - if (!errState) { - reverseProxy.write(chunk); - } - }); - - // - // When the incoming `req` ends, end the corresponding `reverseProxy` - // request unless we have entered an error state. - // - req.on('end', function () { - if (!errState) { - reverseProxy.end(); - } - }); - - // If we have been passed buffered data, resume it. - if (options.buffer && !errState) { - options.buffer.resume(); - } +exports.setMaxSockets = function (value) { + maxSockets = value; }; // -// ### @private function _forwardRequest (req) -// #### @req {ServerRequest} Incoming HTTP Request to proxy. -// Forwards the specified `req` to the location specified -// by `this.forward` ignoring errors and the subsequent response. +// ### function stack (middlewares, proxy) +// adapted from https://github.com/creationix/stack // -HttpProxy.prototype._forwardRequest = function (req) { - var self = this, port, host, outgoing, protocol, forwardProxy; +exports.stack = function stack (middlewares, proxy) { + var handle; + middlewares.reverse().forEach(function (layer) { + var child = handle; + handle = function (req, res) { + var next = function (err) { + if (err) { + throw err; + // + // TODO: figure out where to send errors. + // return error(req, res, err); + // + } + child(req, res); + } - port = this.forward.port; - host = this.forward.host; - - outgoing = { - host: host, - port: port, - agent: _getAgent(host, port, this.forward.https), - method: req.method, - path: req.url, - headers: req.headers - }; - - // Force the `connection` header to be 'close' until - // node.js core re-implements 'keep-alive'. - outgoing.headers['connection'] = 'close'; - - protocol = _getProtocol(this.forward.https, outgoing); - - // Open new HTTP request to internal resource with will act as a reverse proxy pass - forwardProxy = protocol.request(outgoing, function (response) { - // - // Ignore the response from the forward proxy since this is a 'fire-and-forget' proxy. - // Remark (indexzero): We will eventually emit a 'forward' event here for performance tuning. - // + next.__proto__ = proxy; + layer(req, res, next); + }; }); - // Add a listener for the connection timeout event. - // - // Remark: Ignoring this error in the event - // forward target doesn't exist. - // - forwardProxy.once('error', function (err) { }); - - // Chunk the client request body as chunks from the proxied request come in - req.on('data', function (chunk) { - forwardProxy.write(chunk); - }) - - // At the end of the client request, we are going to stop the proxied request - req.on('end', function () { - forwardProxy.end(); - }); + return handle; }; // -// ### function proxyWebSocketRequest (req, socket, head, options) -// #### @req {ServerRequest} Websocket request to proxy. -// #### @socket {net.Socket} Socket for the underlying HTTP request -// #### @head {string} Headers for the Websocket request. -// #### @options {Object} Options to use when proxying this request. +// ### function _getAgent (host, port, secure) +// #### @options {Object} Options to use when creating the agent. // -// options.port {number} Port to use on the proxy target host. -// options.host {string} Host of the proxy target. -// options.buffer {Object} Result from `httpProxy.buffer(req)` -// options.https {Object|boolean} Settings for https. +// { +// host: 'localhost', +// port: 9000, +// https: true, +// maxSockets: 100 +// } // -HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, options) { - var self = this, - listeners = {}, - errState = false, - CRLF = '\r\n', - outgoing; - - options = options || {}; - options.host = options.host || this.target.host; - options.port = options.port || this.target.port; - - if (this.proxyTable && !options.host) { - location = this.proxyTable.getProxyLocation(req); - - if (!location) { - return socket.destroy(); - } - - options.port = location.port; - options.host = location.host; +// Createsan agent from the `http` or `https` module +// and sets the `maxSockets` property appropriately. +// +exports._getAgent = function _getAgent (options) { + if (!options || !options.host) { + throw new Error('`options.host` is required to create an Agent.'); } - - // - // WebSocket requests must have the `GET` method and - // the `upgrade:websocket` header - // - if (req.method !== 'GET' || req.headers.upgrade.toLowerCase() !== 'websocket') { - // - // This request is not WebSocket request - // - return; - } - - // - // Helper function for setting appropriate socket values: - // 1. Turn of all bufferings - // 2. For server set KeepAlive - // 3. For client set encoding - // - function _socket(socket, keepAlive) { - socket.setTimeout(0); - socket.setNoDelay(true); - if (keepAlive) { - if (socket.setKeepAlive) { - socket.setKeepAlive(true, 0); - } - else if (socket.pair.cleartext.socket.setKeepAlive) { - socket.pair.cleartext.socket.setKeepAlive(true, 0); - } - } - else { - socket.setEncoding('utf8'); - } - } - - // - // On `upgrade` from the Agent socket, listen to - // the appropriate events. - // - function onUpgrade (reverseProxy, proxySocket) { - if (!reverseProxy) { - proxySocket.end(); - socket.end(); - return; - } - - // - // Any incoming data on this WebSocket to the proxy target - // will be written to the `reverseProxy` socket. - // - proxySocket.on('data', listeners.onIncoming = function (data) { - if (reverseProxy.incoming.socket.writable) { - try { - self.emit('websocket:outgoing', req, socket, head, data); - reverseProxy.incoming.socket.write(data); - } - catch (e) { - reverseProxy.incoming.socket.end(); - proxySocket.end(); - } - } - }); - - // - // Any outgoing data on this Websocket from the proxy target - // will be written to the `proxySocket` socket. - // - reverseProxy.incoming.socket.on('data', listeners.onOutgoing = function(data) { - try { - self.emit('websocket:incoming', reverseProxy, reverseProxy.incoming, head, data); - proxySocket.write(data); - } - catch (e) { - proxySocket.end(); - socket.end(); - } - }); - - // - // Helper function to detach all event listeners - // from `reverseProxy` and `proxySocket`. - // - function detach() { - proxySocket.removeListener('end', listeners.onIncomingClose); - proxySocket.removeListener('data', listeners.onIncoming); - reverseProxy.incoming.socket.removeListener('end', listeners.onOutgoingClose); - reverseProxy.incoming.socket.removeListener('data', listeners.onOutgoing); - } - - // - // If the incoming `proxySocket` socket closes, then - // detach all event listeners. - // - proxySocket.on('end', listeners.onIncomingClose = function() { - reverseProxy.incoming.socket.end(); - detach(); - - // Emit the `end` event now that we have completed proxying - self.emit('websocket:end', req, socket, head); - }); - - // - // If the `reverseProxy` socket closes, then detach all - // event listeners. - // - reverseProxy.incoming.socket.on('end', listeners.onOutgoingClose = function() { - proxySocket.end(); - detach(); - }); - }; - - // Setup the incoming client socket. - _socket(socket); - - function getPort (port) { - port = port || 80; - return port - 80 === 0 ? '' : ':' + port - } - - // - // Get the protocol, and host for this request and create an instance - // of `http.Agent` or `https.Agent` from the pool managed by `node-http-proxy`. - // - var protocolName = options.https || this.target.https ? 'https' : 'http', - portUri = getPort(this.source.port), - remoteHost = options.host + portUri, - agent = _getAgent(options.host, options.port, options.https || this.target.https); - - // Change headers (if requested). - if (this.changeOrigin) { - req.headers.host = remoteHost; - req.headers.origin = protocolName + '://' + remoteHost; - } - - // - // Make the outgoing WebSocket request - // - outgoing = { - host: options.host, - port: options.port, - method: 'GET', - path: req.url, - headers: req.headers, - }; - var reverseProxy = agent.appendMessage(outgoing); - - // - // On any errors from the `reverseProxy` emit the - // `webSocketProxyError` and close the appropriate - // connections. - // - function proxyError (err) { - reverseProxy.end(); - if (self.emit('webSocketProxyError', req, socket, head)) { - return; - } - - socket.end(); + if (!options.port) { + options.port = options.https ? 443 : 80; } - // - // Here we set the incoming `req`, `socket` and `head` data to the outgoing - // request so that we can reuse this data later on in the closure scope - // available to the `upgrade` event. This bookkeeping is not tracked anywhere - // in nodejs core and is **very** specific to proxying WebSockets. - // - reverseProxy.agent = agent; - reverseProxy.incoming = { - request: req, - socket: socket, - head: head - }; + var Agent = options.https ? https.Agent : http.Agent, + agent; - // - // If the agent for this particular `host` and `port` combination - // is not already listening for the `upgrade` event, then do so once. - // This will force us not to disconnect. - // - // In addition, it's important to note the closure scope here. Since - // there is no mapping of the - // - if (!agent._events || agent._events['upgrade'].length === 0) { - agent.on('upgrade', function (_, remoteSocket, head) { - // - // Prepare the socket for the reverseProxy request and begin to - // stream data between the two sockets. Here it is important to - // note that `remoteSocket._httpMessage === reverseProxy`. - // - _socket(remoteSocket, true); - onUpgrade(remoteSocket._httpMessage, remoteSocket); - }); - } + agent = new Agent({ + host: options.host, + port: options.port + }); - // - // If the reverseProxy connection has an underlying socket, - // then execute the WebSocket handshake. - // - if (typeof reverseProxy.socket !== 'undefined') { - reverseProxy.socket.on('data', function handshake (data) { - // - // Ok, kind of harmfull part of code. Socket.IO sends a hash - // at the end of handshake if protocol === 76, but we need - // to replace 'host' and 'origin' in response so we split - // data to printable data and to non-printable. (Non-printable - // will come after double-CRLF). - // - var sdata = data.toString(); + agent.maxSockets = options.maxSockets || maxSockets; - // Get the Printable data - sdata = sdata.substr(0, sdata.search(CRLF + CRLF)); + return agent; +} - // Get the Non-Printable data - data = data.slice(Buffer.byteLength(sdata), data.length); - - if (self.https && !self.target.https) { - // - // If the proxy server is running HTTPS but the client is running - // HTTP then replace `ws` with `wss` in the data sent back to the client. - // - sdata = sdata.replace('ws:', 'wss:'); - } - - try { - // - // Write the printable and non-printable data to the socket - // from the original incoming request. - // - self.emit('websocket:handshake', req, socket, head, sdata, data); - socket.write(sdata); - socket.write(data); - } - catch (ex) { - proxyError(ex) - } - - // Catch socket errors - socket.on('error', proxyError); - - // Remove data listener now that the 'handshake' is complete - reverseProxy.socket.removeListener('data', handshake); - }); - } - - reverseProxy.on('error', proxyError); - - try { - // - // Attempt to write the upgrade-head to the reverseProxy request. - // - reverseProxy.write(head); - } - catch (ex) { - proxyError(ex); - } - - // - // If we have been passed buffered data, resume it. - // - if (options.buffer && !errState) { - options.buffer.resume(); - } +// +// ### function _getProtocol (options) +// #### @options {Object} Options for the proxy target. +// Returns the appropriate node.js core protocol module (i.e. `http` or `https`) +// based on the `options` supplied. +// +exports._getProtocol = function _getProtocol (options) { + return options.https ? https : http; }; + +// +// ### function _getBase (options) +// #### @options {Object} Options for the proxy target. +// Returns the relevate base object to create on outgoing proxy request. +// If `options.https` are supplied, this function respond with an object +// containing the relevant `ca`, `key`, and `cert` properties. +// +exports._getBase = function _getBase (options) { + var result = {}; + + if (typeof options.https === 'object') { + ['ca', 'cert', 'key'].forEach(function (key) { + if (options.https[key]) { + result[key] = options.https[key]; + } + }); + } + + return result; +}; \ No newline at end of file diff --git a/lib/node-http-proxy/http-proxy.js b/lib/node-http-proxy/http-proxy.js new file mode 100644 index 0000000..9461f2a --- /dev/null +++ b/lib/node-http-proxy/http-proxy.js @@ -0,0 +1,624 @@ +/* + node-http-proxy.js: http proxy for node.js + + Copyright (c) 2010 Charlie Robbins, Mikeal Rogers, Marak Squires, Fedor Indutny + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +var events = require('events'), + util = require('util'), + httpProxy = require('../node-http-proxy'); + +// +// ### 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: +// +// { +// target: { +// host: 'localhost', +// port: 9000 +// }, +// forward: { +// host: 'localhost', +// port: 9001 +// } +// } +// +var HttpProxy = exports.HttpProxy = function (options) { + if (!options || !options.target) { + throw new Error('Both `options` and `options.target` are required.'); + } + + events.EventEmitter.call(this); + + var self = this; + + // + // Setup basic proxying options: + // + // * forward {Object} Options for a forward-proxy (if-any) + // * target {Object} Options for the **sole** proxy target of this instance + // + this.forward = options.forward; + this.target = options.target; + + // + // Setup the necessary instances instance variables for + // the `target` and `forward` `host:port` combinations + // used by this instance. + // + // * agent {http[s].Agent} Agent to be used by this instance. + // * protocol {http|https} Core node.js module to make requests with. + // * base {Object} Base object to create when proxying containing any https settings. + // + function setupProxy (key) { + self[key].agent = httpProxy._getAgent(self[key]); + self[key].protocol = httpProxy._getProtocol(self[key]); + self[key].base = httpProxy._getBase(self[key]); + } + + setupProxy('target') + if (this.forward) { setupProxy('forward') } + + // + // Setup opt-in features + // + this.enable = options.enable || {}; + this.enable.xforward = typeof this.enable.xforward === 'boolean' + ? this.enable.xforward + : true; + + // + // Setup additional options for WebSocket proxying. When forcing + // the WebSocket handshake to change the `sec-websocket-location` + // and `sec-websocket-origin` headers `options.source` **MUST** + // be provided or the operation will fail with an `origin mismatch` + // by definition. + // + this.source = options.source || { host: 'localhost', port: 8000 }; + this.changeOrigin = options.changeOrigin || false; +}; + +// Inherit from events.EventEmitter +util.inherits(HttpProxy, events.EventEmitter); + +// +// ### function proxyRequest (req, res, [port, host, paused]) +// #### @req {ServerRequest} Incoming HTTP Request to proxy. +// #### @res {ServerResponse} Outgoing HTTP Request to write proxied data to. +// #### @buffer {Object} Result from `httpProxy.buffer(req)` +// +HttpProxy.prototype.proxyRequest = function (req, res, buffer) { + var self = this, + errState = false, + outgoing = new Object(this.target.base), + reverseProxy; + + // + // Add common proxy headers to the request so that they can + // be availible to the proxy target server: + // + // * `x-forwarded-for`: IP Address of the original request + // * `x-forwarded-proto`: Protocol of the original request + // * `x-forwarded-port`: Port of the original request. + // + if (this.enable.xforward) { + req.headers['x-forwarded-for'] = req.connection.remoteAddress || req.connection.socket.remoteAddress; + req.headers['x-forwarded-port'] = req.connection.remotePort || req.connection.socket.remotePort; + req.headers['x-forwarded-proto'] = res.connection.pair ? 'https' : 'http'; + } + + // + // Emit the `start` event indicating that we have begun the proxy operation. + // + this.emit('start', req, res, this.target); + + // + // If forwarding is enabled for this instance, foward proxy the + // specified request to the address provided in `this.forward` + // + if (this.forward) { + this.emit('forward', req, res, this.forward); + this._forwardRequest(req); + } + + // + // #### function proxyError (err) + // #### @err {Error} Error contacting the proxy target + // Short-circuits `res` in the event of any error when + // contacting the proxy target at `host` / `port`. + // + function proxyError(err) { + errState = true; + + // + // Emit an `error` event, allowing the application to use custom + // error handling. The error handler should end the response. + // + if (self.emit('proxyError', err, req, res)) { + return; + } + + res.writeHead(500, { 'Content-Type': 'text/plain' }); + + if (req.method !== 'HEAD') { + // + // This NODE_ENV=production behavior is mimics Express and + // Connect. + // + if (process.env.NODE_ENV === 'production') { + res.write('Internal Server Error'); + } + else { + res.write('An error has occurred: ' + JSON.stringify(err)); + } + } + + res.end(); + } + + // + // Setup outgoing proxy with relevant properties. + // + outgoing.host = this.target.host; + outgoing.port = this.target.port; + outgoing.agent = this.target.agent; + outgoing.method = req.method; + outgoing.path = req.url; + outgoing.headers = req.headers; + + // + // Open new HTTP request to internal resource with will act + // as a reverse proxy pass + // + reverseProxy = this.target.protocol.request(outgoing, function (response) { + // + // Process the `reverseProxy` `response` when it's received. + // + if (response.headers.connection) { + if (req.headers.connection) { response.headers.connection = req.headers.connection } + else { response.headers.connection = 'close' } + } + + // Set the headers of the client response + res.writeHead(response.statusCode, response.headers); + + // If `response.statusCode === 304`: No 'data' event and no 'end' + if (response.statusCode === 304) { + return res.end(); + } + + // + // For each data `chunk` received from the `reverseProxy` + // `response` write it to the outgoing `res`. + // If the res socket has been killed already, then write() + // will throw. Nevertheless, try our best to end it nicely. + // + response.on('data', function (chunk) { + if (req.method !== 'HEAD' && res.writable) { + try { + res.write(chunk); + } + catch (er) { + try { res.end() } + catch (er) {} + } + } + }); + + // + // When the `reverseProxy` `response` ends, end the + // corresponding outgoing `res` unless we have entered + // an error state. In which case, assume `res.end()` has + // already been called and the 'error' event listener + // removed. + // + response.on('end', function () { + if (!errState) { + reverseProxy.removeListener('error', proxyError); + res.end(); + + // Emit the `end` event now that we have completed proxying + self.emit('end', req, res); + } + }); + }); + + // + // Handle 'error' events from the `reverseProxy`. + // + reverseProxy.once('error', proxyError); + + // + // For each data `chunk` received from the incoming + // `req` write it to the `reverseProxy` request. + // + req.on('data', function (chunk) { + if (!errState) { + reverseProxy.write(chunk); + } + }); + + // + // When the incoming `req` ends, end the corresponding `reverseProxy` + // request unless we have entered an error state. + // + req.on('end', function () { + if (!errState) { + reverseProxy.end(); + } + }); + + // If we have been passed buffered data, resume it. + if (buffer && !errState) { + buffer.resume(); + } +}; + +// +// ### @private function _forwardRequest (req) +// #### @req {ServerRequest} Incoming HTTP Request to proxy. +// Forwards the specified `req` to the location specified +// by `this.forward` ignoring errors and the subsequent response. +// +HttpProxy.prototype._forwardRequest = function (req) { + var self = this, + outgoing = new Object(this.forward.base), + forwardProxy; + + // + // Setup outgoing proxy with relevant properties. + // + outgoing.host = this.forward.host; + outgoing.port = this.forward.port, + outgoing.agent = this.forward.agent; + outgoing.method = req.method; + outgoing.path = req.url; + outgoing.headers = req.headers; + + // + // Open new HTTP request to internal resource with will + // act as a reverse proxy pass. + // + forwardProxy = this.forward.protocol.request(outgoing, function (response) { + // + // Ignore the response from the forward proxy since this is a 'fire-and-forget' proxy. + // Remark (indexzero): We will eventually emit a 'forward' event here for performance tuning. + // + }); + + // + // Add a listener for the connection timeout event. + // + // Remark: Ignoring this error in the event + // forward target doesn't exist. + // + forwardProxy.once('error', function (err) { }); + + // + // Chunk the client request body as chunks from + // the proxied request come in + // + req.on('data', function (chunk) { + forwardProxy.write(chunk); + }) + + // + // At the end of the client request, we are going to + // stop the proxied request + // + req.on('end', function () { + forwardProxy.end(); + }); +}; + +// +// ### function proxyWebSocketRequest (req, socket, head, options) +// #### @req {ServerRequest} Websocket request to proxy. +// #### @socket {net.Socket} Socket for the underlying HTTP request +// #### @head {string} Headers for the Websocket request. +// #### @buffer {Object} Result from `httpProxy.buffer(req)` +// Performs a WebSocket proxy operation to the location specified by +// `this.target`. +// +HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, buffer) { + var self = this, + outgoing = new Object(this.target.base); + listeners = {}, + errState = false, + CRLF = '\r\n'; + + // + // WebSocket requests must have the `GET` method and + // the `upgrade:websocket` header + // + if (req.method !== 'GET' || req.headers.upgrade.toLowerCase() !== 'websocket') { + // + // This request is not WebSocket request + // + return socket.destroy(); + } + + // + // Helper function for setting appropriate socket values: + // 1. Turn of all bufferings + // 2. For server set KeepAlive + // 3. For client set encoding + // + function _socket(socket, keepAlive) { + socket.setTimeout(0); + socket.setNoDelay(true); + + if (keepAlive) { + if (socket.setKeepAlive) { + socket.setKeepAlive(true, 0); + } + else if (socket.pair.cleartext.socket.setKeepAlive) { + socket.pair.cleartext.socket.setKeepAlive(true, 0); + } + } + else { + socket.setEncoding('utf8'); + } + } + + // + // Setup the incoming client socket. + // + _socket(socket); + + // + // On `upgrade` from the Agent socket, listen to + // the appropriate events. + // + function onUpgrade (reverseProxy, proxySocket) { + if (!reverseProxy) { + proxySocket.end(); + socket.end(); + return; + } + + // + // Any incoming data on this WebSocket to the proxy target + // will be written to the `reverseProxy` socket. + // + proxySocket.on('data', listeners.onIncoming = function (data) { + if (reverseProxy.incoming.socket.writable) { + try { + self.emit('websocket:outgoing', req, socket, head, data); + reverseProxy.incoming.socket.write(data); + } + catch (ex) { + reverseProxy.incoming.socket.end(); + proxySocket.end(); + } + } + }); + + // + // Any outgoing data on this Websocket from the proxy target + // will be written to the `proxySocket` socket. + // + reverseProxy.incoming.socket.on('data', listeners.onOutgoing = function(data) { + try { + self.emit('websocket:incoming', reverseProxy, reverseProxy.incoming, head, data); + proxySocket.write(data); + } + catch (ex) { + proxySocket.end(); + socket.end(); + } + }); + + // + // Helper function to detach all event listeners + // from `reverseProxy` and `proxySocket`. + // + function detach() { + proxySocket.removeListener('end', listeners.onIncomingClose); + proxySocket.removeListener('data', listeners.onIncoming); + reverseProxy.incoming.socket.removeListener('end', listeners.onOutgoingClose); + reverseProxy.incoming.socket.removeListener('data', listeners.onOutgoing); + } + + // + // If the incoming `proxySocket` socket closes, then + // detach all event listeners. + // + proxySocket.on('end', listeners.onIncomingClose = function() { + reverseProxy.incoming.socket.end(); + detach(); + + // Emit the `end` event now that we have completed proxying + self.emit('websocket:end', req, socket, head); + }); + + // + // If the `reverseProxy` socket closes, then detach all + // event listeners. + // + reverseProxy.incoming.socket.on('end', listeners.onOutgoingClose = function() { + proxySocket.end(); + detach(); + }); + }; + + function getPort (port) { + port = port || 80; + return port - 80 === 0 ? '' : ':' + port + } + + // + // Get the protocol, and host for this request and create an instance + // of `http.Agent` or `https.Agent` from the pool managed by `node-http-proxy`. + // + var agent = this.target.agent, + protocolName = this.target.https ? 'https' : 'http', + portUri = getPort(this.source.port), + remoteHost = options.host + portUri; + + // + // Change headers (if requested). + // + if (this.changeOrigin) { + req.headers.host = remoteHost; + req.headers.origin = protocolName + '://' + remoteHost; + } + + // + // Make the outgoing WebSocket request + // + outgoing.host = this.target.host; + outgoing.port = this.target.port; + outgoing.method = 'GET'; + outgoing.path = req.url; + outgoing.headers = req.headers; + + var reverseProxy = agent.appendMessage(outgoing); + + // + // On any errors from the `reverseProxy` emit the + // `webSocketProxyError` and close the appropriate + // connections. + // + function proxyError (err) { + reverseProxy.end(); + if (self.emit('webSocketProxyError', req, socket, head)) { + return; + } + + socket.end(); + } + + // + // Here we set the incoming `req`, `socket` and `head` data to the outgoing + // request so that we can reuse this data later on in the closure scope + // available to the `upgrade` event. This bookkeeping is not tracked anywhere + // in nodejs core and is **very** specific to proxying WebSockets. + // + reverseProxy.agent = agent; + reverseProxy.incoming = { + request: req, + socket: socket, + head: head + }; + + // + // If the agent for this particular `host` and `port` combination + // is not already listening for the `upgrade` event, then do so once. + // This will force us not to disconnect. + // + // In addition, it's important to note the closure scope here. Since + // there is no mapping of the + // + if (!agent._events || agent._events['upgrade'].length === 0) { + agent.on('upgrade', function (_, remoteSocket, head) { + // + // Prepare the socket for the reverseProxy request and begin to + // stream data between the two sockets. Here it is important to + // note that `remoteSocket._httpMessage === reverseProxy`. + // + _socket(remoteSocket, true); + onUpgrade(remoteSocket._httpMessage, remoteSocket); + }); + } + + // + // If the reverseProxy connection has an underlying socket, + // then execute the WebSocket handshake. + // + if (typeof reverseProxy.socket !== 'undefined') { + reverseProxy.socket.on('data', function handshake (data) { + // + // Ok, kind of harmfull part of code. Socket.IO sends a hash + // at the end of handshake if protocol === 76, but we need + // to replace 'host' and 'origin' in response so we split + // data to printable data and to non-printable. (Non-printable + // will come after double-CRLF). + // + var sdata = data.toString(); + + // Get the Printable data + sdata = sdata.substr(0, sdata.search(CRLF + CRLF)); + + // Get the Non-Printable data + data = data.slice(Buffer.byteLength(sdata), data.length); + + if (self.https && !self.target.https) { + // + // If the proxy server is running HTTPS but the client is running + // HTTP then replace `ws` with `wss` in the data sent back to the client. + // + sdata = sdata.replace('ws:', 'wss:'); + } + + try { + // + // Write the printable and non-printable data to the socket + // from the original incoming request. + // + self.emit('websocket:handshake', req, socket, head, sdata, data); + socket.write(sdata); + socket.write(data); + } + catch (ex) { + // + // Remove data listener on socket error because the + // 'handshake' has failed. + // + reverseProxy.socket.removeListener('data', handshake); + return proxyError(ex); + } + + // Catch socket errors + socket.on('error', proxyError); + + // + // Remove data listener now that the 'handshake' is complete + // + reverseProxy.socket.removeListener('data', handshake); + }); + } + + reverseProxy.on('error', proxyError); + + try { + // + // Attempt to write the upgrade-head to the reverseProxy request. + // + reverseProxy.write(head); + } + catch (ex) { + return proxyError(ex); + } + + // + // If we have been passed buffered data, resume it. + // + if (buffer && !errState) { + buffer.resume(); + } +}; diff --git a/lib/proxy-table.js b/lib/node-http-proxy/proxy-table.js similarity index 98% rename from lib/proxy-table.js rename to lib/node-http-proxy/proxy-table.js index 90a129b..233e552 100644 --- a/lib/proxy-table.js +++ b/lib/node-http-proxy/proxy-table.js @@ -81,7 +81,9 @@ util.inherits(ProxyTable, events.EventEmitter); // Sets the host-based routes to be used by this instance. // ProxyTable.prototype.setRoutes = function (router) { - if (!router) throw new Error('Cannot update ProxyTable routes without router.'); + if (!router) { + throw new Error('Cannot update ProxyTable routes without router.'); + } this.router = router; diff --git a/lib/node-http-proxy/routing-proxy.js b/lib/node-http-proxy/routing-proxy.js new file mode 100644 index 0000000..26887bd --- /dev/null +++ b/lib/node-http-proxy/routing-proxy.js @@ -0,0 +1,4 @@ + +var RoutingProxy = exports.RoutingProxy = function () { + +}; \ No newline at end of file From be4562da9fafef8b26856f7f73f6c5a2c4e389b0 Mon Sep 17 00:00:00 2001 From: indexzero Date: Sun, 28 Aug 2011 04:40:51 -0400 Subject: [PATCH 03/29] [api test] Updated httpProxy.createServer() for new API exposed by simplified HttpProxy object. [breaking] Temporarily removed pending refactor: middleware & ProxyTable support --- lib/node-http-proxy.js | 117 +++++++++++++++++------------------------ test/helpers.js | 105 +++++++++++++++++++++--------------- 2 files changed, 112 insertions(+), 110 deletions(-) diff --git a/lib/node-http-proxy.js b/lib/node-http-proxy.js index 825d053..af0dd29 100644 --- a/lib/node-http-proxy.js +++ b/lib/node-http-proxy.js @@ -56,94 +56,75 @@ var HttpProxy = exports.HttpProxy = require('./node-http-proxy/http-proxy' // exports.createServer = function () { var args = Array.prototype.slice.call(arguments), - callback, forward, - port, host, - proxy, server, options = {}, - middleware = [], + host, port, + server, proxy, + callback, handler, silent; + // + // Liberally parse arguments of the form: + // + // httpProxy.createServer('localhost', 9000, callback); + // httpProxy.createServer({ host: 'localhost', port: 9000 }, callback); + // **NEED MORE HERE!!!** + // args.forEach(function (arg) { switch (typeof arg) { - case 'string': host = arg; break; - case 'number': port = arg; break; - case 'function': middleware.push(handler = callback = arg); break; - case 'object': options = arg; break; + case 'string': host = arg; break; + case 'number': port = arg; break; + case 'object': options = arg || {}; break; + case 'function': callback = arg; break; }; }); - proxy = new HttpProxy(options); - - if (port && host) { + if (!host && !port && !options) { // - // If we have a target host and port for the request - // then proxy to the specified location. - // - handler = function (req, res) { - proxy.proxyRequest(req, res, { - port: port, - host: host - }); - } - - if (middleware.length) { - middleware.push(handler); - } - } - else if (proxy.proxyTable) { - // - // If the proxy is configured with a ProxyTable - // instance then use that before failing. - // - handler = function (req, res) { - proxy.proxyRequest(req, res); - } - - if (middleware.length) { - middleware.push(handler); - } - } - - if (middleware.length > 1) { - handler = callback = exports.stack(middleware, proxy); - } - else if (middleware.length) { - // - // Do not use middleware code if it's not needed. - // - var h = middleware[0]; - handler = callback = function (req,res) { h(req,res,proxy) }; - } - - if (!handler) { - // - // Otherwise this server is improperly configured. + // If `host`, `port` and `options` are all not passed, then + // this server is improperly configured. // throw new Error('Cannot proxy without port, host, or router.') } + + // + // Hoist up any explicit `host` or `port` arguments + // that have been passed in to the options we will + // pass to the `httpProxy.HttpProxy` constructor. + // + options.target = options.target || {}; + options.target.port = options.target.port || port; + options.target.host = options.target.host || host; + + // + // Create the `http[s].Server` instance which will use + // an instance of `httpProxy.HttpProxy`. + // + proxy = new HttpProxy(options); + handler = callback + ? function (req, res) { callback(req, res, proxy) } + : proxy.proxyRequest; + + server = options.https + ? https.createServer(options.https, handler.bind(proxy)) + : http.createServer(handler.bind(proxy)); - server = options.https - ? https.createServer(options.https, handler) - : http.createServer(handler); - - server.on('close', function () { - proxy.close(); - }); + //server.on('close', function () { + // proxy.close(); + //}); proxy.on('routes', function (routes) { server.emit('routes', routes); }); if (!callback) { - // WebSocket support: if callback is empty tunnel - // websocket request automatically + // + // If an explicit callback has not been supplied then + // automagically proxy the request using the `HttpProxy` + // instance we have created. + // server.on('upgrade', function (req, socket, head) { - // Tunnel websocket requests too - proxy.proxyWebSocketRequest(req, socket, head, { - port: port, - host: host - }); + proxy.proxyWebSocketRequest(req, socket, head); }); } @@ -306,7 +287,7 @@ exports._getProtocol = function _getProtocol (options) { exports._getBase = function _getBase (options) { var result = {}; - if (typeof options.https === 'object') { + if (options.https && typeof options.https === 'object') { ['ca', 'cert', 'key'].forEach(function (key) { if (options.https[key]) { result[key] = options.https[key]; diff --git a/test/helpers.js b/test/helpers.js index b326e94..9c91abf 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -15,18 +15,6 @@ var fs = require('fs'), websocket = require('./../vendor/websocket'), httpProxy = require('./../lib/node-http-proxy'); -function merge (target) { - var objs = Array.prototype.slice.call(arguments, 1); - objs.forEach(function(o) { - Object.keys(o).forEach(function (attr) { - if (! o.__lookupGetter__(attr)) { - target[attr] = o[attr]; - } - }); - }); - return target; -} - var loadHttps = exports.loadHttps = function () { return { key: fs.readFileSync(path.join(__dirname, 'fixtures', 'agent2-key.pem'), 'utf8'), @@ -34,21 +22,17 @@ var loadHttps = exports.loadHttps = function () { }; }; -var TestRunner = exports.TestRunner = function (protocol, target) { - this.options = {}; - this.options.target = {}; - this.protocol = protocol; - this.target = target; - this.testServers = []; +var TestRunner = exports.TestRunner = function (source, target) { + this.source = { protocol: source }, + this.target = { protocol: target }; + this.testServers = []; - if (protocol === 'https') { - this.options.https = loadHttps(); + if (source === 'https') { + this.source.https = loadHttps(); } if (target === 'https') { - this.options.target = { - https: loadHttps() - }; + this.target.https = loadHttps(); } }; @@ -64,11 +48,12 @@ TestRunner.prototype.assertProxied = function (host, proxyPort, port, createProx options = { method: 'GET', - uri: self.protocol + '://localhost:' + proxyPort, + uri: self.source.protocol + '://localhost:' + proxyPort, headers: { host: host } }; + function startTest () { if (port) { @@ -94,7 +79,7 @@ TestRunner.prototype.assertProxied = function (host, proxyPort, port, createProx TestRunner.prototype.assertResponseCode = function (proxyPort, statusCode, createProxy) { var assertion = "should receive " + statusCode + " responseCode", - protocol = this.protocol; + protocol = this.source.protocol; var test = { topic: function () { @@ -202,8 +187,7 @@ TestRunner.prototype.webSocketTestWithTable = function (options) { // TestRunner.prototype.startProxyServer = function (port, targetPort, host, callback) { var that = this, - options = that.options, - proxyServer = httpProxy.createServer(targetPort, host, options); + proxyServer = httpProxy.createServer(host, targetPort, this.getOptions()); proxyServer.listen(port, function () { that.testServers.push(proxyServer); @@ -215,21 +199,19 @@ TestRunner.prototype.startProxyServer = function (port, targetPort, host, callba // Creates the reverse proxy server with a specified latency // TestRunner.prototype.startLatentProxyServer = function (port, targetPort, host, latency, callback) { + // // Initialize the nodeProxy and start proxying the request + // var that = this, proxyServer; - - proxyServer = httpProxy.createServer(function (req, res, proxy) { - var buffer = proxy.buffer(req); + + proxyServer = httpProxy.createServer(host, targetPort, function (req, res, proxy) { + var buffer = httpProxy.buffer(req); setTimeout(function () { - proxy.proxyRequest(req, res, { - port: targetPort, - host: host, - buffer: buffer - }); + proxy.proxyRequest(req, res, buffer); }, latency); - }, this.options); + }, this.getOptions()); proxyServer.listen(port, function () { that.testServers.push(proxyServer); @@ -242,7 +224,7 @@ TestRunner.prototype.startLatentProxyServer = function (port, targetPort, host, // TestRunner.prototype.startProxyServerWithTable = function (port, options, callback) { var that = this, - proxyServer = httpProxy.createServer(merge({}, options, this.options)); + proxyServer = httpProxy.createServer(merge({}, options, this.getOptions())); proxyServer.listen(port, function () { that.testServers.push(proxyServer); @@ -260,7 +242,7 @@ TestRunner.prototype.startProxyServerWithTableAndLatency = function (port, laten // Initialize the nodeProxy and start proxying the request // var that = this, - proxy = new httpProxy.HttpProxy(merge({}, options, that.options)), + proxy = new httpProxy.HttpProxy(merge({}, options, this.getOptions())), proxyServer; var handler = function (req, res) { @@ -289,7 +271,7 @@ TestRunner.prototype.startProxyServerWithTableAndLatency = function (port, laten // TestRunner.prototype.startProxyServerWithForwarding = function (port, targetPort, host, options, callback) { var that = this, - proxyServer = httpProxy.createServer(targetPort, host, merge({}, options, this.options)); + proxyServer = httpProxy.createServer(targetPort, host, merge({}, options, this.getOptions())); proxyServer.listen(port, function () { that.testServers.push(proxyServer); @@ -310,9 +292,9 @@ TestRunner.prototype.startTargetServer = function (port, output, callback) { res.write(output); res.end(); }; - - targetServer = this.options.target.https - ? https.createServer(this.options.target.https, handler) + + targetServer = this.target.https + ? https.createServer(this.target.https, handler) : http.createServer(handler); targetServer.listen(port, function () { @@ -331,3 +313,42 @@ TestRunner.prototype.closeServers = function () { return this.testServers; }; + +// +// Creates a new instance of the options to +// pass to `httpProxy.createServer()` +// +TestRunner.prototype.getOptions = function () { + return { + https: clone(this.source.https), + target: { + https: clone(this.target.https) + } + }; +}; + +// +// ### @private function clone (object) +// #### @object {Object} Object to clone +// Shallow clones the specified object. +// +function clone (object) { + if (!object) { return null } + + return Object.keys(object).reduce(function (obj, k) { + obj[k] = object[k]; + return obj; + }, {}); +} + +function merge (target) { + var objs = Array.prototype.slice.call(arguments, 1); + objs.forEach(function(o) { + Object.keys(o).forEach(function (attr) { + if (! o.__lookupGetter__(attr)) { + target[attr] = o[attr]; + } + }); + }); + return target; +} \ No newline at end of file From daf9231a66f10a25782d2227df1b1501099ac5d1 Mon Sep 17 00:00:00 2001 From: indexzero Date: Sun, 28 Aug 2011 05:06:14 -0400 Subject: [PATCH 04/29] [test fix] A few minor fixes to ensure basic WebSocket tests are working. Better scope tests by supported protocol --- lib/node-http-proxy.js | 6 +- lib/node-http-proxy/http-proxy.js | 15 +-- test/helpers.js | 1 - .../http-proxy-table-test.js} | 5 +- .../http-proxy-test.js} | 2 +- test/websocket/websocket-proxy-table-test.js | 104 ++++++++++++++++++ .../websocket-proxy-test.js} | 48 +------- 7 files changed, 120 insertions(+), 61 deletions(-) rename test/{proxy-table-test.js => http/http-proxy-table-test.js} (97%) rename test/{node-http-proxy-test.js => http/http-proxy-test.js} (98%) create mode 100644 test/websocket/websocket-proxy-table-test.js rename test/{web-socket-proxy-test.js => websocket/websocket-proxy-test.js} (78%) diff --git a/lib/node-http-proxy.js b/lib/node-http-proxy.js index af0dd29..ccbd80b 100644 --- a/lib/node-http-proxy.js +++ b/lib/node-http-proxy.js @@ -78,7 +78,7 @@ exports.createServer = function () { case 'function': callback = arg; break; }; }); - + if (!host && !port && !options) { // // If `host`, `port` and `options` are all not passed, then @@ -285,12 +285,12 @@ exports._getProtocol = function _getProtocol (options) { // containing the relevant `ca`, `key`, and `cert` properties. // exports._getBase = function _getBase (options) { - var result = {}; + var result = function () {}; if (options.https && typeof options.https === 'object') { ['ca', 'cert', 'key'].forEach(function (key) { if (options.https[key]) { - result[key] = options.https[key]; + result.prototype[key] = options.https[key]; } }); } diff --git a/lib/node-http-proxy/http-proxy.js b/lib/node-http-proxy/http-proxy.js index 9461f2a..223452e 100644 --- a/lib/node-http-proxy/http-proxy.js +++ b/lib/node-http-proxy/http-proxy.js @@ -98,7 +98,8 @@ var HttpProxy = exports.HttpProxy = function (options) { // be provided or the operation will fail with an `origin mismatch` // by definition. // - this.source = options.source || { host: 'localhost', port: 8000 }; + this.source = options.source || { host: 'localhost', port: 8000 }; + this.source.https = this.source.https || options.https; this.changeOrigin = options.changeOrigin || false; }; @@ -114,7 +115,7 @@ util.inherits(HttpProxy, events.EventEmitter); HttpProxy.prototype.proxyRequest = function (req, res, buffer) { var self = this, errState = false, - outgoing = new Object(this.target.base), + outgoing = new(this.target.base), reverseProxy; // @@ -286,7 +287,7 @@ HttpProxy.prototype.proxyRequest = function (req, res, buffer) { // HttpProxy.prototype._forwardRequest = function (req) { var self = this, - outgoing = new Object(this.forward.base), + outgoing = new(this.forward.base), forwardProxy; // @@ -336,7 +337,7 @@ HttpProxy.prototype._forwardRequest = function (req) { }; // -// ### function proxyWebSocketRequest (req, socket, head, options) +// ### function proxyWebSocketRequest (req, socket, head, buffer) // #### @req {ServerRequest} Websocket request to proxy. // #### @socket {net.Socket} Socket for the underlying HTTP request // #### @head {string} Headers for the Websocket request. @@ -346,7 +347,7 @@ HttpProxy.prototype._forwardRequest = function (req) { // HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, buffer) { var self = this, - outgoing = new Object(this.target.base); + outgoing = new(this.target.base); listeners = {}, errState = false, CRLF = '\r\n'; @@ -478,7 +479,7 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, buffer) var agent = this.target.agent, protocolName = this.target.https ? 'https' : 'http', portUri = getPort(this.source.port), - remoteHost = options.host + portUri; + remoteHost = this.target.host + portUri; // // Change headers (if requested). @@ -567,7 +568,7 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, buffer) // Get the Non-Printable data data = data.slice(Buffer.byteLength(sdata), data.length); - if (self.https && !self.target.https) { + if (self.source.https && !self.target.https) { // // If the proxy server is running HTTPS but the client is running // HTTP then replace `ws` with `wss` in the data sent back to the client. diff --git a/test/helpers.js b/test/helpers.js index 9c91abf..036a1e5 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -112,7 +112,6 @@ TestRunner.prototype.assertResponseCode = function (proxyPort, statusCode, creat // // WebSocketTest // - TestRunner.prototype.webSocketTest = function (options) { var self = this; diff --git a/test/proxy-table-test.js b/test/http/http-proxy-table-test.js similarity index 97% rename from test/proxy-table-test.js rename to test/http/http-proxy-table-test.js index 359c82c..87cf6a8 100644 --- a/test/proxy-table-test.js +++ b/test/http/http-proxy-table-test.js @@ -12,11 +12,10 @@ var assert = require('assert'), argv = require('optimist').argv, request = require('request'), vows = require('vows'), - helpers = require('./helpers'), - TestRunner = helpers.TestRunner; + helpers = require('../helpers'); var protocol = argv.https ? 'https' : 'http', - runner = new TestRunner(protocol), + runner = new helpers.TestRunner(protocol), routeFile = path.join(__dirname, 'config.json'); var fileOptions = { diff --git a/test/node-http-proxy-test.js b/test/http/http-proxy-test.js similarity index 98% rename from test/node-http-proxy-test.js rename to test/http/http-proxy-test.js index f05ae5a..5437261 100644 --- a/test/node-http-proxy-test.js +++ b/test/http/http-proxy-test.js @@ -29,7 +29,7 @@ var assert = require('assert'), argv = require('optimist').argv, request = require('request'), vows = require('vows'), - helpers = require('./helpers'); + helpers = require('../helpers'); var forwardOptions = { forward: { diff --git a/test/websocket/websocket-proxy-table-test.js b/test/websocket/websocket-proxy-table-test.js new file mode 100644 index 0000000..a3edf05 --- /dev/null +++ b/test/websocket/websocket-proxy-table-test.js @@ -0,0 +1,104 @@ +/* + node-http-proxy-test.js: http proxy for node.js + + Copyright (c) 2010 Charlie Robbins, Marak Squires and Fedor Indutny + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +var util = require('util'), + assert = require('assert'), + argv = require('optimist').argv, + colors = require('colors'), + request = require('request'), + vows = require('vows'), + websocket = require('../vendor/websocket'), + helpers = require('../helpers'); + +try { + var utils = require('socket.io/lib/socket.io/utils'), + io = require('socket.io'); +} +catch (ex) { + console.error('Socket.io is required for this example:'); + console.error('npm ' + 'install'.green + ' socket.io@0.6.18'.magenta); + process.exit(1); +} + +var protocol = argv.https ? 'https' : 'http', + wsprotocol = argv.https ? 'wss' : 'ws', + runner = new helpers.TestRunner(protocol); + +vows.describe('node-http-proxy/websocket/' + wsprotocol).addBatch({ + "When using server created by httpProxy.createServer()": { + "using proxy table with no latency": { + "when an inbound message is sent from a WebSocket client": { + topic: function () { + var that = this + headers = {}; + + runner.webSocketTestWithTable({ + io: io, + host: 'localhost', + wsprotocol: wsprotocol, + protocol: protocol, + router: {'localhost':'localhost:8230'}, + ports: { + target: 8230, + proxy: 8231 + }, + onListen: function (socket) { + socket.on('connection', function (client) { + client.on('message', function (msg) { + that.callback(null, msg, headers); + }); + }); + }, + onWsupgrade: function (req, res) { + headers.request = req; + headers.response = res.headers; + }, + onOpen: function (ws) { + ws.send(utils.encode('from client')); + } + }); + }, + "the target server should receive the message": function (err, msg, headers) { + assert.equal(msg, 'from client'); + }, + "the origin and sec-websocket-origin headers should match": function (err, msg, headers) { + assert.isString(headers.response['sec-websocket-location']); + assert.isTrue(headers.response['sec-websocket-location'].indexOf(wsprotocol) !== -1); + assert.equal(headers.request.Origin, headers.response['sec-websocket-origin']); + } + } + } + } +}).addBatch({ + "When the tests are over": { + topic: function () { + return runner.closeServers(); + }, + "the servers should clean up": function () { + assert.isTrue(true); + } + } +}).export(module); diff --git a/test/web-socket-proxy-test.js b/test/websocket/websocket-proxy-test.js similarity index 78% rename from test/web-socket-proxy-test.js rename to test/websocket/websocket-proxy-test.js index 339bc71..bfc63e6 100644 --- a/test/web-socket-proxy-test.js +++ b/test/websocket/websocket-proxy-test.js @@ -30,8 +30,8 @@ var util = require('util'), colors = require('colors'), request = require('request'), vows = require('vows'), - websocket = require('./../vendor/websocket'), - helpers = require('./helpers'); + websocket = require('../../vendor/websocket'), + helpers = require('../helpers'); try { var utils = require('socket.io/lib/socket.io/utils'), @@ -48,50 +48,6 @@ var protocol = argv.https ? 'https' : 'http', runner = new helpers.TestRunner(protocol); vows.describe('node-http-proxy/websocket/' + wsprotocol).addBatch({ - "when using proxy table":{ - "with no latency" : { - "when an inbound message is sent from a WebSocket client": { - topic: function () { - var that = this - headers = {}; - - runner.webSocketTestWithTable({ - io: io, - host: 'localhost', - wsprotocol: wsprotocol, - protocol: protocol, - router: {'localhost':'localhost:8230'}, - ports: { - target: 8230, - proxy: 8231 - }, - onListen: function (socket) { - socket.on('connection', function (client) { - client.on('message', function (msg) { - that.callback(null, msg, headers); - }); - }); - }, - onWsupgrade: function (req, res) { - headers.request = req; - headers.response = res.headers; - }, - onOpen: function (ws) { - ws.send(utils.encode('from client')); - } - }); - }, - "the target server should receive the message": function (err, msg, headers) { - assert.equal(msg, 'from client'); - }, - "the origin and sec-websocket-origin headers should match": function (err, msg, headers) { - assert.isString(headers.response['sec-websocket-location']); - assert.isTrue(headers.response['sec-websocket-location'].indexOf(wsprotocol) !== -1); - assert.equal(headers.request.Origin, headers.response['sec-websocket-origin']); - } - } - } - }, "When using server created by httpProxy.createServer()": { "with no latency" : { "when an inbound message is sent from a WebSocket client": { From 340be42797e87fcc11859a771200075e7fe0c5f1 Mon Sep 17 00:00:00 2001 From: indexzero Date: Sun, 28 Aug 2011 05:28:57 -0400 Subject: [PATCH 05/29] [minor] Dont use `.bind()` --- lib/node-http-proxy.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/node-http-proxy.js b/lib/node-http-proxy.js index ccbd80b..561dad0 100644 --- a/lib/node-http-proxy.js +++ b/lib/node-http-proxy.js @@ -103,11 +103,11 @@ exports.createServer = function () { proxy = new HttpProxy(options); handler = callback ? function (req, res) { callback(req, res, proxy) } - : proxy.proxyRequest; + : function (req, res) { proxy.proxyRequest(req, res) }; server = options.https - ? https.createServer(options.https, handler.bind(proxy)) - : http.createServer(handler.bind(proxy)); + ? https.createServer(options.https, handler) + : http.createServer(handler); //server.on('close', function () { // proxy.close(); From de4a6fe8a5f78460b030e635e5f4a63312cd4a76 Mon Sep 17 00:00:00 2001 From: indexzero Date: Sun, 28 Aug 2011 21:21:35 -0400 Subject: [PATCH 06/29] [fix] Only set `x-forward-*` headers if req.connection and req.connection.socket --- lib/node-http-proxy/http-proxy.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/node-http-proxy/http-proxy.js b/lib/node-http-proxy/http-proxy.js index 223452e..5e9116c 100644 --- a/lib/node-http-proxy/http-proxy.js +++ b/lib/node-http-proxy/http-proxy.js @@ -126,10 +126,10 @@ HttpProxy.prototype.proxyRequest = function (req, res, buffer) { // * `x-forwarded-proto`: Protocol of the original request // * `x-forwarded-port`: Port of the original request. // - if (this.enable.xforward) { + if (this.enable.xforward && req.connection && req.connection.socket) { req.headers['x-forwarded-for'] = req.connection.remoteAddress || req.connection.socket.remoteAddress; req.headers['x-forwarded-port'] = req.connection.remotePort || req.connection.socket.remotePort; - req.headers['x-forwarded-proto'] = res.connection.pair ? 'https' : 'http'; + req.headers['x-forwarded-proto'] = req.connection.pair ? 'https' : 'http'; } // From e1c41d06942b56f6cd65a079ae78b54456a8bbe1 Mon Sep 17 00:00:00 2001 From: indexzero Date: Sun, 28 Aug 2011 21:27:01 -0400 Subject: [PATCH 07/29] [fix] Add guards to every throw-able res.end call --- lib/node-http-proxy/http-proxy.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/node-http-proxy/http-proxy.js b/lib/node-http-proxy/http-proxy.js index 5e9116c..f6822b3 100644 --- a/lib/node-http-proxy/http-proxy.js +++ b/lib/node-http-proxy/http-proxy.js @@ -178,7 +178,8 @@ HttpProxy.prototype.proxyRequest = function (req, res, buffer) { } } - res.end(); + try { res.end() } + catch (ex) { console.error("res.end error: %s", ex.message) } } // @@ -209,7 +210,9 @@ HttpProxy.prototype.proxyRequest = function (req, res, buffer) { // If `response.statusCode === 304`: No 'data' event and no 'end' if (response.statusCode === 304) { - return res.end(); + try { res.end() } + catch (ex) { console.error("res.end error: %s", ex.message) } + return; } // @@ -223,9 +226,10 @@ HttpProxy.prototype.proxyRequest = function (req, res, buffer) { try { res.write(chunk); } - catch (er) { + catch (ex) { + console.error("res.write error: %s", ex.message); try { res.end() } - catch (er) {} + catch (ex) { console.error("res.write error: %s", ex.message) } } } }); @@ -240,8 +244,10 @@ HttpProxy.prototype.proxyRequest = function (req, res, buffer) { response.on('end', function () { if (!errState) { reverseProxy.removeListener('error', proxyError); - res.end(); - + + try { res.end() } + catch (ex) { console.error("res.end error: %s", ex.message) } + // Emit the `end` event now that we have completed proxying self.emit('end', req, res); } From ca1d12cf1bbfbe98b5159f9c02e2f6c818a1c749 Mon Sep 17 00:00:00 2001 From: indexzero Date: Thu, 8 Sep 2011 16:21:20 -0700 Subject: [PATCH 08/29] [fix] Memory leak hunting. --- lib/node-http-proxy.js | 8 ++++++++ lib/node-http-proxy/http-proxy.js | 18 +++++++++++++----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/lib/node-http-proxy.js b/lib/node-http-proxy.js index 561dad0..02aae9c 100644 --- a/lib/node-http-proxy.js +++ b/lib/node-http-proxy.js @@ -174,6 +174,14 @@ exports.buffer = function (obj) { obj.removeListener('data', onData); obj.removeListener('end', onEnd); }, + destroy: function () { + this.end(); + this.resume = function () { + console.error("Cannot resume buffer after destroying it."); + }; + + onData = onEnd = events = obj = null; + }, resume: function () { this.end(); for (var i = 0, len = events.length; i < len; ++i) { diff --git a/lib/node-http-proxy/http-proxy.js b/lib/node-http-proxy/http-proxy.js index f6822b3..4ccbcab 100644 --- a/lib/node-http-proxy/http-proxy.js +++ b/lib/node-http-proxy/http-proxy.js @@ -279,9 +279,13 @@ HttpProxy.prototype.proxyRequest = function (req, res, buffer) { } }); + // // If we have been passed buffered data, resume it. - if (buffer && !errState) { - buffer.resume(); + // + if (buffer) { + return !errState + ? buffer.resume() + : buffer.destroy(); } }; @@ -419,6 +423,7 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, buffer) reverseProxy.incoming.socket.write(data); } catch (ex) { + detach(); reverseProxy.incoming.socket.end(); proxySocket.end(); } @@ -429,12 +434,13 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, buffer) // Any outgoing data on this Websocket from the proxy target // will be written to the `proxySocket` socket. // - reverseProxy.incoming.socket.on('data', listeners.onOutgoing = function(data) { + reverseProxy.incoming.socket.on('data', listeners.onOutgoing = function (data) { try { self.emit('websocket:incoming', reverseProxy, reverseProxy.incoming, head, data); proxySocket.write(data); } catch (ex) { + detach(); proxySocket.end(); socket.end(); } @@ -625,7 +631,9 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, buffer) // // If we have been passed buffered data, resume it. // - if (buffer && !errState) { - buffer.resume(); + if (buffer) { + return !errState + ? buffer.resume() + : buffer.destroy(); } }; From 6a7fd14bfa9f25694d75cf490e32817ff15a94fe Mon Sep 17 00:00:00 2001 From: indexzero Date: Thu, 8 Sep 2011 16:29:15 -0700 Subject: [PATCH 09/29] Add flow control --- lib/node-http-proxy/http-proxy.js | 62 ++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 10 deletions(-) diff --git a/lib/node-http-proxy/http-proxy.js b/lib/node-http-proxy/http-proxy.js index 4ccbcab..00fc05e 100644 --- a/lib/node-http-proxy/http-proxy.js +++ b/lib/node-http-proxy/http-proxy.js @@ -224,12 +224,22 @@ HttpProxy.prototype.proxyRequest = function (req, res, buffer) { response.on('data', function (chunk) { if (req.method !== 'HEAD' && res.writable) { try { - res.write(chunk); + var flushed = res.write(chunk); } catch (ex) { console.error("res.write error: %s", ex.message); + try { res.end() } - catch (ex) { console.error("res.write error: %s", ex.message) } + catch (ex) { console.error("res.end error: %s", ex.message) } + + return; + } + + if (!flushed) { + response.pause(); + res.once('drain', function () { + response.resume(); + }); } } }); @@ -265,7 +275,13 @@ HttpProxy.prototype.proxyRequest = function (req, res, buffer) { // req.on('data', function (chunk) { if (!errState) { - reverseProxy.write(chunk); + var flushed = reverseProxy.write(chunk); + if (!flushed) { + req.pause(); + reverseProxy.once('drain', function () { + req.resume(); + }); + } } }); @@ -334,8 +350,14 @@ HttpProxy.prototype._forwardRequest = function (req) { // the proxied request come in // req.on('data', function (chunk) { - forwardProxy.write(chunk); - }) + var flushed = forwardProxy.write(chunk); + if (!flushed) { + req.pause(); + forwardProxy.once('drain', function () { + req.resume(); + }); + } + }); // // At the end of the client request, we are going to @@ -357,7 +379,7 @@ HttpProxy.prototype._forwardRequest = function (req) { // HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, buffer) { var self = this, - outgoing = new(this.target.base); + outgoing = new(this.target.base), listeners = {}, errState = false, CRLF = '\r\n'; @@ -420,7 +442,13 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, buffer) if (reverseProxy.incoming.socket.writable) { try { self.emit('websocket:outgoing', req, socket, head, data); - reverseProxy.incoming.socket.write(data); + var flushed = reverseProxy.incoming.socket.write(data); + if (!flushed) { + proxySocket.pause(); + reverseProxy.incoming.socket.once('drain', function () { + proxySocket.resume(); + }); + } } catch (ex) { detach(); @@ -437,7 +465,13 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, buffer) reverseProxy.incoming.socket.on('data', listeners.onOutgoing = function (data) { try { self.emit('websocket:incoming', reverseProxy, reverseProxy.incoming, head, data); - proxySocket.write(data); + var flushed = proxySocket.write(data); + if (!flushed) { + reverseProxy.incoming.socket.pause(); + proxySocket.once('drain', function () { + reverseProxy.incoming.socket.resume(); + }); + } } catch (ex) { detach(); @@ -595,7 +629,13 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, buffer) // self.emit('websocket:handshake', req, socket, head, sdata, data); socket.write(sdata); - socket.write(data); + var flushed = socket.write(data); + if (!flushed) { + reverseProxy.socket.pause(); + socket.once('drain', function () { + reverseProxy.socket.resume(); + }); + } } catch (ex) { // @@ -620,7 +660,9 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, buffer) try { // - // Attempt to write the upgrade-head to the reverseProxy request. + // Attempt to write the upgrade-head to the reverseProxy + // request. This is small, and there's only ever one of + // it; no need for pause/resume. // reverseProxy.write(head); } From 37e25418916a31e4a513ee5866d6013858d579cf Mon Sep 17 00:00:00 2001 From: indexzero Date: Thu, 8 Sep 2011 16:36:19 -0700 Subject: [PATCH 10/29] Emit drain if it doesn't happen on its own in 100ms --- lib/node-http-proxy/http-proxy.js | 48 +++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/lib/node-http-proxy/http-proxy.js b/lib/node-http-proxy/http-proxy.js index 00fc05e..89a447a 100644 --- a/lib/node-http-proxy/http-proxy.js +++ b/lib/node-http-proxy/http-proxy.js @@ -240,6 +240,14 @@ HttpProxy.prototype.proxyRequest = function (req, res, buffer) { res.once('drain', function () { response.resume(); }); + + // + // Force the `drain` event in 100ms if it hasn't + // happened on its own. + // + setTimeout(function () { + res.emit('drain'); + }, 100); } } }); @@ -281,6 +289,14 @@ HttpProxy.prototype.proxyRequest = function (req, res, buffer) { reverseProxy.once('drain', function () { req.resume(); }); + + // + // Force the `drain` event in 100ms if it hasn't + // happened on its own. + // + setTimeout(function () { + reverseProxy.emit('drain'); + }, 100); } } }); @@ -356,6 +372,14 @@ HttpProxy.prototype._forwardRequest = function (req) { forwardProxy.once('drain', function () { req.resume(); }); + + // + // Force the `drain` event in 100ms if it hasn't + // happened on its own. + // + setTimeout(function () { + forwardProxy.emit('drain'); + }, 100); } }); @@ -448,6 +472,14 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, buffer) reverseProxy.incoming.socket.once('drain', function () { proxySocket.resume(); }); + + // + // Force the `drain` event in 100ms if it hasn't + // happened on its own. + // + setTimeout(function () { + reverseProxy.incoming.socket.emit('drain'); + }, 100); } } catch (ex) { @@ -471,6 +503,14 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, buffer) proxySocket.once('drain', function () { reverseProxy.incoming.socket.resume(); }); + + // + // Force the `drain` event in 100ms if it hasn't + // happened on its own. + // + setTimeout(function () { + proxySocket.emit('drain'); + }, 100); } } catch (ex) { @@ -635,6 +675,14 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, buffer) socket.once('drain', function () { reverseProxy.socket.resume(); }); + + // + // Force the `drain` event in 100ms if it hasn't + // happened on its own. + // + setTimeout(function () { + socket.emit('drain'); + }, 100); } } catch (ex) { From 0c71119ee58ee84068120be72308ecb28cb3e532 Mon Sep 17 00:00:00 2001 From: indexzero Date: Thu, 8 Sep 2011 16:41:42 -0700 Subject: [PATCH 11/29] resume() can throw --- lib/node-http-proxy/http-proxy.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/node-http-proxy/http-proxy.js b/lib/node-http-proxy/http-proxy.js index 89a447a..fd94d82 100644 --- a/lib/node-http-proxy/http-proxy.js +++ b/lib/node-http-proxy/http-proxy.js @@ -238,7 +238,8 @@ HttpProxy.prototype.proxyRequest = function (req, res, buffer) { if (!flushed) { response.pause(); res.once('drain', function () { - response.resume(); + try { response.resume() } + catch (er) { console.error("response.resume error: %s", er.message) } }); // @@ -287,7 +288,8 @@ HttpProxy.prototype.proxyRequest = function (req, res, buffer) { if (!flushed) { req.pause(); reverseProxy.once('drain', function () { - req.resume(); + try { req.resume() } + catch (er) { console.error("req.resume error: %s", er.message) } }); // @@ -370,7 +372,8 @@ HttpProxy.prototype._forwardRequest = function (req) { if (!flushed) { req.pause(); forwardProxy.once('drain', function () { - req.resume(); + try { req.resume() } + catch (er) { console.error("req.resume error: %s", er.message) } }); // @@ -470,7 +473,8 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, buffer) if (!flushed) { proxySocket.pause(); reverseProxy.incoming.socket.once('drain', function () { - proxySocket.resume(); + try { proxySocket.resume() } + catch (er) { console.error("proxySocket.resume error: %s", er.message) } }); // @@ -501,7 +505,8 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, buffer) if (!flushed) { reverseProxy.incoming.socket.pause(); proxySocket.once('drain', function () { - reverseProxy.incoming.socket.resume(); + try { reverseProxy.incoming.socket.resume() } + catch (er) { console.error("reverseProxy.incoming.socket.resume error: %s", er.message) } }); // @@ -619,7 +624,7 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, buffer) // This will force us not to disconnect. // // In addition, it's important to note the closure scope here. Since - // there is no mapping of the + // there is no mapping of the socket to the request bound to it. // if (!agent._events || agent._events['upgrade'].length === 0) { agent.on('upgrade', function (_, remoteSocket, head) { @@ -673,7 +678,8 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, buffer) if (!flushed) { reverseProxy.socket.pause(); socket.once('drain', function () { - reverseProxy.socket.resume(); + try { reverseProxy.socket.resume() } + catch (er) { console.error("reverseProxy.socket.resume error: %s", er.message) } }); // From 0e36912906640fdb007e0492b75c3f6a7b580ec6 Mon Sep 17 00:00:00 2001 From: indexzero Date: Thu, 8 Sep 2011 16:44:25 -0700 Subject: [PATCH 12/29] Fixed large DoS vector in the middleware implementation --- lib/node-http-proxy.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/node-http-proxy.js b/lib/node-http-proxy.js index 02aae9c..ea05ab1 100644 --- a/lib/node-http-proxy.js +++ b/lib/node-http-proxy.js @@ -222,12 +222,19 @@ exports.stack = function stack (middlewares, proxy) { handle = function (req, res) { var next = function (err) { if (err) { - throw err; - // - // TODO: figure out where to send errors. - // return error(req, res, err); - // + if (res._headerSent) { + res.destroy(); + } + else { + res.statusCode = 500; + res.setHeader('Content-Type', 'text/plain'); + res.end('Internal Server Error'); + } + + console.error('Error in middleware(s): %s', err.stack); + return; } + child(req, res); } From ec03d72c5d8749aee835f571869f69816be02265 Mon Sep 17 00:00:00 2001 From: indexzero Date: Thu, 8 Sep 2011 16:53:42 -0700 Subject: [PATCH 13/29] [minor] Move private methods to the bottom of file(s) --- lib/node-http-proxy.js | 2 +- lib/node-http-proxy/http-proxy.js | 144 +++++++++++++++--------------- 2 files changed, 73 insertions(+), 73 deletions(-) diff --git a/lib/node-http-proxy.js b/lib/node-http-proxy.js index ea05ab1..4769312 100644 --- a/lib/node-http-proxy.js +++ b/lib/node-http-proxy.js @@ -153,7 +153,7 @@ exports.createServer = function () { // __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 +// 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). // exports.buffer = function (obj) { diff --git a/lib/node-http-proxy/http-proxy.js b/lib/node-http-proxy/http-proxy.js index fd94d82..f288d3e 100644 --- a/lib/node-http-proxy/http-proxy.js +++ b/lib/node-http-proxy/http-proxy.js @@ -323,78 +323,6 @@ HttpProxy.prototype.proxyRequest = function (req, res, buffer) { } }; -// -// ### @private function _forwardRequest (req) -// #### @req {ServerRequest} Incoming HTTP Request to proxy. -// Forwards the specified `req` to the location specified -// by `this.forward` ignoring errors and the subsequent response. -// -HttpProxy.prototype._forwardRequest = function (req) { - var self = this, - outgoing = new(this.forward.base), - forwardProxy; - - // - // Setup outgoing proxy with relevant properties. - // - outgoing.host = this.forward.host; - outgoing.port = this.forward.port, - outgoing.agent = this.forward.agent; - outgoing.method = req.method; - outgoing.path = req.url; - outgoing.headers = req.headers; - - // - // Open new HTTP request to internal resource with will - // act as a reverse proxy pass. - // - forwardProxy = this.forward.protocol.request(outgoing, function (response) { - // - // Ignore the response from the forward proxy since this is a 'fire-and-forget' proxy. - // Remark (indexzero): We will eventually emit a 'forward' event here for performance tuning. - // - }); - - // - // Add a listener for the connection timeout event. - // - // Remark: Ignoring this error in the event - // forward target doesn't exist. - // - forwardProxy.once('error', function (err) { }); - - // - // Chunk the client request body as chunks from - // the proxied request come in - // - req.on('data', function (chunk) { - var flushed = forwardProxy.write(chunk); - if (!flushed) { - req.pause(); - forwardProxy.once('drain', function () { - try { req.resume() } - catch (er) { console.error("req.resume error: %s", er.message) } - }); - - // - // Force the `drain` event in 100ms if it hasn't - // happened on its own. - // - setTimeout(function () { - forwardProxy.emit('drain'); - }, 100); - } - }); - - // - // At the end of the client request, we are going to - // stop the proxied request - // - req.on('end', function () { - forwardProxy.end(); - }); -}; - // // ### function proxyWebSocketRequest (req, socket, head, buffer) // #### @req {ServerRequest} Websocket request to proxy. @@ -733,3 +661,75 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, buffer) : buffer.destroy(); } }; + +// +// ### @private function _forwardRequest (req) +// #### @req {ServerRequest} Incoming HTTP Request to proxy. +// Forwards the specified `req` to the location specified +// by `this.forward` ignoring errors and the subsequent response. +// +HttpProxy.prototype._forwardRequest = function (req) { + var self = this, + outgoing = new(this.forward.base), + forwardProxy; + + // + // Setup outgoing proxy with relevant properties. + // + outgoing.host = this.forward.host; + outgoing.port = this.forward.port, + outgoing.agent = this.forward.agent; + outgoing.method = req.method; + outgoing.path = req.url; + outgoing.headers = req.headers; + + // + // Open new HTTP request to internal resource with will + // act as a reverse proxy pass. + // + forwardProxy = this.forward.protocol.request(outgoing, function (response) { + // + // Ignore the response from the forward proxy since this is a 'fire-and-forget' proxy. + // Remark (indexzero): We will eventually emit a 'forward' event here for performance tuning. + // + }); + + // + // Add a listener for the connection timeout event. + // + // Remark: Ignoring this error in the event + // forward target doesn't exist. + // + forwardProxy.once('error', function (err) { }); + + // + // Chunk the client request body as chunks from + // the proxied request come in + // + req.on('data', function (chunk) { + var flushed = forwardProxy.write(chunk); + if (!flushed) { + req.pause(); + forwardProxy.once('drain', function () { + try { req.resume() } + catch (er) { console.error("req.resume error: %s", er.message) } + }); + + // + // Force the `drain` event in 100ms if it hasn't + // happened on its own. + // + setTimeout(function () { + forwardProxy.emit('drain'); + }, 100); + } + }); + + // + // At the end of the client request, we are going to + // stop the proxied request + // + req.on('end', function () { + forwardProxy.end(); + }); +}; \ No newline at end of file From 0eae2a913a2173d85478f8c9deec929388284ee2 Mon Sep 17 00:00:00 2001 From: indexzero Date: Thu, 8 Sep 2011 16:55:45 -0700 Subject: [PATCH 14/29] [api] Added new `close()` method which cleans up sockets from HttpProxy instances --- lib/node-http-proxy/http-proxy.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/node-http-proxy/http-proxy.js b/lib/node-http-proxy/http-proxy.js index f288d3e..71eae76 100644 --- a/lib/node-http-proxy/http-proxy.js +++ b/lib/node-http-proxy/http-proxy.js @@ -662,6 +662,21 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, buffer) } }; +// +// ### function close() +// Closes all sockets associated with the Agents +// belonging to this instance. +// +HttpProxy.prototype.close = function () { + [this.forward, this.target].forEach(function (proxy) { + if (proxy.agent) { + proxy.agent.sockets.forEach(function (socket) { + socket.end(); + }); + } + }); +}; + // // ### @private function _forwardRequest (req) // #### @req {ServerRequest} Incoming HTTP Request to proxy. From 81d6c318758231f77a52fab7de174fcc63b7a243 Mon Sep 17 00:00:00 2001 From: indexzero Date: Thu, 8 Sep 2011 17:01:19 -0700 Subject: [PATCH 15/29] [dist] Reorganize examples based on classification(s): http, websocket, or middleware --- examples/{ => http}/basic-proxy.js | 0 examples/{ => http}/concurrent-proxy.js | 0 examples/{ => http}/custom-proxy-error.js | 0 examples/{ => http}/forward-proxy.js | 0 examples/{ => http}/latent-proxy.js | 0 examples/{ => http}/proxy-https-to-http.js | 0 examples/{ => http}/proxy-https-to-https.js | 0 examples/{ => http}/proxy-table.js | 0 examples/{ => http}/standalone-proxy.js | 0 examples/{ => middleware}/bodyDecoder-middleware.js | 0 examples/{ => middleware}/gzip-middleware.js | 0 examples/{ => middleware}/jsonp-middleware.js | 0 examples/{ => middleware}/url-middleware.js | 0 examples/{ => middleware}/url-middleware2.js | 0 examples/{ => websocket}/latent-websocket-proxy.js | 0 examples/{ => websocket}/standalone-websocket-proxy.js | 0 examples/{web-socket-proxy.js => websocket/websocket-proxy.js} | 0 17 files changed, 0 insertions(+), 0 deletions(-) rename examples/{ => http}/basic-proxy.js (100%) rename examples/{ => http}/concurrent-proxy.js (100%) rename examples/{ => http}/custom-proxy-error.js (100%) rename examples/{ => http}/forward-proxy.js (100%) rename examples/{ => http}/latent-proxy.js (100%) rename examples/{ => http}/proxy-https-to-http.js (100%) rename examples/{ => http}/proxy-https-to-https.js (100%) rename examples/{ => http}/proxy-table.js (100%) rename examples/{ => http}/standalone-proxy.js (100%) rename examples/{ => middleware}/bodyDecoder-middleware.js (100%) rename examples/{ => middleware}/gzip-middleware.js (100%) rename examples/{ => middleware}/jsonp-middleware.js (100%) rename examples/{ => middleware}/url-middleware.js (100%) rename examples/{ => middleware}/url-middleware2.js (100%) rename examples/{ => websocket}/latent-websocket-proxy.js (100%) rename examples/{ => websocket}/standalone-websocket-proxy.js (100%) rename examples/{web-socket-proxy.js => websocket/websocket-proxy.js} (100%) diff --git a/examples/basic-proxy.js b/examples/http/basic-proxy.js similarity index 100% rename from examples/basic-proxy.js rename to examples/http/basic-proxy.js diff --git a/examples/concurrent-proxy.js b/examples/http/concurrent-proxy.js similarity index 100% rename from examples/concurrent-proxy.js rename to examples/http/concurrent-proxy.js diff --git a/examples/custom-proxy-error.js b/examples/http/custom-proxy-error.js similarity index 100% rename from examples/custom-proxy-error.js rename to examples/http/custom-proxy-error.js diff --git a/examples/forward-proxy.js b/examples/http/forward-proxy.js similarity index 100% rename from examples/forward-proxy.js rename to examples/http/forward-proxy.js diff --git a/examples/latent-proxy.js b/examples/http/latent-proxy.js similarity index 100% rename from examples/latent-proxy.js rename to examples/http/latent-proxy.js diff --git a/examples/proxy-https-to-http.js b/examples/http/proxy-https-to-http.js similarity index 100% rename from examples/proxy-https-to-http.js rename to examples/http/proxy-https-to-http.js diff --git a/examples/proxy-https-to-https.js b/examples/http/proxy-https-to-https.js similarity index 100% rename from examples/proxy-https-to-https.js rename to examples/http/proxy-https-to-https.js diff --git a/examples/proxy-table.js b/examples/http/proxy-table.js similarity index 100% rename from examples/proxy-table.js rename to examples/http/proxy-table.js diff --git a/examples/standalone-proxy.js b/examples/http/standalone-proxy.js similarity index 100% rename from examples/standalone-proxy.js rename to examples/http/standalone-proxy.js diff --git a/examples/bodyDecoder-middleware.js b/examples/middleware/bodyDecoder-middleware.js similarity index 100% rename from examples/bodyDecoder-middleware.js rename to examples/middleware/bodyDecoder-middleware.js diff --git a/examples/gzip-middleware.js b/examples/middleware/gzip-middleware.js similarity index 100% rename from examples/gzip-middleware.js rename to examples/middleware/gzip-middleware.js diff --git a/examples/jsonp-middleware.js b/examples/middleware/jsonp-middleware.js similarity index 100% rename from examples/jsonp-middleware.js rename to examples/middleware/jsonp-middleware.js diff --git a/examples/url-middleware.js b/examples/middleware/url-middleware.js similarity index 100% rename from examples/url-middleware.js rename to examples/middleware/url-middleware.js diff --git a/examples/url-middleware2.js b/examples/middleware/url-middleware2.js similarity index 100% rename from examples/url-middleware2.js rename to examples/middleware/url-middleware2.js diff --git a/examples/latent-websocket-proxy.js b/examples/websocket/latent-websocket-proxy.js similarity index 100% rename from examples/latent-websocket-proxy.js rename to examples/websocket/latent-websocket-proxy.js diff --git a/examples/standalone-websocket-proxy.js b/examples/websocket/standalone-websocket-proxy.js similarity index 100% rename from examples/standalone-websocket-proxy.js rename to examples/websocket/standalone-websocket-proxy.js diff --git a/examples/web-socket-proxy.js b/examples/websocket/websocket-proxy.js similarity index 100% rename from examples/web-socket-proxy.js rename to examples/websocket/websocket-proxy.js From 3a4d312eda08e7a5cecb3c82b04023e22f368e2b Mon Sep 17 00:00:00 2001 From: indexzero Date: Thu, 8 Sep 2011 17:57:44 -0700 Subject: [PATCH 16/29] [test] Whitespace fix --- test/helpers.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/helpers.js b/test/helpers.js index 036a1e5..1bc9915 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -148,7 +148,6 @@ TestRunner.prototype.webSocketTest = function (options) { // // WebSocketTestWithTable // - TestRunner.prototype.webSocketTestWithTable = function (options) { var self = this; From 29372298208135f571538cc29dcc05f41f79b01c Mon Sep 17 00:00:00 2001 From: indexzero Date: Thu, 8 Sep 2011 17:58:11 -0700 Subject: [PATCH 17/29] [dist] Update examples/package.json to conform to nodejitsu style guidelines --- examples/package.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/package.json b/examples/package.json index fa7f43a..ca95fd8 100644 --- a/examples/package.json +++ b/examples/package.json @@ -1,12 +1,12 @@ { - "name": "http-proxy-examples" -, "description": "packages required to run the examples" -, "version": "0.0.0" -, "dependencies": { - "connect": "1.6" - , "connect-gzip": "0.1" - , "connect-jsonp": "0.0.5" - , "connect-restreamer": "1" - , "proxy-by-url": ">= 0.0.1" + "name": "http-proxy-examples", + "description": "packages required to run the examples", + "version": "0.0.0", + "dependencies": { + "connect": "1.6", + "connect-gzip": "0.1", + "connect-jsonp": "0.0.5", + "connect-restreamer": "1", + "proxy-by-url": ">= 0.0.1" } } \ No newline at end of file From 5927ecd62a082269c3b6a0ae4f5b4a673784bcdb Mon Sep 17 00:00:00 2001 From: indexzero Date: Thu, 8 Sep 2011 18:22:38 -0700 Subject: [PATCH 18/29] [api test dist] Stubbed out the API for the higher-level `RoutingProxy` object to be exposed by `node-http-proxy` --- lib/node-http-proxy/http-proxy.js | 4 +- lib/node-http-proxy/proxy-table.js | 4 +- lib/node-http-proxy/routing-proxy.js | 165 +++++++++++++++++- ...xy-table-test.js => routing-proxy-test.js} | 0 ...est.js => websocket-routing-proxy-test.js} | 0 5 files changed, 170 insertions(+), 3 deletions(-) rename test/http/{http-proxy-table-test.js => routing-proxy-test.js} (100%) rename test/websocket/{websocket-proxy-table-test.js => websocket-routing-proxy-test.js} (100%) diff --git a/lib/node-http-proxy/http-proxy.js b/lib/node-http-proxy/http-proxy.js index 71eae76..babf1ac 100644 --- a/lib/node-http-proxy/http-proxy.js +++ b/lib/node-http-proxy/http-proxy.js @@ -81,7 +81,9 @@ var HttpProxy = exports.HttpProxy = function (options) { } setupProxy('target') - if (this.forward) { setupProxy('forward') } + if (this.forward) { + setupProxy('forward'); + } // // Setup opt-in features diff --git a/lib/node-http-proxy/proxy-table.js b/lib/node-http-proxy/proxy-table.js index 233e552..a6ecff6 100644 --- a/lib/node-http-proxy/proxy-table.js +++ b/lib/node-http-proxy/proxy-table.js @@ -72,7 +72,9 @@ var ProxyTable = exports.ProxyTable = function (router, silent, hostnameOnly) { } }; -// Inherit from events.EventEmitter +// +// Inherit from `events.EventEmitter` +// util.inherits(ProxyTable, events.EventEmitter); // diff --git a/lib/node-http-proxy/routing-proxy.js b/lib/node-http-proxy/routing-proxy.js index 26887bd..969aff5 100644 --- a/lib/node-http-proxy/routing-proxy.js +++ b/lib/node-http-proxy/routing-proxy.js @@ -1,4 +1,167 @@ +var events = require('events'), + util = require('util'), + HttpProxy = require('./http-proxy').HttpProxy, + ProxyTable = require('./proxy-table').ProxyTable; -var RoutingProxy = exports.RoutingProxy = function () { +// +// ### function RoutingProxy (options) +// #### @options {Object} Options for this instance +// Constructor function for the RoutingProxy object, a higher level +// reverse proxy Object which can proxy to multiple hosts and also interface +// easily with a RoutingTable instance. +// +var RoutingProxy = exports.RoutingProxy = function (options) { + events.EventEmitter.call(this); + if (options.router) { + // + // TODO: Consume the RoutingTable for various things: `this.proxyTable` + // + } + + // + // Create a set of `HttpProxy` objects to be used later on calls + // to `.proxyRequest()` and `.proxyWebSocketRequest()`. + // + this.proxies = {}; +}; + + +// +// Inherit from `events.EventEmitter`. +// +util.inherits(RoutingProxy, events.EventEmitter); + +// +// ### function add (options) +// #### @options {Object} Options for the `HttpProxy` to add. +// Adds a new instance of `HttpProxy` to this `RoutingProxy` instance +// for the specified `options.host` and `options.port`. +// +RoutingProxy.prototype.add = function (options) { + +}; + +// +// ### function remove (options) +// #### @options {Object} Options mapping to the `HttpProxy` to remove. +// Removes an instance of `HttpProxy` from this `RoutingProxy` instance +// for the specified `options.host` and `options.port` (if they exist). +// +RoutingProxy.prototype.remove = function (options) { + +}; + +// +// ### function close() +// Cleans up any state left behind (sockets, timeouts, etc) +// associated with this instance. +// +RoutingProxy.prototype.close = function () { + var self = this; + + if (this.proxyTable) { + // + // Close the `RoutingTable` associated with + // this instance (if any). + // + this.proxyTable.close(); + } + + // + // Close all sockets for all `HttpProxy` object(s) + // associated with this instance. + // + Object.keys(this.proxies).forEach(function (key) { + self.proxies[key].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. +// #### @options {Object} Options for the outgoing proxy request. +// +// options.port {number} Port to use on the proxy target host. +// options.host {string} Host of the proxy target. +// options.buffer {Object} Result from `httpProxy.buffer(req)` +// options.https {Object|boolean} Settings for https. +// +RoutingProxy.prototype.proxyRequest = function (req, res, options) { + options = options || {}; + + // + // Check the proxy table for this instance to see if we need + // to get the proxy location for the request supplied. We will + // always ignore the proxyTable if an explicit `port` and `host` + // arguments are supplied to `proxyRequest`. + // + if (this.proxyTable && !options.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) { + try { + res.writeHead(404); + res.end(); + } + catch (er) { + console.error("res.writeHead/res.end error: %s", er.message); + } + + return; + } + + // + // When using the ProxyTable in conjunction with an HttpProxy instance + // only the following arguments are valid: + // + // * `proxy.proxyRequest(req, res, { host: 'localhost' })`: This will be skipped + // * `proxy.proxyRequest(req, res, { buffer: buffer })`: Buffer will get updated appropriately + // * `proxy.proxyRequest(req, res)`: Options will be assigned appropriately. + // + options.port = location.port; + options.host = location.host; + } + + var key = options.host + ':' + options.port, + proxy = this.proxies[key] || this._addTarget(options); + + proxy.proxyRequest(req, res, options.buffer); +}; + +// +// ### function proxyWebSocketRequest (req, socket, head, options) +// #### @req {ServerRequest} Websocket request to proxy. +// #### @socket {net.Socket} Socket for the underlying HTTP request +// #### @head {string} Headers for the Websocket request. +// #### @options {Object} Options to use when proxying this request. +// +// options.port {number} Port to use on the proxy target host. +// options.host {string} Host of the proxy target. +// options.buffer {Object} Result from `httpProxy.buffer(req)` +// options.https {Object|boolean} Settings for https. +// +RoutingProxy.prototype.proxyWebSocketRequest = function (req, socket, head, options) { + options = options || {}; + + if (this.proxyTable && !options.host) { + location = this.proxyTable.getProxyLocation(req); + + if (!location) { + return socket.destroy(); + } + + options.port = location.port; + options.host = location.host; + } + + var key = options.host + ':' + options.port, + proxy = this.proxies[key] || this._addTarget(options); + + proxy.proxyWebSocketRequest(req, socket, head, options.buffer); }; \ No newline at end of file diff --git a/test/http/http-proxy-table-test.js b/test/http/routing-proxy-test.js similarity index 100% rename from test/http/http-proxy-table-test.js rename to test/http/routing-proxy-test.js diff --git a/test/websocket/websocket-proxy-table-test.js b/test/websocket/websocket-routing-proxy-test.js similarity index 100% rename from test/websocket/websocket-proxy-table-test.js rename to test/websocket/websocket-routing-proxy-test.js From 598fe2e38def56518a1f0a8196b2fcb7f1bc569e Mon Sep 17 00:00:00 2001 From: indexzero Date: Fri, 9 Sep 2011 18:14:49 -0400 Subject: [PATCH 19/29] [api doc] Rebuilt httpProxy.createServer() with the newer high-level RoutingProxy API --- examples/{lib => helpers}/store.js | 0 lib/node-http-proxy.js | 129 +++++++++++++++++++++++------ lib/node-http-proxy/http-proxy.js | 2 +- 3 files changed, 105 insertions(+), 26 deletions(-) rename examples/{lib => helpers}/store.js (100%) diff --git a/examples/lib/store.js b/examples/helpers/store.js similarity index 100% rename from examples/lib/store.js rename to examples/helpers/store.js diff --git a/lib/node-http-proxy.js b/lib/node-http-proxy.js index 4769312..12bc716 100644 --- a/lib/node-http-proxy.js +++ b/lib/node-http-proxy.js @@ -56,12 +56,14 @@ var HttpProxy = exports.HttpProxy = require('./node-http-proxy/http-proxy' // exports.createServer = function () { var args = Array.prototype.slice.call(arguments), + handlers = [], options = {}, - host, port, - server, proxy, - callback, + message, handler, - silent; + server, + proxy, + host, + port; // // Liberally parse arguments of the form: @@ -75,18 +77,50 @@ exports.createServer = function () { case 'string': host = arg; break; case 'number': port = arg; break; case 'object': options = arg || {}; break; - case 'function': callback = arg; break; + case 'function': handlers.push(arg); break; }; }); - if (!host && !port && !options) { - // - // If `host`, `port` and `options` are all not passed, then - // this server is improperly configured. - // - throw new Error('Cannot proxy without port, host, or router.') - } + // + // Helper function to create intelligent error message(s) + // for the very liberal arguments parsing performed by + // `require('http-proxy').createServer()`. + // + function validArguments() { + var conditions = { + 'port and host': function () { + return port && host; + }, + 'options.target or options.router': function () { + return options && (options.router || + (options.target && options.target.host && options.target.port)); + }, + 'or proxy handlers': function () { + return handlers && handlers.length; + } + } + + var missing = Object.keys(conditions).filter(function (name) { + return !conditions[name](); + }); + + if (missing.length === 3) { + message = 'Cannot proxy without ' + missing.join(', '); + return false; + } + + return true; + } + if (!validArguments()) { + // + // If `host`, `port` and `options` are all not passed (with valid + // options) then this server is improperly configured. + // + throw new Error(message); + return; + } + // // Hoist up any explicit `host` or `port` arguments // that have been passed in to the options we will @@ -96,28 +130,57 @@ exports.createServer = function () { options.target.port = options.target.port || port; options.target.host = options.target.host || host; + if (options.target && options.target.host && options.target.port) { + // + // If an explicit `host` and `port` combination has been passed + // to `.createServer()` then instantiate a hot-path optimized + // `HttpProxy` object and add the "proxy" middleware layer. + // + proxy = new HttpProxy(options); + handlers.push(function (req, res) { + proxy.proxyRequest(req, res); + }); + } + else { + // + // If no explicit `host` or `port` combination has been passed then + // we have to assume that this is a "go-anywhere" Proxy (i.e. a `RoutingProxy`). + // + proxy = new RoutingProxy(options); + + if (options.router) { + // + // If a routing table has been supplied than we assume + // the user intends us to add the "proxy" middleware layer + // for them + // + handlers.push(function (req, res) { + proxy.proxyRequest(req, res); + }); + + proxy.on('routes', function (routes) { + server.emit('routes', routes); + }); + } + } + // // Create the `http[s].Server` instance which will use // an instance of `httpProxy.HttpProxy`. // - proxy = new HttpProxy(options); - handler = callback - ? function (req, res) { callback(req, res, proxy) } - : function (req, res) { proxy.proxyRequest(req, res) }; + handler = handlers.length > 1 + ? exports.stack(handlers, proxy) + : function (req, res) { handlers[0](req, res, proxy) }; server = options.https ? https.createServer(options.https, handler) : http.createServer(handler); - //server.on('close', function () { - // proxy.close(); - //}); - - proxy.on('routes', function (routes) { - server.emit('routes', routes); + server.on('close', function () { + proxy.close(); }); - if (!callback) { + if (handlers.length <= 1) { // // If an explicit callback has not been supplied then // automagically proxy the request using the `HttpProxy` @@ -213,6 +276,13 @@ exports.setMaxSockets = function (value) { // // ### function stack (middlewares, proxy) +// #### @middlewares {Array} Array of functions to stack. +// #### @proxy {HttpProxy|RoutingProxy} Proxy instance to +// Iteratively build up a single handler to the `http.Server` +// `request` event (i.e. `function (req, res)`) by wrapping +// each middleware `layer` into a `child` middleware which +// is in invoked by the parent (i.e. predecessor in the Array). +// // adapted from https://github.com/creationix/stack // exports.stack = function stack (middlewares, proxy) { @@ -235,9 +305,18 @@ exports.stack = function stack (middlewares, proxy) { return; } - child(req, res); - } + if (child) { + child(req, res); + } + }; + // + // Set the prototype of the `next` function to the instance + // of the `proxy` so that in can be used interchangably from + // a `connect` style callback and a true `HttpProxy` object. + // + // e.g. `function (req, res, next)` vs. `function (req, res, proxy)` + // next.__proto__ = proxy; layer(req, res, next); }; diff --git a/lib/node-http-proxy/http-proxy.js b/lib/node-http-proxy/http-proxy.js index babf1ac..e02be9c 100644 --- a/lib/node-http-proxy/http-proxy.js +++ b/lib/node-http-proxy/http-proxy.js @@ -671,7 +671,7 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, buffer) // HttpProxy.prototype.close = function () { [this.forward, this.target].forEach(function (proxy) { - if (proxy.agent) { + if (proxy && proxy.agent) { proxy.agent.sockets.forEach(function (socket) { socket.end(); }); From f765f90ec37defaa2b493f859a982add51e25b76 Mon Sep 17 00:00:00 2001 From: indexzero Date: Sat, 10 Sep 2011 03:55:07 -0400 Subject: [PATCH 20/29] [api] Finalized the RoutingProxy API --- lib/node-http-proxy/http-proxy.js | 4 +- lib/node-http-proxy/proxy-table.js | 57 ++++++++-------- lib/node-http-proxy/routing-proxy.js | 98 +++++++++++++++++++++++++--- 3 files changed, 122 insertions(+), 37 deletions(-) diff --git a/lib/node-http-proxy/http-proxy.js b/lib/node-http-proxy/http-proxy.js index e02be9c..165fccf 100644 --- a/lib/node-http-proxy/http-proxy.js +++ b/lib/node-http-proxy/http-proxy.js @@ -80,7 +80,7 @@ var HttpProxy = exports.HttpProxy = function (options) { self[key].base = httpProxy._getBase(self[key]); } - setupProxy('target') + setupProxy('target'); if (this.forward) { setupProxy('forward'); } @@ -102,7 +102,7 @@ var HttpProxy = exports.HttpProxy = function (options) { // this.source = options.source || { host: 'localhost', port: 8000 }; this.source.https = this.source.https || options.https; - this.changeOrigin = options.changeOrigin || false; + this.changeOrigin = options.changeOrigin || false; }; // Inherit from events.EventEmitter diff --git a/lib/node-http-proxy/proxy-table.js b/lib/node-http-proxy/proxy-table.js index a6ecff6..749ea7d 100644 --- a/lib/node-http-proxy/proxy-table.js +++ b/lib/node-http-proxy/proxy-table.js @@ -1,7 +1,7 @@ /* node-http-proxy.js: Lookup table for proxy targets in node.js - Copyright (c) 2010 Charlie Robbins + Copyright (c) 2010 Charlie Robbins Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -29,7 +29,7 @@ var util = require('util'), fs = require('fs'); // -// ### function ProxyTable (router, silent) +// ### function ProxyTable (router, silent) // #### @router {Object} Object containing the host based routes // #### @silent {Boolean} Value indicating whether we should suppress logs // #### @hostnameOnly {Boolean} Value indicating if we should route based on __hostname string only__ @@ -37,31 +37,34 @@ var util = require('util'), // locations of proxy targets based on ServerRequest headers; specifically // the HTTP host header. // -var ProxyTable = exports.ProxyTable = function (router, silent, hostnameOnly) { +var ProxyTable = exports.ProxyTable = function (options) { events.EventEmitter.call(this); - - this.silent = typeof silent !== 'undefined' ? silent : true; - this.hostnameOnly = typeof hostnameOnly !== 'undefined' ? hostnameOnly : false; - - if (typeof router === 'object') { + + this.silent = options.silent || options.silent !== true; + this.hostnameOnly = options.hostnameOnly === true; + + if (typeof options.router === 'object') { // - // If we are passed an object literal setup - // the routes with RegExps from the router + // If we are passed an object literal setup + // the routes with RegExps from the router // - this.setRoutes(router); + this.setRoutes(options.router); } - else if (typeof router === 'string') { + else if (typeof options.router === 'string') { // - // If we are passed a string then assume it is a + // If we are passed a string then assume it is a // file path, parse that file and watch it for changes // var self = this; - this.routeFile = router; - this.setRoutes(JSON.parse(fs.readFileSync(router)).router); - + this.routeFile = options.router; + this.setRoutes(JSON.parse(fs.readFileSync(options.router)).router); + fs.watchFile(this.routeFile, function () { fs.readFile(self.routeFile, function (err, data) { - if (err) throw err; + if (err) { + self.emit('error', err); + } + self.setRoutes(JSON.parse(data).router); self.emit('routes', self.hostnameOnly === false ? self.routes : self.router); }); @@ -78,17 +81,17 @@ var ProxyTable = exports.ProxyTable = function (router, silent, hostnameOnly) { util.inherits(ProxyTable, events.EventEmitter); // -// ### function setRoutes (router) +// ### function setRoutes (router) // #### @router {Object} Object containing the host based routes -// Sets the host-based routes to be used by this instance. +// Sets the host-based routes to be used by this instance. // ProxyTable.prototype.setRoutes = function (router) { if (!router) { throw new Error('Cannot update ProxyTable routes without router.'); } - + this.router = router; - + if (this.hostnameOnly === false) { var self = this; this.routes = []; @@ -105,7 +108,7 @@ ProxyTable.prototype.setRoutes = function (router) { }; // -// ### function getProxyLocation (req) +// ### function getProxyLocation (req) // #### @req {ServerRequest} The incoming server request to get proxy information about. // Returns the proxy location based on the HTTP Headers in the ServerRequest `req` // available to this instance. @@ -114,14 +117,14 @@ ProxyTable.prototype.getProxyLocation = function (req) { if (!req || !req.headers || !req.headers.host) { return null; } - + var target = req.headers.host.split(':')[0]; if (this.hostnameOnly == true) { if (this.router.hasOwnProperty(target)) { var location = this.router[target].split(':'), host = location[0], port = location.length === 1 ? 80 : location[1]; - + return { port: port, host: host @@ -131,7 +134,9 @@ ProxyTable.prototype.getProxyLocation = function (req) { else { target += req.url; for (var i in this.routes) { - var match, route = this.routes[i]; + var route = this.routes[i], + match; + if (match = target.match(route.route)) { var location = route.target.split(':'), host = location[0], @@ -144,7 +149,7 @@ ProxyTable.prototype.getProxyLocation = function (req) { } } } - + return null; }; diff --git a/lib/node-http-proxy/routing-proxy.js b/lib/node-http-proxy/routing-proxy.js index 969aff5..3938b26 100644 --- a/lib/node-http-proxy/routing-proxy.js +++ b/lib/node-http-proxy/routing-proxy.js @@ -1,3 +1,11 @@ +/* + * routing-proxy.js: A routing proxy consuming a RoutingTable and multiple HttpProxy instances + * + * (C) 2011 Nodejitsu Inc. + * MIT LICENCE + * + */ + var events = require('events'), util = require('util'), HttpProxy = require('./http-proxy').HttpProxy, @@ -12,11 +20,15 @@ var events = require('events'), // var RoutingProxy = exports.RoutingProxy = function (options) { events.EventEmitter.call(this); + + var self = this; + options = options || {}; if (options.router) { - // - // TODO: Consume the RoutingTable for various things: `this.proxyTable` - // + this.proxyTable = new ProxyTable(options); + this.proxyTable.on('routes', function (routes) { + self.emit('routes', routes); + }); } // @@ -24,6 +36,21 @@ var RoutingProxy = exports.RoutingProxy = function (options) { // to `.proxyRequest()` and `.proxyWebSocketRequest()`. // this.proxies = {}; + + // + // Setup default target options (such as `https`). + // + this.targetĀ  = {}; + this.target.https = options.target && options.target.https; + + // + // Setup other default options to be used for instances of + // `HttpProxy` created by this `RoutingProxy` instance. + // + this.source = options.source || { host: 'localhost', port: 8000 }; + this.https = this.source.https || options.https; + this.enable = options.enable; + this.forward = options.forward; }; @@ -39,7 +66,30 @@ util.inherits(RoutingProxy, events.EventEmitter); // for the specified `options.host` and `options.port`. // RoutingProxy.prototype.add = function (options) { + var self = this, + key = this._getKey(options); + // + // TODO: Consume properties in `options` related to the `ProxyTable`. + // + options.target = options.target || {}; + options.target.host = options.target.host || options.host; + options.target.port = options.target.port || options.port; + options.target.https = this.target && this.target.https || + options.target && options.target.https || + options.https; + + // + // Setup options to pass-thru to the new `HttpProxy` instance + // for the specified `options.host` and `options.port` pair. + // + ['https', 'enable', 'forward'].forEach(function (key) { + if (options[key] !== false && self[key]) { + options[key] = self[key]; + } + }); + + this.proxies[key] = new HttpProxy(options); }; // @@ -49,7 +99,7 @@ RoutingProxy.prototype.add = function (options) { // for the specified `options.host` and `options.port` (if they exist). // RoutingProxy.prototype.remove = function (options) { - + var key = this._getKey(options); }; // @@ -129,8 +179,13 @@ RoutingProxy.prototype.proxyRequest = function (req, res, options) { } var key = options.host + ':' + options.port, - proxy = this.proxies[key] || this._addTarget(options); - + proxy; + + if (!this.proxies[key]) { + this.add(options); + } + + proxy = this.proxies[key]; proxy.proxyRequest(req, res, options.buffer); }; @@ -161,7 +216,32 @@ RoutingProxy.prototype.proxyWebSocketRequest = function (req, socket, head, opti } var key = options.host + ':' + options.port, - proxy = this.proxies[key] || this._addTarget(options); - + proxy; + + if (!this.proxies[key]) { + this.add(options); + } + + proxy = this.proxies[key]; proxy.proxyWebSocketRequest(req, socket, head, options.buffer); -}; \ No newline at end of file +}; + +// +// ### @private function _getKey (options) +// #### @options {Object} Options to extract the key from +// Ensures that the appropriate options are present in the `options` +// provided and responds with a string key representing the `host`, `port` +// combination contained within. +// +RoutingProxy.prototype._getKey = function (options) { + if (!options || ((!options.host || !options.port) + && (!options.target || !options.target.host || !options.target.port))) { + throw new Error('options.host and options.port or options.target are required.'); + return; + } + + return [ + options.host || options.target.host, + options.port || options.target.port + ].join(':'); +} \ No newline at end of file From 734769fa9b2c3054d45e33c3e552af80ce3f4740 Mon Sep 17 00:00:00 2001 From: indexzero Date: Sat, 10 Sep 2011 03:55:35 -0400 Subject: [PATCH 21/29] [test] Updated tests to reflect finalized API of the RoutingProxy --- test/helpers.js | 53 +++++++++++++------ test/http/http-proxy-test.js | 9 ++-- test/http/routing-proxy-test.js | 23 ++++---- test/websocket/websocket-proxy-test.js | 24 ++++----- .../websocket/websocket-routing-proxy-test.js | 18 +++---- 5 files changed, 72 insertions(+), 55 deletions(-) diff --git a/test/helpers.js b/test/helpers.js index 1bc9915..b1bbd22 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -5,15 +5,16 @@ * */ -var fs = require('fs'), +var assert = require('assert'), + fs = require('fs'), http = require('http'), https = require('https'), path = require('path'), - vows = require('vows'), - assert = require('assert'), + argv = require('optimist').argv, request = require('request'), - websocket = require('./../vendor/websocket'), - httpProxy = require('./../lib/node-http-proxy'); + vows = require('vows'), + websocket = require('../vendor/websocket'), + httpProxy = require('../lib/node-http-proxy'); var loadHttps = exports.loadHttps = function () { return { @@ -22,16 +23,34 @@ var loadHttps = exports.loadHttps = function () { }; }; -var TestRunner = exports.TestRunner = function (source, target) { - this.source = { protocol: source }, - this.target = { protocol: target }; +var parseProtocol = exports.parseProtocol = function () { + function setupProtocol (secure) { + return { + secure: secure, + protocols: { + http: secure ? 'https' : 'http', + ws: secure ? 'wss' : 'ws' + } + } + } + + return { + source: setupProtocol(argv.source === 'secure'), + target: setupProtocol(argv.target === 'secure') + }; +} + +var TestRunner = exports.TestRunner = function (options) { + options = options || {}; + this.source = options.source || {}; + this.target = options.target || {}; this.testServers = []; - if (source === 'https') { + if (this.source.secure) { this.source.https = loadHttps(); } - if (target === 'https') { + if (this.target.secure) { this.target.https = loadHttps(); } }; @@ -48,7 +67,7 @@ TestRunner.prototype.assertProxied = function (host, proxyPort, port, createProx options = { method: 'GET', - uri: self.source.protocol + '://localhost:' + proxyPort, + uri: self.source.protocols.http + '://localhost:' + proxyPort, headers: { host: host } @@ -79,7 +98,7 @@ TestRunner.prototype.assertProxied = function (host, proxyPort, port, createProx TestRunner.prototype.assertResponseCode = function (proxyPort, statusCode, createProxy) { var assertion = "should receive " + statusCode + " responseCode", - protocol = this.source.protocol; + protocol = this.source.protocols.http; var test = { topic: function () { @@ -240,11 +259,11 @@ TestRunner.prototype.startProxyServerWithTableAndLatency = function (port, laten // Initialize the nodeProxy and start proxying the request // var that = this, - proxy = new httpProxy.HttpProxy(merge({}, options, this.getOptions())), + proxy = new httpProxy.RoutingProxy(merge({}, options, this.getOptions())), proxyServer; var handler = function (req, res) { - var buffer = proxy.buffer(req); + var buffer = httpProxy.buffer(req); setTimeout(function () { proxy.proxyRequest(req, res, { buffer: buffer @@ -252,9 +271,9 @@ TestRunner.prototype.startProxyServerWithTableAndLatency = function (port, laten }, latency); }; - proxyServer = that.options.https - ? https.createServer(that.options.https, handler, that.options) - : http.createServer(handler, that.options); + proxyServer = this.source.https + ? https.createServer(this.source.https, handler) + : http.createServer(handler); proxyServer.listen(port, function () { that.testServers.push(proxyServer); diff --git a/test/http/http-proxy-test.js b/test/http/http-proxy-test.js index 5437261..c202f97 100644 --- a/test/http/http-proxy-test.js +++ b/test/http/http-proxy-test.js @@ -26,7 +26,6 @@ var assert = require('assert'), util = require('util'), - argv = require('optimist').argv, request = require('request'), vows = require('vows'), helpers = require('../helpers'); @@ -45,11 +44,11 @@ var badForwardOptions = { } }; -var protocol = argv.https ? 'https' : 'http', - target = argv.target ? argv.target : 'http', - runner = new helpers.TestRunner(protocol, target); +var options = helpers.parseProtocol(), + testName = [options.source.protocols.http, options.target.protocols.http].join('-to-'), + runner = new helpers.TestRunner(options); -vows.describe('node-http-proxy/' + protocol).addBatch({ +vows.describe('node-http-proxy/http-proxy/' + testName).addBatch({ "When using server created by httpProxy.createServer()": { "with no latency" : { "and a valid target server": runner.assertProxied('localhost', 8080, 8081, function (callback) { diff --git a/test/http/routing-proxy-test.js b/test/http/routing-proxy-test.js index 87cf6a8..1180fb7 100644 --- a/test/http/routing-proxy-test.js +++ b/test/http/routing-proxy-test.js @@ -14,8 +14,9 @@ var assert = require('assert'), vows = require('vows'), helpers = require('../helpers'); -var protocol = argv.https ? 'https' : 'http', - runner = new helpers.TestRunner(protocol), +var options = helpers.parseProtocol(), + testName = [options.source.protocols.http, options.target.protocols.http].join('-to-'), + runner = new helpers.TestRunner(options), routeFile = path.join(__dirname, 'config.json'); var fileOptions = { @@ -40,7 +41,7 @@ var hostnameOptions = { } }; -vows.describe('node-http-proxy/proxy-table/' + protocol).addBatch({ +vows.describe('node-http-proxy/routing-proxy/' + testName).addBatch({ "When using server created by httpProxy.createServer()": { "when passed a routing table": { "and routing by RegExp": { @@ -81,16 +82,14 @@ vows.describe('node-http-proxy/proxy-table/' + protocol).addBatch({ fs.writeFileSync(routeFile, JSON.stringify(config)); this.server.on('routes', function () { - var options = { - method: 'GET', - uri: protocol + '://localhost:8100', - headers: { - host: 'dynamic.com' - } - }; - runner.startTargetServer(8103, 'hello dynamic.com', function () { - request(options, that.callback); + request({ + method: 'GET', + uri: options.source.protocols.http + '://localhost:8100', + headers: { + host: 'dynamic.com' + } + }, that.callback); }); }); }, diff --git a/test/websocket/websocket-proxy-test.js b/test/websocket/websocket-proxy-test.js index bfc63e6..7d833f5 100644 --- a/test/websocket/websocket-proxy-test.js +++ b/test/websocket/websocket-proxy-test.js @@ -43,11 +43,11 @@ catch (ex) { process.exit(1); } -var protocol = argv.https ? 'https' : 'http', - wsprotocol = argv.https ? 'wss' : 'ws', - runner = new helpers.TestRunner(protocol); +var options = helpers.parseProtocol(), + testName = [options.source.protocols.ws, options.target.protocols.ws].join('-to-'), + runner = new helpers.TestRunner(options); -vows.describe('node-http-proxy/websocket/' + wsprotocol).addBatch({ +vows.describe('node-http-proxy/http-proxy/' + testName).addBatch({ "When using server created by httpProxy.createServer()": { "with no latency" : { "when an inbound message is sent from a WebSocket client": { @@ -58,8 +58,8 @@ vows.describe('node-http-proxy/websocket/' + wsprotocol).addBatch({ runner.webSocketTest({ io: io, host: 'localhost', - wsprotocol: wsprotocol, - protocol: protocol, + wsprotocol: options.source.protocols.ws, + protocol: options.source.protocols.http, ports: { target: 8130, proxy: 8131 @@ -85,7 +85,7 @@ vows.describe('node-http-proxy/websocket/' + wsprotocol).addBatch({ }, "the origin and sec-websocket-origin headers should match": function (err, msg, headers) { assert.isString(headers.response['sec-websocket-location']); - assert.isTrue(headers.response['sec-websocket-location'].indexOf(wsprotocol) !== -1); + assert.isTrue(headers.response['sec-websocket-location'].indexOf(options.source.protocols.ws) !== -1); assert.equal(headers.request.Origin, headers.response['sec-websocket-origin']); } }, @@ -97,8 +97,8 @@ vows.describe('node-http-proxy/websocket/' + wsprotocol).addBatch({ runner.webSocketTest({ io: io, host: 'localhost', - wsprotocol: wsprotocol, - protocol: protocol, + wsprotocol: options.source.protocols.ws, + protocol: options.source.protocols.http, ports: { target: 8132, proxy: 8133 @@ -126,8 +126,8 @@ vows.describe('node-http-proxy/websocket/' + wsprotocol).addBatch({ runner.webSocketTest({ io: io, host: 'localhost', - wsprotocol: wsprotocol, - protocol: protocol, + wsprotocol: options.source.protocols.ws, + protocol: options.source.protocols.http, ports: { target: 8134, proxy: 8135 @@ -154,7 +154,7 @@ vows.describe('node-http-proxy/websocket/' + wsprotocol).addBatch({ }, "the origin and sec-websocket-origin headers should match": function (err, msg, headers) { assert.isString(headers.response['sec-websocket-location']); - assert.isTrue(headers.response['sec-websocket-location'].indexOf(wsprotocol) !== -1); + assert.isTrue(headers.response['sec-websocket-location'].indexOf(options.source.protocols.ws) !== -1); assert.equal(headers.request.Origin, headers.response['sec-websocket-origin']); } } diff --git a/test/websocket/websocket-routing-proxy-test.js b/test/websocket/websocket-routing-proxy-test.js index a3edf05..2e5addf 100644 --- a/test/websocket/websocket-routing-proxy-test.js +++ b/test/websocket/websocket-routing-proxy-test.js @@ -30,7 +30,7 @@ var util = require('util'), colors = require('colors'), request = require('request'), vows = require('vows'), - websocket = require('../vendor/websocket'), + websocket = require('../../vendor/websocket'), helpers = require('../helpers'); try { @@ -43,11 +43,11 @@ catch (ex) { process.exit(1); } -var protocol = argv.https ? 'https' : 'http', - wsprotocol = argv.https ? 'wss' : 'ws', - runner = new helpers.TestRunner(protocol); +var options = helpers.parseProtocol(), + testName = [options.source.protocols.ws, options.target.protocols.ws].join('-to-'), + runner = new helpers.TestRunner(options); -vows.describe('node-http-proxy/websocket/' + wsprotocol).addBatch({ +vows.describe('node-http-proxy/routing-proxy/' + testName).addBatch({ "When using server created by httpProxy.createServer()": { "using proxy table with no latency": { "when an inbound message is sent from a WebSocket client": { @@ -58,9 +58,9 @@ vows.describe('node-http-proxy/websocket/' + wsprotocol).addBatch({ runner.webSocketTestWithTable({ io: io, host: 'localhost', - wsprotocol: wsprotocol, - protocol: protocol, - router: {'localhost':'localhost:8230'}, + wsprotocol: options.source.protocols.ws, + protocol: options.source.protocols.http, + router: { 'localhost' : 'localhost:8230' }, ports: { target: 8230, proxy: 8231 @@ -86,7 +86,7 @@ vows.describe('node-http-proxy/websocket/' + wsprotocol).addBatch({ }, "the origin and sec-websocket-origin headers should match": function (err, msg, headers) { assert.isString(headers.response['sec-websocket-location']); - assert.isTrue(headers.response['sec-websocket-location'].indexOf(wsprotocol) !== -1); + assert.isTrue(headers.response['sec-websocket-location'].indexOf(options.source.protocols.ws) !== -1); assert.equal(headers.request.Origin, headers.response['sec-websocket-origin']); } } From 6e1ade0bb8174b744abb58df72b098bd96134ca4 Mon Sep 17 00:00:00 2001 From: indexzero Date: Sat, 10 Sep 2011 03:55:53 -0400 Subject: [PATCH 22/29] [dist] Update scripts in package.json --- package.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index ef6e642..d10d8a9 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,10 @@ }, "main": "./lib/node-http-proxy", "bin": { "node-http-proxy": "./bin/node-http-proxy" }, - "scripts": { "test": "vows test/*-test.js --spec && vows test/*-test.js --spec --https" }, + "scripts": { + "test": "npm run-script test-http && npm run-script test-https", + "test-http": "vows --spec && vows --spec --target=secure", + "test-https": "vows --spec --source=secure && vows --spec --source=secure --target=secure" + }, "engines": { "node": "0.4.x || 0.5.x" } } From 0ba5023e82fe8a08ed55194644d147c323368f41 Mon Sep 17 00:00:00 2001 From: indexzero Date: Sat, 10 Sep 2011 03:56:18 -0400 Subject: [PATCH 23/29] [doc] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e270ff7..4242f62 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ httpProxy.createServer(function (req, res, proxy) { // Buffer the request so that `data` and `end` events // are not lost during async operation(s). // - var buffer = proxy.buffer(req); + var buffer = httpProxy.buffer(req); // // Wait for two seconds then respond: this simulates From 6c1c5544515bf17f0e6ed3588e16ae1a75f8a25b Mon Sep 17 00:00:00 2001 From: indexzero Date: Sat, 10 Sep 2011 03:56:57 -0400 Subject: [PATCH 24/29] [dist] Update .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index aa98793..468b525 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -test/config.json +config.json node_modules/ npm-debug.log From 2cd8256c4d6089409f603655ea3b3a5ccf1fb065 Mon Sep 17 00:00:00 2001 From: indexzero Date: Sat, 10 Sep 2011 03:57:26 -0400 Subject: [PATCH 25/29] [minor] Small update to bin/node-http-proxy --- bin/node-http-proxy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/node-http-proxy b/bin/node-http-proxy index c165b1e..53139d2 100755 --- a/bin/node-http-proxy +++ b/bin/node-http-proxy @@ -4,7 +4,7 @@ var path = require('path'), fs = require('fs'), util = require('util'), argv = require('optimist').argv, - httpProxy = require('./../lib/node-http-proxy'); + httpProxy = require('../lib/node-http-proxy'); var help = [ "usage: node-http-proxy [options] ", From 13eaec55dc50e2aae164cb8adaa0f1a3c5a66c68 Mon Sep 17 00:00:00 2001 From: indexzero Date: Sat, 10 Sep 2011 04:04:31 -0400 Subject: [PATCH 26/29] [doc] Updated examples --- examples/helpers/store.js | 9 +++----- examples/http/basic-proxy.js | 19 +++++++-------- examples/http/concurrent-proxy.js | 23 ++++++++++--------- examples/http/custom-proxy-error.js | 9 ++------ examples/http/forward-proxy.js | 4 ++-- examples/http/latent-proxy.js | 4 ++-- examples/http/proxy-https-to-http.js | 2 +- examples/http/proxy-https-to-https.js | 2 +- examples/http/proxy-table.js | 2 +- examples/http/standalone-proxy.js | 4 ++-- examples/middleware/gzip-middleware.js | 2 +- examples/middleware/jsonp-middleware.js | 2 +- examples/middleware/url-middleware.js | 2 +- examples/middleware/url-middleware2.js | 11 ++++----- examples/websocket/latent-websocket-proxy.js | 4 ++-- .../websocket/standalone-websocket-proxy.js | 2 +- examples/websocket/websocket-proxy.js | 2 +- 17 files changed, 48 insertions(+), 55 deletions(-) diff --git a/examples/helpers/store.js b/examples/helpers/store.js index 8144b13..62a627e 100644 --- a/examples/helpers/store.js +++ b/examples/helpers/store.js @@ -1,5 +1,4 @@ -module.exports = Store // // just to make these example a little bit interesting, // make a little key value store with an http interface @@ -20,12 +19,10 @@ module.exports = Store // // TODO: cached map-reduce views and auto-magic sharding. // +var Store = module.exports = function Store () { + this.store = {}; +}; - - -function Store () { - this.store = {} -} Store.prototype = { get: function (key) { return this.store[key] diff --git a/examples/http/basic-proxy.js b/examples/http/basic-proxy.js index 5827625..b890e69 100644 --- a/examples/http/basic-proxy.js +++ b/examples/http/basic-proxy.js @@ -27,16 +27,17 @@ var util = require('util'), colors = require('colors'), http = require('http'), - httpProxy = require('./../lib/node-http-proxy'); + httpProxy = require('../../lib/node-http-proxy'); + +var welcome = [ + '# # ##### ##### ##### ##### ##### #### # # # #', + '# # # # # # # # # # # # # # # # ', + '###### # # # # ##### # # # # # # ## # ', + '# # # # ##### ##### ##### # # ## # ', + '# # # # # # # # # # # # # ', + '# # # # # # # # #### # # # ' +].join('\n'); -// ascii art from http://github.com/marak/asciimo -var welcome = '\ -# # ##### ##### ##### ##### ##### #### # # # # \n\ -# # # # # # # # # # # # # # # # \n\ -###### # # # # ##### # # # # # # ## # \n\ -# # # # ##### ##### ##### # # ## # \n\ -# # # # # # # # # # # # # \n\ -# # # # # # # # #### # # # \n'; util.puts(welcome.rainbow.bold); // diff --git a/examples/http/concurrent-proxy.js b/examples/http/concurrent-proxy.js index 4bf5673..230dfc6 100644 --- a/examples/http/concurrent-proxy.js +++ b/examples/http/concurrent-proxy.js @@ -27,7 +27,7 @@ var util = require('util'), colors = require('colors'), http = require('http'), - httpProxy = require('./../lib/node-http-proxy'); + httpProxy = require('../../lib/node-http-proxy'); // // Basic Http Proxy Server @@ -42,23 +42,24 @@ httpProxy.createServer(9000, 'localhost').listen(8000); // -var connections = [] - , go +var connections = [], + go; http.createServer(function (req, res) { - - connections.push (function (){ + connections.push(function () { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.write('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2)); res.end(); - }) - process.stdout.write(connections.length + ', ') + }); + + process.stdout.write(connections.length + ', '); + if (connections.length > 110 || go) { - go = true - while(connections.length) - connections.shift()() + go = true; + while (connections.length) { + connections.shift()(); + } } - }).listen(9000); util.puts('http proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8000'.yellow); diff --git a/examples/http/custom-proxy-error.js b/examples/http/custom-proxy-error.js index 0fd90ad..dc439ea 100644 --- a/examples/http/custom-proxy-error.js +++ b/examples/http/custom-proxy-error.js @@ -27,17 +27,12 @@ var util = require('util'), colors = require('colors'), http = require('http'), - httpProxy = require('./../lib/node-http-proxy'); + httpProxy = require('../../lib/node-http-proxy'); // // Http Proxy Server with Latency // -var server = httpProxy.createServer(function (req, res, proxy) { - proxy.proxyRequest(req, res, { - port: 9000, - host: 'localhost' - }); -}) +var server = httpProxy.createServer(9000, 'localhost'); // // Tell the server to listen on port 8002 diff --git a/examples/http/forward-proxy.js b/examples/http/forward-proxy.js index 667d672..ecc20fd 100644 --- a/examples/http/forward-proxy.js +++ b/examples/http/forward-proxy.js @@ -27,7 +27,7 @@ var util = require('util'), colors = require('colors'), http = require('http'), - httpProxy = require('./../lib/node-http-proxy'); + httpProxy = require('../../lib/node-http-proxy'); // // Setup proxy server with forwarding @@ -60,4 +60,4 @@ http.createServer(function (req, res) { util.puts('http proxy server '.blue + 'started '.green.bold + 'on port '.blue + '8003 '.yellow + 'with forward proxy'.magenta.underline); util.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '9000 '.yellow); -util.puts('http forward server '.blue + 'started '.green.bold + 'on port '.blue + '9001 '.yellow); +util.puts('http forward server '.blue + 'started '.green.bold + 'on port '.blue + '9001 '.yellow); \ No newline at end of file diff --git a/examples/http/latent-proxy.js b/examples/http/latent-proxy.js index f2e51f8..5da5598 100644 --- a/examples/http/latent-proxy.js +++ b/examples/http/latent-proxy.js @@ -27,13 +27,13 @@ var util = require('util'), colors = require('colors'), http = require('http'), - httpProxy = require('./../lib/node-http-proxy'); + httpProxy = require('../../lib/node-http-proxy'); // // Http Proxy Server with Latency // httpProxy.createServer(function (req, res, proxy) { - var buffer = proxy.buffer(req); + var buffer = httpProxy.buffer(req); setTimeout(function() { proxy.proxyRequest(req, res, { port: 9000, diff --git a/examples/http/proxy-https-to-http.js b/examples/http/proxy-https-to-http.js index 059c311..7018301 100644 --- a/examples/http/proxy-https-to-http.js +++ b/examples/http/proxy-https-to-http.js @@ -28,7 +28,7 @@ var https = require('https'), http = require('http'), util = require('util'), colors = require('colors'), - httpProxy = require('./../lib/node-http-proxy'), + httpProxy = require('../../lib/node-http-proxy'), helpers = require('./../test/helpers'); var opts = helpers.loadHttps(); diff --git a/examples/http/proxy-https-to-https.js b/examples/http/proxy-https-to-https.js index d93cab8..cde2e12 100644 --- a/examples/http/proxy-https-to-https.js +++ b/examples/http/proxy-https-to-https.js @@ -28,7 +28,7 @@ var https = require('https'), http = require('http'), util = require('util'), colors = require('colors'), - httpProxy = require('./../lib/node-http-proxy'), + httpProxy = require('../../lib/node-http-proxy'), helpers = require('./../test/helpers'); var opts = helpers.loadHttps(); diff --git a/examples/http/proxy-table.js b/examples/http/proxy-table.js index 5036dcd..55d97ae 100644 --- a/examples/http/proxy-table.js +++ b/examples/http/proxy-table.js @@ -27,7 +27,7 @@ var util = require('util'), colors = require('colors'), http = require('http'), - httpProxy = require('./../lib/node-http-proxy'); + httpProxy = require('../../lib/node-http-proxy'); // // Http Proxy Server with Proxy Table diff --git a/examples/http/standalone-proxy.js b/examples/http/standalone-proxy.js index 768d0b7..e39d4b0 100644 --- a/examples/http/standalone-proxy.js +++ b/examples/http/standalone-proxy.js @@ -27,14 +27,14 @@ var util = require('util'), colors = require('colors'), http = require('http'), - httpProxy = require('./../lib/node-http-proxy'); + httpProxy = require('../../lib/node-http-proxy'); // // Http Server with proxyRequest Handler and Latency // var proxy = new httpProxy.HttpProxy(); http.createServer(function (req, res) { - var buffer = proxy.buffer(req); + var buffer = httpProxy.buffer(req); setTimeout(function() { proxy.proxyRequest(req, res, { port: 9000, diff --git a/examples/middleware/gzip-middleware.js b/examples/middleware/gzip-middleware.js index 856e2f6..29097ec 100644 --- a/examples/middleware/gzip-middleware.js +++ b/examples/middleware/gzip-middleware.js @@ -27,7 +27,7 @@ var util = require('util'), colors = require('colors'), http = require('http'), - httpProxy = require('./../lib/node-http-proxy'); + httpProxy = require('../../lib/node-http-proxy'); // // Basic Http Proxy Server diff --git a/examples/middleware/jsonp-middleware.js b/examples/middleware/jsonp-middleware.js index 11cc658..4cc31dd 100644 --- a/examples/middleware/jsonp-middleware.js +++ b/examples/middleware/jsonp-middleware.js @@ -27,4 +27,4 @@ http.createServer(new Store().handler()).listen(7531) require('http-proxy').createServer( require('connect-jsonp')(true), 'localhost', 7531 -).listen(1337) +).listen(1337) \ No newline at end of file diff --git a/examples/middleware/url-middleware.js b/examples/middleware/url-middleware.js index 79fdf32..06fd478 100644 --- a/examples/middleware/url-middleware.js +++ b/examples/middleware/url-middleware.js @@ -37,7 +37,7 @@ httpProxy.createServer( // This is where our middlewares go, with any options desired - in this case, // the list of routes/URLs and their destinations. // - require('proxy-by-url')({ + require('proxy-by-url')({ '/hello': { port: 9000, host: 'localhost' }, '/charlie': { port: 80, host: 'charlieistheman.com' }, '/google': { port: 80, host: 'google.com' } diff --git a/examples/middleware/url-middleware2.js b/examples/middleware/url-middleware2.js index 95dfef4..fcf3df2 100644 --- a/examples/middleware/url-middleware2.js +++ b/examples/middleware/url-middleware2.js @@ -19,12 +19,11 @@ httpProxy.createServer( // // Target Http Server (to listen for requests on 'localhost') // -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); +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); // And finally, some colored startup output. util.puts('http proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8000'.yellow); diff --git a/examples/websocket/latent-websocket-proxy.js b/examples/websocket/latent-websocket-proxy.js index 2b9f0f0..3598ca9 100644 --- a/examples/websocket/latent-websocket-proxy.js +++ b/examples/websocket/latent-websocket-proxy.js @@ -28,7 +28,7 @@ var sys = require('sys'), http = require('http'), colors = require('colors'), websocket = require('./../vendor/websocket'), - httpProxy = require('./../lib/node-http-proxy'); + httpProxy = require('../../lib/node-http-proxy'); try { var utils = require('socket.io/lib/socket.io/utils'), @@ -80,7 +80,7 @@ var proxyServer = http.createServer(function (req, res) { // WebSocket requests as well. // proxyServer.on('upgrade', function (req, socket, head) { - var buffer = proxy.buffer(socket); + var buffer = httpProxy.buffer(socket); setTimeout(function () { proxy.proxyWebSocketRequest(req, socket, head, { diff --git a/examples/websocket/standalone-websocket-proxy.js b/examples/websocket/standalone-websocket-proxy.js index e750c5a..fdefa6d 100644 --- a/examples/websocket/standalone-websocket-proxy.js +++ b/examples/websocket/standalone-websocket-proxy.js @@ -28,7 +28,7 @@ var sys = require('sys'), http = require('http'), colors = require('colors'), websocket = require('./../vendor/websocket'), - httpProxy = require('./../lib/node-http-proxy'); + httpProxy = require('../../lib/node-http-proxy'); try { var utils = require('socket.io/lib/socket.io/utils'), diff --git a/examples/websocket/websocket-proxy.js b/examples/websocket/websocket-proxy.js index d7a8efa..a1e49c6 100644 --- a/examples/websocket/websocket-proxy.js +++ b/examples/websocket/websocket-proxy.js @@ -28,7 +28,7 @@ var sys = require('sys'), http = require('http'), colors = require('colors'), websocket = require('./../vendor/websocket'), - httpProxy = require('./../lib/node-http-proxy'); + httpProxy = require('../../lib/node-http-proxy'); try { var utils = require('socket.io/lib/socket.io/utils'), From d6c543691b6f665cfd0a0ea063de2238574bc113 Mon Sep 17 00:00:00 2001 From: indexzero Date: Sat, 10 Sep 2011 04:58:19 -0400 Subject: [PATCH 27/29] [doc] Document `setMaxSockets`. Fixes #81 --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 4242f62..7a852f3 100644 --- a/README.md +++ b/README.md @@ -357,6 +357,13 @@ server.on('upgrade', function(req, socket, head) { }); ``` +### Configuring your Socket limits + +By default, `node-http-proxy` will set a 100 socket limit for all `host:port` proxy targets. If you wish to change this you can two it in two ways: + +1. By passing the `maxSockets` option to `httpProxy.createServer()` +2. By calling `httpProxy.setMaxSockets(n)`, where `n` is the number of sockets you with to use. + ## Using node-http-proxy from the command line When you install this package with npm, a node-http-proxy binary will become available to you. Using this binary is easy with some simple options: From 0eb4917ded952a469057d0d925c1393fc3b7af02 Mon Sep 17 00:00:00 2001 From: indexzero Date: Sat, 10 Sep 2011 06:57:51 -0400 Subject: [PATCH 28/29] [fix] Add `x-forward-*` headers for WebSocket requests. Closes #74 --- lib/node-http-proxy/http-proxy.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/node-http-proxy/http-proxy.js b/lib/node-http-proxy/http-proxy.js index 165fccf..9b4ef59 100644 --- a/lib/node-http-proxy/http-proxy.js +++ b/lib/node-http-proxy/http-proxy.js @@ -351,6 +351,20 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, buffer) // return socket.destroy(); } + + // + // Add common proxy headers to the request so that they can + // be availible to the proxy target server: + // + // * `x-forwarded-for`: IP Address of the original request + // * `x-forwarded-proto`: Protocol of the original request + // * `x-forwarded-port`: Port of the original request. + // + if (this.enable.xforward && req.connection && req.connection.socket) { + req.headers['x-forwarded-for'] = req.connection.remoteAddress || req.connection.socket.remoteAddress; + req.headers['x-forwarded-port'] = req.connection.remotePort || req.connection.socket.remotePort; + req.headers['x-forwarded-proto'] = req.connection.pair ? 'https' : 'http'; + } // // Helper function for setting appropriate socket values: From 0182ba37cd4c618cd50947ea2addef823349e49f Mon Sep 17 00:00:00 2001 From: indexzero Date: Sat, 10 Sep 2011 07:01:32 -0400 Subject: [PATCH 29/29] [dist] Version bump. 0.7.0 --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index d10d8a9..0aca902 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,13 @@ { "name": "http-proxy", "description": "A full-featured http reverse proxy for node.js", - "version": "0.6.3", + "version": "0.7.0", "author": "Charlie Robbins ", "contributors": [ { "name": "Mikeal Rogers", "email": "mikeal.rogers@gmail.com" }, { "name": "Marak Squires", "email": "marak.squires@gmail.com" }, - { "name": "Fedor Indutny", "email": "fedor.indutny@gmail.com" } + { "name": "Fedor Indutny", "email": "fedor.indutny@gmail.com" }, + { "name": "Dominic Tarr", "email": "dominic@nodejitsu.com" } ], "repository": { "type": "git",