Ethan Ethan - 4 months ago 34
Node.js Question

http-proxy-rules and Websockets

I haven't been able to find any documentation/answer to my needs.

I'm in something of a pickle. I'm developing a websocket application which will allow module expansion by creating new services (websocket servers). Of course this means more ports to connect to. The problem is, our corporate policy only has a very few ports open so I need to proxy my requests.

I've read many answers saying to use NGINX, but I simply can't. First off I'm running windows, second our company is very strict on what can and can't be used. I am able to install any node module, though. I've attempted to use the http-proxy module along with http-proxy-rules.

The problem is, I'm getting 404's on every websocket request. I'll note that the default proxy (for normal webservice, not sockets) is working 100% fine.

Here's my current code:

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

// Set up proxy rules instance
var proxyRules = new HttpProxyRules({
rules: {
'.*/ws/admin': 'http://localhost:26266', // Rule for websocket service (admin module)
'.*/ws/quickquery': 'http://localhost:26265' // Rule for websocket service (quickquery module)
},
default: 'http://Surface.levisinger.com:8080' // default target
});

// Create reverse proxy instance
var proxy = httpProxy.createProxy();

// Create http server that leverages reverse proxy instance
// and proxy rules to proxy requests to different targets
http.createServer(function(req, res) {

// a match method is exposed on the proxy rules instance
// to test a request to see if it matches against one of the specified rules
var target = proxyRules.match(req);
if (target) {
//console.log(req);
console.log("Returning " + target + " for " + req.headers.host);
return proxy.web(req, res, {
target: target,
ws: true
});
}

res.writeHead(500, { 'Content-Type': 'text/plain' });
res.end('The request url and path did not match any of the listed rules!');
}).listen(5050);


My client code connecting to the websocket looks like this:

var servPath = (cliSettings["AppPaths"]["Admin"] == null) ? 'http://' + window.location.hostname + ':5050' : cliSettings["AppPaths"]["Admin"],
AdminIO = new io(servPath, {
extraHeaders: {
Service: "Admin"
},
path: '/ws/admin'})


...

And the websocket server is called like this:

io = require('socket.io').listen(26266,{ path: '/ws/admin'}) // can use up to 26484


I really hope someone here will have an idea. Thanks!

Answer

Figured out how to do this. There are a few things to this... 1. You have to use custom websocket paths if you want to proxy them. 2. You must give the entire path to the websocket when proxying them. 3. You'll need to specify an event to handle websocket (ws) traffic.

Here's an example for you all.

+++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++++++++++++++++++

Proxy Server:

var   ini = require('node-ini'),
      conf = ini.parseSync('../settings/config.ini'),
      http = require('http'),
      httpProxy = require('http-proxy'),
      HttpProxyRules = require('http-proxy-rules');

  // Set up proxy rules instance
  var proxyRules = new HttpProxyRules({
    rules: {
      '.*/ws/remote': 'http://' + conf["Server"]["binding"] + ':26267/ws/remote',
      '.*/ws/admin': 'http://' + conf["Server"]["binding"] + ':26266/ws/admin', // Rule for websocket service (admin module)
      '.*/ws/quickquery': 'http://' + conf["Server"]["binding"] + ':26265/ws/quickquery' // Rule for websocket service (quickquery module)      
    },
    default: 'http://' + conf["Server"]["binding"] + ':8080' // default target
  });

  // Create reverse proxy instance
  var proxy = httpProxy.createProxy();

  // Create http server that leverages reverse proxy instance
  // and proxy rules to proxy requests to different targets
  var proxyServer = http.createServer(function(req, res) {

    // a match method is exposed on the proxy rules instance
    // to test a request to see if it matches against one of the specified rules
    var target = proxyRules.match(req);
    if (target) {     
      //console.log(req.url); 
      //console.log("Returning " + target + " for " + req.url);
      return proxy.web(req, res, {
        target: target,
        ws: true
      });      
    }

    res.writeHead(500, { 'Content-Type': 'text/plain' });
    res.end('The request url and path did not match any of the listed rules!');
  }).listen(conf["Server"]["port"]);

//
// Listen to the `upgrade` event and proxy the
// WebSocket requests as well.
//
proxyServer.on('upgrade', function (req, socket, head) {
    var target = proxyRules.match(req);
    if (target) {
        return proxy.ws(req, socket, head, { target: target });      
    }
});
process.on('SIGINT', function() {
   db.stop(function(err) {
     process.exit(err ? 1 : 0);
   });
});

Websocket Server socket listener:

io = require('socket.io').listen(26266,{ path: '/ws/admin'});

Connect to websocket from client page:

AdminIO = new io({path: '/ws/admin'});

+++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++++++++++++++++++

The example above will proxy my "admin" connection, which is running on port 26266, through port 80. (I'd of course recommend using 443/SSL in every situation, but that's a bit more complex).

Hope this helps someone!

Comments