mirror of
https://github.com/http-party/node-http-proxy.git
synced 2025-12-08 20:59:18 +00:00
295 lines
8.5 KiB
JavaScript
295 lines
8.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;
|
|
this.changeOrigin = options.changeOrigin || false;
|
|
|
|
//
|
|
// Listen for 'newListener' events so that we can bind 'proxyError'
|
|
// listeners to each HttpProxy's 'proxyError' event.
|
|
//
|
|
this.on('newListener', function (evt) {
|
|
if (evt === 'proxyError' || evt === 'webSocketProxyError') {
|
|
Object.keys(self.proxies).forEach(function (key) {
|
|
self.proxies[key].on(evt, self.emit.bind(self, evt));
|
|
});
|
|
}
|
|
});
|
|
};
|
|
|
|
|
|
//
|
|
// 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.socketPath = options.target.socketPath || options.socketPath;
|
|
options.target.https = this.target && this.target.https ||
|
|
options.target && options.target.https;
|
|
|
|
//
|
|
// Setup options to pass-thru to the new `HttpProxy` instance
|
|
// for the specified `options.host` and `options.port` pair.
|
|
//
|
|
['https', 'enable', 'forward', 'changeOrigin'].forEach(function (key) {
|
|
if (options[key] !== false && self[key]) {
|
|
options[key] = self[key];
|
|
}
|
|
});
|
|
|
|
this.proxies[key] = new HttpProxy(options);
|
|
|
|
if (this.listeners('proxyError').length > 0) {
|
|
this.proxies[key].on('proxyError', this.emit.bind(this, 'proxyError'));
|
|
}
|
|
|
|
if (this.listeners('webSocketProxyError').length > 0) {
|
|
this.proxies[key].on('webSocketProxyError', this.emit.bind(this, 'webSocketProxyError'));
|
|
}
|
|
|
|
[
|
|
'start',
|
|
'forward',
|
|
'end',
|
|
'websocket:start',
|
|
'websocket:end',
|
|
'websocket:incoming',
|
|
'websocket:outgoing'
|
|
].forEach(function(event) {
|
|
this.proxies[key].on(event, this.emit.bind(this, event));
|
|
}, this);
|
|
};
|
|
|
|
//
|
|
// ### 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),
|
|
proxy = this.proxies[key];
|
|
|
|
delete this.proxies[key];
|
|
return proxy;
|
|
};
|
|
|
|
//
|
|
// ### 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 || {};
|
|
|
|
var location;
|
|
|
|
//
|
|
// 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.target && this.target.https)
|
|
|| (location && location.protocol === 'https')) {
|
|
options.target = options.target || {};
|
|
options.target.https = true;
|
|
}
|
|
|
|
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 || {};
|
|
|
|
var location,
|
|
proxy,
|
|
key;
|
|
|
|
if (this.proxyTable && !options.host) {
|
|
location = this.proxyTable.getProxyLocation(req);
|
|
|
|
if (!location) {
|
|
return socket.destroy();
|
|
}
|
|
|
|
options.port = location.port;
|
|
options.host = location.host;
|
|
}
|
|
|
|
key = this._getKey(options);
|
|
|
|
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 [
|
|
options.host || options.target.host,
|
|
options.port || options.target.port
|
|
].join(':');
|
|
};
|