Search code examples
javascriptnode.jsexpresswebsocketws

WebSockets WS error during WebSocket handshake: Unexpected response code: 200


trying to deploy my app with ws on Heroku. On my local machine everything worked out well but after deploying on Heroku I get a handshake error with response status 200

Here's my server code:

const WebSocketServer = require('ws').Server;
const moment = require('moment');
const app = require('./app');

const wss = new WebSocketServer({ app });

const connections = new Set();

wss.on('connection', (ws) => {
  connections.add(ws);

  ws.on('message', (message) => {
    const time = moment(new Date()).format('HH:mm');
    const messageData = {
      time,
      message,
    };
    for (const connection of connections) {
      connection.send(JSON.stringify(messageData));
    }
  });

  ws.on('close', () => {
    connections.delete(ws);
  });
 });

And my client code:

const HOST = location.origin.replace(/^http/, 'ws');
const ws = new WebSocket(HOST);

ws.onmessage = (e) => {
  //
};

messageForm.addEventListener('submit', (e) => {
  //
  ws.send(message);
  //
});

Solution

  • The ws Server constructor does not accept an app option, so your wss isn't actually listening to anything. If this exact code did work locally, it's hard to see how.

    Since you're attempting to connect to a WebSocket that shares the same path as your page, the express app ends up handling the WebSocket client request and responds with your page HTML.

    Your HTTP exchange ends up looking something like this — first the browser loads the page:

    GET /page HTTP/1.1
    Accept: text/html
    …
    
    HTTP/1.1 200 OK
    Content-Type: text/html
    
    <html> …
    

    Then the page makes a new WebSocket(…), resulting in a WebSocket upgrade request:

    GET /page HTTP/1.1
    Connection: Upgrade
    Upgrade: websocket
    …
    
    HTTP/1.1 200 OK
    Content-Type: text/html
    
    <html> …
    

    You should see why this would fail: that's not a WebSocket response. Express thinks the request is a regular HTTP GET request and responds with your page's HTML. The browser's WebSocket implementation sees a response that does not conform to the WebSocket spec and rightly throws an error. It reports "status 200" because the server did respond with a HTTP 200 status code — even though it was an accident.

    The correct way to attach a ws Server to a HTTP server is to either:

    1. Pass in the http.Server (not the express app) to ws.Server's server option

      var app = express(),
          server = http.createServer(app),
          wss = new ws.Server({ server });
      
      server.listen(process.env.PORT);
      
    2. Or manually attach an upgrade listener to your http.Server and call wss.handleUpgrade.