Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Re-emit 'start', 'forward' and 'end' events in RoutingProxy #216

Merged
merged 4 commits into from

3 participants

@coderarity

Fixes nodejitsu/node-http-proxy#214. This will re-emit more events from RoutingProxy so that you can listen for those events yourself. Be VERY careful for the following case, which will add a new listener for every request:

//DO NOT USE THIS CODE! IT'S BROKEN!
var httpProxy = require('http-proxy');

httpProxy.createServer(function (req, res, proxy) {

    var buffer = httpProxy.buffer(req);

    proxy.on('end', function() {
      console.log("The request was proxied (server)");
    });

    proxy.proxyRequest(req, res, {
      host: '127.0.0.1',
      port: 8080,
      buffer: buffer
    });

}).listen(8000);

It would be good if we could fix this case! This is a very easy problem to run into!

@coderarity

The reason that this case exists is because you only have access to the RoutingProxy inside of the server function. The RoutingProxy never gets recreated between requests, so it will end up adding a new listener every time a request is made.

One solution to get around this right now is to not use httpProxy.createServer at all, instead making your own server, like so:

var http = require('http'),
    httpProxy = require('http-proxy');

var proxy = new httpProxy.RoutingProxy();

proxy.on('end', function() {
  console.log('proxied');
})

http.createServer(function (req, res) {
  proxy.proxyRequest(req, res, {
    host: 'localhost',
    port: 8080
  });
}).listen(8000);

You could also use the server.proxy object to do this:

var httpProxy = require('http-proxy');

var server = httpProxy.createServer(function (req, res, proxy) {

    var buffer = httpProxy.buffer(req);

    proxy.proxyRequest(req, res, {
      host: '127.0.0.1',
      port: 8080,
      buffer: buffer
    });

});

server.proxy.on('end', function() {
  console.log("The request was proxied (server)");
});

server.listen(8000);

I'm not sure how to fix the case with listening for events on the proxy object inside the server function. You could remove the old listener every request, but adding and removing a listener on every request is very inefficient. If it recreated the RoutingProxy every request, that would almost be as inefficient. I'm not sure if it's even reasonable to eliminate this case - although it is likely to be a common mistake.

@thefosk

+1

@coderarity

This last commit fixes many problems people have had with RoutingProxy hanging. It will now return a real error from the matched HttpProxy to the client. Fixes nodejitsu/node-http-proxy#185 and will provide error information for a bunch of issues that have reported node-http-proxy to be hanging. I added it to this pull request since it changes how I handle some of the event's redirection in the first round of commits.

@indexzero
Owner

@CodeRarity Looks good to me. Merge at will.

@coderarity coderarity merged commit f223ce8 into from
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
Showing with 73 additions and 29 deletions.
  1. +26 −0 README.md
  2. +47 −29 lib/node-http-proxy/routing-proxy.js
View
26 README.md
@@ -217,6 +217,32 @@ proxyServerWithForwarding.listen(80);
The forwarding option can be used in conjunction with the proxy table options by simply including both the 'forward' and 'router' properties in the options passed to 'createServer'.
+### Listening for proxy events
+Sometimes you want to listen to an event on a proxy. For example, you may want to listen to the 'end' event, which represents when the proxy has finished proxying a request.
+
+``` js
+var httpProxy = require('http-proxy');
+
+var server = httpProxy.createServer(function (req, res, proxy) {
+
+ var buffer = httpProxy.buffer(req);
+
+ proxy.proxyRequest(req, res, {
+ host: '127.0.0.1',
+ port: 9000,
+ buffer: buffer
+ });
+
+});
+
+server.proxy.on('end', function() {
+ console.log("The request was proxied.");
+});
+
+server.listen(8000);
+```
+
+It's important to remember not to listen for events on the proxy object in the function passed to `httpProxy.createServer`. Doing so would add a new listener on every request, which would end up being a disaster.
## Using HTTPS
You have all the full flexibility of node-http-proxy offers in HTTPS as well as HTTP. The two basic scenarios are: with a stand-alone proxy server or in conjunction with another HTTPS server.
View
76 lib/node-http-proxy/routing-proxy.js
@@ -23,26 +23,26 @@ var RoutingProxy = exports.RoutingProxy = function (options) {
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
+ // 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.
@@ -51,6 +51,18 @@ var RoutingProxy = exports.RoutingProxy = function (options) {
this.https = this.source.https || options.https;
this.enable = options.enable;
this.forward = options.forward;
+
+ //
+ // 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, this.emit.bind(this, evt));
+ });
+ }
+ });
};
@@ -68,30 +80,37 @@ util.inherits(RoutingProxy, events.EventEmitter);
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.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.
+ // 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'));
+ 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'));
+ }
+ this.proxies[key].on('start', this.emit.bind(this, 'start'));
+ this.proxies[key].on('forward', this.emit.bind(this, 'forward'));
+ this.proxies[key].on('end', this.emit.bind(this, 'end'));
};
//
@@ -111,18 +130,18 @@ RoutingProxy.prototype.remove = function (options) {
//
RoutingProxy.prototype.close = function () {
var self = this;
-
+
if (this.proxyTable) {
//
- // Close the `RoutingTable` associated with
+ // Close the `RoutingTable` associated with
// this instance (if any).
//
this.proxyTable.close();
}
-
+
//
// Close all sockets for all `HttpProxy` object(s)
- // associated with this instance.
+ // associated with this instance.
//
Object.keys(this.proxies).forEach(function (key) {
self.proxies[key].close();
@@ -160,11 +179,11 @@ RoutingProxy.prototype.proxyRequest = function (req, res, options) {
try {
res.writeHead(404);
res.end();
- }
+ }
catch (er) {
console.error("res.writeHead/res.end error: %s", er.message);
}
-
+
return;
}
@@ -179,15 +198,14 @@ RoutingProxy.prototype.proxyRequest = function (req, res, options) {
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);
};
@@ -206,7 +224,7 @@ RoutingProxy.prototype.proxyRequest = function (req, res, options) {
//
RoutingProxy.prototype.proxyWebSocketRequest = function (req, socket, head, options) {
options = options || {};
-
+
if (this.proxyTable && !options.host) {
location = this.proxyTable.getProxyLocation(req);
@@ -217,7 +235,7 @@ RoutingProxy.prototype.proxyWebSocketRequest = function (req, socket, head, opti
options.port = location.port;
options.host = location.host;
}
-
+
var key = this._getKey(options),
proxy;
@@ -225,7 +243,7 @@ RoutingProxy.prototype.proxyWebSocketRequest = function (req, socket, head, opti
this.add(options);
}
- proxy = this.proxies[key];
+ proxy = this.proxies[key];
proxy.proxyWebSocketRequest(req, socket, head, options.buffer);
};
@@ -237,14 +255,14 @@ RoutingProxy.prototype.proxyWebSocketRequest = function (req, socket, head, opti
// combination contained within.
//
RoutingProxy.prototype._getKey = function (options) {
- if (!options || ((!options.host || !options.port)
+ 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.host || options.target.host,
options.port || options.target.port
].join(':');
}
Something went wrong with that request. Please try again.