From cfddd12e821bd6b07ff2dbf0aa543ddfc3664dca Mon Sep 17 00:00:00 2001 From: indexzero Date: Sat, 16 Apr 2011 23:08:40 -0400 Subject: [PATCH] [api] Update `.proxyRequest()` and `.proxyWebSocketRequest()` APIs to take an options hash instead of a set of arguments. Add HTTPS support. --- examples/latent-proxy.js | 6 +- examples/standalone-proxy.js | 6 +- lib/node-http-proxy.js | 186 +++++++++++++++++++++++------------ test/helpers.js | 12 ++- 4 files changed, 141 insertions(+), 69 deletions(-) diff --git a/examples/latent-proxy.js b/examples/latent-proxy.js index ee7bf6705..da2aa5de6 100644 --- a/examples/latent-proxy.js +++ b/examples/latent-proxy.js @@ -35,7 +35,11 @@ var util = require('util'), httpProxy.createServer(function (req, res, proxy) { var buffer = proxy.buffer(req); setTimeout(function() { - proxy.proxyRequest(req, res, 9000, 'localhost', buffer); + proxy.proxyRequest(req, res, { + port: 9000, + host: 'localhost', + buffer: buffer + }); }, 200) }).listen(8002); diff --git a/examples/standalone-proxy.js b/examples/standalone-proxy.js index 06e98c897..b7327602b 100644 --- a/examples/standalone-proxy.js +++ b/examples/standalone-proxy.js @@ -36,7 +36,11 @@ var proxy = new httpProxy.HttpProxy(); http.createServer(function (req, res) { var buffer = proxy.buffer(req); setTimeout(function() { - proxy.proxyRequest(req, res, 9000, 'localhost', buffer); + proxy.proxyRequest(req, res, { + port: 9000, + host: 'localhost', + buffer: buffer + }); }, 200); }).listen(8004); diff --git a/lib/node-http-proxy.js b/lib/node-http-proxy.js index 68bc9e796..06b992b16 100644 --- a/lib/node-http-proxy.js +++ b/lib/node-http-proxy.js @@ -26,6 +26,7 @@ var util = require('util'), http = require('http'), + https = require('https'), events = require('events'), ProxyTable = require('./proxy-table').ProxyTable, maxSockets = 100; @@ -36,21 +37,46 @@ var util = require('util'), exports.version = [0, 4, 2]; // -// ### function _getAgent (host, port) +// ### 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` module // and sets the `maxSockets` property appropriately. // -function _getAgent (host, port) { - // - // TODO (indexzero): Make this configurable for http / https - // - var agent = http.getAgent(host, port); +function _getAgent (host, port, secure) { + var agent = !secure ? http.getAgent(host, port) : https.getAgent({ + host: host, + port: port + }); + agent.maxSockets = maxSockets; return agent; } +// +// ### function _getProtocol (outgoing, https) +// #### @outgoing {Object} Outgoing request options +// #### @secure {Object|boolean} Settings for `https` +// 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 (outgoing, secure) { + var protocol = secure ? https : http; + + if (typeof secure === 'object') { + ['ca', 'cert', 'key'].forEach(function (prop) { + if (secure[prop]) { + outgoing[prop] = secure[prop]; + } + }) + } + + return protocol; +} + // // ### function getMaxSockets () // Returns the maximum number of sockets @@ -104,20 +130,34 @@ exports.createServer = function () { proxy = new HttpProxy(options); server = http.createServer(function (req, res) { - proxy.emit('request', req, res, req.headers.host, req.url); - - // If we were passed a callback to process the request - // or response in some way, then call it. if (callback) { + // + // If we were passed a callback to process the request + // or response in some way, then call it. + // callback(req, res, proxy); } else if (port && host) { - proxy.proxyRequest(req, res, port, host); + // + // If we have a target host and port for the request + // then proxy to the specified location. + // + proxy.proxyRequest(req, res, { + port: port, + host: host + }); } else if (proxy.proxyTable) { + // + // If the proxy is configured with a ProxyTable + // instance then use that before failing. + // proxy.proxyRequest(req, res); } else { + // + // Otherwise this server is improperly configured. + // throw new Error('Cannot proxy without port, host, or router.') } }); @@ -136,10 +176,19 @@ exports.createServer = function () { server.on('upgrade', function(req, socket, head) { // Tunnel websocket requests too - proxy.proxyWebSocketRequest(req, socket, head, port, host); + proxy.proxyWebSocketRequest(req, socket, head, { + port: port, + host: host + }); }); } - + + // + // Set the proxy on the server so it is available + // to the consumer of the server + // + server.proxy = proxy; + return server; }; @@ -165,11 +214,12 @@ exports.createServer = function () { var HttpProxy = exports.HttpProxy = function (options) { events.EventEmitter.call(this); - options = options || {}; - this.options = options; + var self = this; + options = options || {}; + this.forward = options.forward; + this.https = options.https; if (options.router) { - var self = this; this.proxyTable = new ProxyTable(options.router, options.silent, options.hostnameOnly); this.proxyTable.on('routes', function (routes) { self.emit('routes', routes); @@ -196,7 +246,7 @@ util.inherits(HttpProxy, events.EventEmitter); // // __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. +// However, this is not a big leap from the implementation in node-http-proxy < 0.4.0. // This simply chooses to manage the scope of the events on a new Object literal as opposed to // [on the HttpProxy instance](https://github.com/nodejitsu/node-http-proxy/blob/v0.3.1/lib/node-http-proxy.js#L154). // @@ -238,12 +288,17 @@ HttpProxy.prototype.close = function () { // ### function proxyRequest (req, res, [port, host, paused]) // #### @req {ServerRequest} Incoming HTTP Request to proxy. // #### @res {ServerResponse} Outgoing HTTP Request to write proxied data to. -// #### @port {number} **Optional** Port to use on the proxy target host. -// #### @host {string} **Optional** Host of the proxy target. -// #### @buffer {Object} **Optional** Result from `httpProxy.buffer(req)` +// #### @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. // -HttpProxy.prototype.proxyRequest = function (req, res, port, host, buffer) { - var self = this, reverseProxy, location, errState = false, opts; +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. + options = options || {}; // // Check the proxy table for this instance to see if we need @@ -251,7 +306,7 @@ HttpProxy.prototype.proxyRequest = function (req, res, port, host, buffer) { // always ignore the proxyTable if an explicit `port` and `host` // arguments are supplied to `proxyRequest`. // - if (this.proxyTable && !host) { + if (this.proxyTable && !options.host) { location = this.proxyTable.getProxyLocation(req); // @@ -267,13 +322,12 @@ HttpProxy.prototype.proxyRequest = function (req, res, port, host, buffer) { // When using the ProxyTable in conjunction with an HttpProxy instance // only the following arguments are valid: // - // * `proxy.proxyRequest(req, res, port, host, buffer)`: This will be skipped - // * `proxy.proxyRequest(req, res, buffer)`: Buffer will get updated appropriately - // * `proxy.proxyRequest(req, res)`: No effect `undefined = undefined` + // * `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. // - buffer = port; - port = location.port; - host = location.host; + options.port = location.port; + options.host = location.host; } // @@ -284,14 +338,14 @@ HttpProxy.prototype.proxyRequest = function (req, res, port, host, buffer) { // // Emit the `start` event indicating that we have begun the proxy operation. // - this.emit('start', req, res, host, port); + this.emit('start', req, res, options); // // If forwarding is enabled for this instance, foward proxy the - // specified request to the address provided in `this.options.forward` + // specified request to the address provided in `this.forward` // - if (this.options.forward) { - this.emit('forward', req, res, this.options.forward.host, this.options.forward.port); + if (this.forward) { + this.emit('forward', req, res, this.forward); this._forwardRequest(req); } @@ -312,10 +366,10 @@ HttpProxy.prototype.proxyRequest = function (req, res, port, host, buffer) { res.end(); } - var opts = { - host: host, - port: port, - agent: _getAgent(host, port), + outgoing = { + host: options.host, + port: options.port, + agent: _getAgent(options.host, options.port, options.https || this.https), method: req.method, path: req.url, headers: req.headers @@ -323,10 +377,12 @@ HttpProxy.prototype.proxyRequest = function (req, res, port, host, buffer) { // Force the `connection` header to be 'close' until // node.js core re-implements 'keep-alive'. - opts.headers['connection'] = 'close'; + outgoing.headers['connection'] = 'close'; + + protocol = _getProtocol(outgoing, options.https || this.https); // Open new HTTP request to internal resource with will act as a reverse proxy pass - reverseProxy = http.request(opts, function (response) { + reverseProxy = protocol.request(outgoing, function (response) { // Process the `reverseProxy` `response` when it's received. if (response.headers.connection) { @@ -388,8 +444,8 @@ HttpProxy.prototype.proxyRequest = function (req, res, port, host, buffer) { }); // If we have been passed buffered data, resume it. - if (buffer && !errState) { - buffer.resume(); + if (options.buffer && !errState) { + options.buffer.resume(); } }; @@ -397,18 +453,18 @@ HttpProxy.prototype.proxyRequest = function (req, res, port, host, buffer) { // ### @private function _forwardRequest (req) // #### @req {ServerRequest} Incoming HTTP Request to proxy. // Forwards the specified `req` to the location specified -// by `this.options.forward` ignoring errors and the subsequent response. +// by `this.forward` ignoring errors and the subsequent response. // HttpProxy.prototype._forwardRequest = function (req) { - var self = this, port, host, forwardProxy, opts; + var self = this, port, host, outgoing, protocol, forwardProxy; - port = this.options.forward.port; - host = this.options.forward.host; + port = this.forward.port; + host = this.forward.host; - opts = { + outgoing = { host: host, port: port, - agent: _getAgent(host, port), + agent: _getAgent(host, port, this.forward.https), method: req.method, path: req.url, headers: req.headers @@ -416,10 +472,12 @@ HttpProxy.prototype._forwardRequest = function (req) { // Force the `connection` header to be 'close' until // node.js core re-implements 'keep-alive'. - opts.headers['connection'] = 'close'; + outgoing.headers['connection'] = 'close'; + + protocol = _getProtocol(outgoing, this.forward.https); // Open new HTTP request to internal resource with will act as a reverse proxy pass - forwardProxy = http.request(opts, function (response) { + 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. @@ -444,8 +502,8 @@ HttpProxy.prototype._forwardRequest = function (req) { }); }; -HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, port, host, buffer) { - var self = this, CRLF = '\r\n'; +HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, options) { + var self = this, outgoing, errState = false, CRLF = '\r\n'; // WebSocket requests has method = GET if (req.method !== 'GET' || req.headers.upgrade.toLowerCase() !== 'websocket') { @@ -519,24 +577,24 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, port, h _socket(socket); // Remote host address - var remoteHost = host + (port - 80 === 0 ? '' : ':' + port), - agent = _getAgent(host, port); + var agent = _getAgent(options.host, options.port), + remoteHost = options.host + (options.port - 80 === 0 ? '' : ':' + options.port); // Change headers - req.headers.host = remoteHost; - req.headers.origin = 'http://' + host; + req.headers.host = remoteHost; + req.headers.origin = 'http://' + options.host; - var opts = { - host: host, - port: port, + outgoing = { + host: options.host, + port: options.port, agent: agent, method: 'GET', path: req.url, headers: req.headers - } + }; // Make the outgoing WebSocket request - var request = http.request(opts, function () { }); + var request = http.request(outgoing, function () { }); // Not disconnect on update agent.on('upgrade', function(request, remoteSocket, head) { @@ -567,8 +625,8 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, port, h data = data.slice(Buffer.byteLength(sdata), data.length); // Replace host and origin - sdata = sdata.replace(remoteHost, host) - .replace(remoteHost, host); + sdata = sdata.replace(remoteHost, options.host) + .replace(remoteHost, options.host); try { // Write printable @@ -602,7 +660,7 @@ HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, port, h } // If we have been passed buffered data, resume it. - if (buffer && !errState) { - buffer.resume(); + if (options.buffer && !errState) { + options.buffer.resume(); } }; \ No newline at end of file diff --git a/test/helpers.js b/test/helpers.js index 85782e70b..39fca6d98 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -100,10 +100,14 @@ 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 data = proxy.buffer(req); + var buffer = proxy.buffer(req); setTimeout(function () { - proxy.proxyRequest(req, res, targetPort, host, data); + proxy.proxyRequest(req, res, { + port: targetPort, + host: host, + buffer: buffer + }); }, latency); }); @@ -135,7 +139,9 @@ TestRunner.prototype.startProxyServerWithTableAndLatency = function (port, laten proxyServer = http.createServer(function (req, res) { var buffer = proxy.buffer(req); setTimeout(function () { - proxy.proxyRequest(req, res, buffer); + proxy.proxyRequest(req, res, { + buffer: buffer + }); }, latency); });