mirror of
https://github.com/http-party/node-http-proxy.git
synced 2025-12-08 20:59:18 +00:00
error-prone in-line method. _getKey() will look for options.target as well as options.host:options.port, and so is useful for a segmented proxy server where the destination proxies are not constructed before first references.
251 lines
7.5 KiB
JavaScript
251 lines
7.5 KiB
JavaScript
/*
|
||
* 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,
|
||
ProxyTable = require('./proxy-table').ProxyTable;
|
||
|
||
//
|
||
// ### 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);
|
||
|
||
var self = this;
|
||
options = options || {};
|
||
|
||
if (options.router) {
|
||
this.proxyTable = new ProxyTable(options);
|
||
this.proxyTable.on('routes', function (routes) {
|
||
self.emit('routes', routes);
|
||
});
|
||
}
|
||
|
||
//
|
||
// Create a set of `HttpProxy` objects to be used later on calls
|
||
// 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;
|
||
};
|
||
|
||
|
||
//
|
||
// 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) {
|
||
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);
|
||
this.proxies[key].on('proxyError', this.emit.bind(this, 'proxyError'));
|
||
this.proxies[key].on('webSocketProxyError', this.emit.bind(this, 'webSocketProxyError'));
|
||
};
|
||
|
||
//
|
||
// ### 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) {
|
||
var key = this._getKey(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 = this._getKey(options),
|
||
proxy;
|
||
|
||
if (!this.proxies[key]) {
|
||
this.add(options);
|
||
|
||
}
|
||
|
||
proxy = this.proxies[key];
|
||
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 = this._getKey(options),
|
||
proxy;
|
||
|
||
if (!this.proxies[key]) {
|
||
this.add(options);
|
||
}
|
||
|
||
proxy = this.proxies[key];
|
||
proxy.proxyWebSocketRequest(req, socket, head, options.buffer);
|
||
};
|
||
|
||
//
|
||
// ### @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(':');
|
||
}
|