2 * routing-proxy.js: A routing proxy consuming a RoutingTable and multiple HttpProxy instances
4 * (C) 2011 Nodejitsu Inc.
9 var events = require('events'),
10 utile = require('utile'),
11 HttpProxy = require('./http-proxy').HttpProxy,
12 ProxyTable = require('./proxy-table').ProxyTable;
15 // ### function RoutingProxy (options)
16 // #### @options {Object} Options for this instance
17 // Constructor function for the RoutingProxy object, a higher level
18 // reverse proxy Object which can proxy to multiple hosts and also interface
19 // easily with a RoutingTable instance.
21 var RoutingProxy = exports.RoutingProxy = function (options) {
22 events.EventEmitter.call(this);
25 options = options || {};
28 this.proxyTable = new ProxyTable(options);
29 this.proxyTable.on('routes', function (routes) {
30 self.emit('routes', routes);
35 // Create a set of `HttpProxy` objects to be used later on calls
36 // to `.proxyRequest()` and `.proxyWebSocketRequest()`.
41 // Setup default target options (such as `https`).
44 this.target.https = options.target && options.target.https;
45 this.target.maxSockets = options.target && options.target.maxSockets;
48 // Setup other default options to be used for instances of
49 // `HttpProxy` created by this `RoutingProxy` instance.
51 this.source = options.source || { host: 'localhost', port: 8000 };
52 this.https = this.source.https || options.https;
53 this.enable = options.enable;
54 this.forward = options.forward;
55 this.changeOrigin = options.changeOrigin || false;
58 // Listen for 'newListener' events so that we can bind 'proxyError'
59 // listeners to each HttpProxy's 'proxyError' event.
61 this.on('newListener', function (evt) {
62 if (evt === 'proxyError' || evt === 'webSocketProxyError') {
63 Object.keys(self.proxies).forEach(function (key) {
64 self.proxies[key].on(evt, self.emit.bind(self, evt));
72 // Inherit from `events.EventEmitter`.
74 utile.inherits(RoutingProxy, events.EventEmitter);
77 // ### function add (options)
78 // #### @options {Object} Options for the `HttpProxy` to add.
79 // Adds a new instance of `HttpProxy` to this `RoutingProxy` instance
80 // for the specified `options.host` and `options.port`.
82 RoutingProxy.prototype.add = function (options) {
84 key = this._getKey(options);
87 // TODO: Consume properties in `options` related to the `ProxyTable`.
89 options.target = options.target || {};
90 options.target.host = options.target.host || options.host;
91 options.target.port = options.target.port || options.port;
92 options.target.socketPath = options.target.socketPath || options.socketPath;
93 options.target.https = this.target && this.target.https ||
94 options.target && options.target.https;
95 options.target.maxSockets = this.target && this.target.maxSockets;
98 // Setup options to pass-thru to the new `HttpProxy` instance
99 // for the specified `options.host` and `options.port` pair.
101 ['https', 'enable', 'forward', 'changeOrigin'].forEach(function (key) {
102 if (options[key] !== false && self[key]) {
103 options[key] = self[key];
107 this.proxies[key] = new HttpProxy(options);
109 if (this.listeners('proxyError').length > 0) {
110 this.proxies[key].on('proxyError', this.emit.bind(this, 'proxyError'));
113 if (this.listeners('webSocketProxyError').length > 0) {
114 this.proxies[key].on('webSocketProxyError', this.emit.bind(this, 'webSocketProxyError'));
124 'websocket:incoming',
126 ].forEach(function (event) {
127 this.proxies[key].on(event, this.emit.bind(this, event));
132 // ### function remove (options)
133 // #### @options {Object} Options mapping to the `HttpProxy` to remove.
134 // Removes an instance of `HttpProxy` from this `RoutingProxy` instance
135 // for the specified `options.host` and `options.port` (if they exist).
137 RoutingProxy.prototype.remove = function (options) {
138 var key = this._getKey(options),
139 proxy = this.proxies[key];
141 delete this.proxies[key];
146 // ### function close()
147 // Cleans up any state left behind (sockets, timeouts, etc)
148 // associated with this instance.
150 RoutingProxy.prototype.close = function () {
153 if (this.proxyTable) {
155 // Close the `RoutingTable` associated with
156 // this instance (if any).
158 this.proxyTable.close();
162 // Close all sockets for all `HttpProxy` object(s)
163 // associated with this instance.
165 Object.keys(this.proxies).forEach(function (key) {
166 self.proxies[key].close();
171 // ### function proxyRequest (req, res, [port, host, paused])
172 // #### @req {ServerRequest} Incoming HTTP Request to proxy.
173 // #### @res {ServerResponse} Outgoing HTTP Request to write proxied data to.
174 // #### @options {Object} Options for the outgoing proxy request.
176 // options.port {number} Port to use on the proxy target host.
177 // options.host {string} Host of the proxy target.
178 // options.buffer {Object} Result from `httpProxy.buffer(req)`
179 // options.https {Object|boolean} Settings for https.
181 RoutingProxy.prototype.proxyRequest = function (req, res, options) {
182 options = options || {};
187 // Check the proxy table for this instance to see if we need
188 // to get the proxy location for the request supplied. We will
189 // always ignore the proxyTable if an explicit `port` and `host`
190 // arguments are supplied to `proxyRequest`.
192 if (this.proxyTable && !options.host) {
193 location = this.proxyTable.getProxyLocation(req);
196 // If no location is returned from the ProxyTable instance
197 // then respond with `404` since we do not have a valid proxy target.
201 if (!this.emit('notFound', req, res)) {
207 console.error("res.writeHead/res.end error: %s", er.message);
214 // When using the ProxyTable in conjunction with an HttpProxy instance
215 // only the following arguments are valid:
217 // * `proxy.proxyRequest(req, res, { host: 'localhost' })`: This will be skipped
218 // * `proxy.proxyRequest(req, res, { buffer: buffer })`: Buffer will get updated appropriately
219 // * `proxy.proxyRequest(req, res)`: Options will be assigned appropriately.
221 options.port = location.port;
222 options.host = location.host;
225 var key = this._getKey(options),
228 if ((this.target && this.target.https)
229 || (location && location.protocol === 'https')) {
230 options.target = options.target || {};
231 options.target.https = true;
234 if (!this.proxies[key]) {
235 this.add(utile.clone(options));
238 proxy = this.proxies[key];
239 proxy.proxyRequest(req, res, options.buffer);
243 // ### function proxyWebSocketRequest (req, socket, head, options)
244 // #### @req {ServerRequest} Websocket request to proxy.
245 // #### @socket {net.Socket} Socket for the underlying HTTP request
246 // #### @head {string} Headers for the Websocket request.
247 // #### @options {Object} Options to use when proxying this request.
249 // options.port {number} Port to use on the proxy target host.
250 // options.host {string} Host of the proxy target.
251 // options.buffer {Object} Result from `httpProxy.buffer(req)`
252 // options.https {Object|boolean} Settings for https.
254 RoutingProxy.prototype.proxyWebSocketRequest = function (req, socket, head, options) {
255 options = options || {};
261 if (this.proxyTable && !options.host) {
262 location = this.proxyTable.getProxyLocation(req);
265 return socket.destroy();
268 options.port = location.port;
269 options.host = location.host;
272 key = this._getKey(options);
274 if (!this.proxies[key]) {
275 this.add(utile.clone(options));
278 proxy = this.proxies[key];
279 proxy.proxyWebSocketRequest(req, socket, head, options.buffer);
283 // ### function addHost (host, target)
284 // #### @host {String} Host to add to proxyTable
285 // #### @target {String} Target to add to proxyTable
286 // Adds a host to proxyTable
288 RoutingProxy.prototype.addHost = function (host, target) {
289 if (this.proxyTable) {
290 this.proxyTable.addRoute(host, target);
295 // ### function removeHost (host)
296 // #### @host {String} Host to remove from proxyTable
297 // Removes a host to proxyTable
299 RoutingProxy.prototype.removeHost = function (host) {
300 if (this.proxyTable) {
301 this.proxyTable.removeRoute(host);
306 // ### @private function _getKey (options)
307 // #### @options {Object} Options to extract the key from
308 // Ensures that the appropriate options are present in the `options`
309 // provided and responds with a string key representing the `host`, `port`
310 // combination contained within.
312 RoutingProxy.prototype._getKey = function (options) {
313 if (!options || ((!options.host || !options.port)
314 && (!options.target || !options.target.host || !options.target.port))) {
315 throw new Error('options.host and options.port or options.target are required.');
319 options.host || options.target.host,
320 options.port || options.target.port