question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

CORS pre-flight breaks socket.io behind load balancer

See original GitHub issue

I ran into an issue on our servers. We are running socket.io v1.0.6 on multiple server instances behind a load balancer. For polling, the requests go through the ELB with sticky sessions turned on. Our real-time service is on a subdomain, and thanks to CORS pre-flight requests, socket.io fucks up. Here is what happens on the client when the polling transport is used:

  1. A socket.io handshake POST request occurs. The response comes back valid with an sid, and the headers include the AWS ELB cookie.
  2. Next, a pre-flight OPTIONS request is made by the browser. The ELB cookie is not included by the browser here. As a result, the OPTIONS request is routed to a potentially different server which will not recognize the sid in the query string.
  3. When the request is routed to the wrong server, socket.io responds with a 400 HTTP status code and an Session ID unknown error.
  4. Since the pre-flight request fails, the browser also fails the actual GET polling request, and tries to re-do the handshake from the beginning
  5. Possibly due to the headers being sent, the browser sends the OPTIONS pre-flight request fairly regularly as opposed to doing it only once, so this cycle repeats over and over.

The fix on our end currently is to respond to all OPTIONS requests with a 200 and all the usual Access-Control-Allow-… headers the browser knows and loves. We do this before they even get to socket.io in our nginx config.

Now, engine.io appears to already handle this case here: https://github.com/Automattic/engine.io/blob/master/lib/transports/polling-xhr.js#L40

However, that check is only reached if the sid is valid here: https://github.com/Automattic/engine.io/blob/master/lib/server.js#L180

which it isn’t, of course. I can submit a PR but I’d like to know how you guys think it’d be best to handle this. AFAIK, if a request method is OPTIONS, we can make the assumption that we are polling. But, since we don’t have a valid sid to look up a client by, this might mean moving fairly transport-specific logic into server.js which sounds less than ideal.

Thoughts?

Issue Analytics

  • State:closed
  • Created 9 years ago
  • Reactions:6
  • Comments:43 (25 by maintainers)

github_iconTop GitHub Comments

5reactions
MiLkcommented, Jun 17, 2017

I’m trying to reproduce the issue, but so far the connection seems actually stable. screen shot 2017-06-17 at 18 02 06 screen shot 2017-06-17 at 18 01 33

I’m using https://github.com/socketio/engine.io/tree/a63c7b787c54b3a47da7f355826bf2770139c62b.

var app = require('express')();
var cors = require('cors');

app.options('*', cors({
  origin: true,
  methods: 'POST',
  allowedHeaders: ['Content-Type'],
  credentials: true,
}));

var server = require('http').Server(app);
var io = require('socket.io')(server, {
  handlePreflightRequest: false
});

...
5reactions
ashaffercommented, Feb 20, 2015

For anyone else with this issue, the following code will fix it:

module.exports = function(srv) {
  var listeners = srv.listeners('request').slice(0);
  srv.removeAllListeners('request');
  srv.on('request', function(req, res) {
    if(req.method === 'OPTIONS' && req.url.indexOf('/socket.io') === 0) {
      var headers = {};
      if (req.headers.origin) {
        headers['Access-Control-Allow-Credentials'] = 'true';
        headers['Access-Control-Allow-Origin'] = req.headers.origin;
      } else {
        headers['Access-Control-Allow-Origin'] = '*';
      }

      headers['Access-Control-Allow-Headers'] = 'origin, content-type, accept';
      res.writeHead(200, headers);
      res.end();
    } else {
      listeners.forEach(function(fn) {
        fn.call(srv, req, res);
      });
    }
  });
};

Applied after socket.io:

e.g:

io.listen(server);
handleOptions(server);
Read more comments on GitHub >

github_iconTop Results From Across the Web

Handling CORS | Socket.IO
If you have properly configured your server (see above), this could mean that your browser wasn't able to reach the Socket.IO server.
Read more >
External HTTP(S) Load Balancing overview - Google Cloud
External HTTP(S) Load Balancing is a proxy-based Layer 7 load balancer that enables you to run and scale your services behind a single...
Read more >
Socket.io gives CORS error even if I allowed cors it on server
If running Express as follows: var app = express(); var server = app.listen(3000);. So to start socket.io, we can do the following:
Read more >
How To Use Web Sockets (Socket IO) With Digital Ocean ...
How to get up and running and what to watch out for when using Socket IO with load balancers on Kubernetes on Digital...
Read more >
primus - npm
you might need to add WebSocket specific settings to its configuration files. ... maxAge, cors Cache duration of CORS preflight, 30 days.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found