Monday, June 24, 2019

Common HTTP Status Code Errors when getting started with WebSockets

I'm working on an application where the frontend is React and the backend is Node running on CentOS 7.  I am introducing WebSockets for some data requests that can be long-running queries.  I haven't used WebSockets in the past so wanted to share two HTTP error statuses I repeatedly got when starting out.  There are multiple reasons why I chose the Node "ws" library for WebSocket communication:

  • Actively maintained, roughly new version per month
  • Implements both WebSocket protocol versions 8 and 13 for Node
  • Straightforward to use, and easy to use with the Express Node web framework.  The server is used for both http and WebSockets communication
  • Over 13 million weekly downloads (most I found from any of the WebSocket libraries) on npmjs.com
  • Handles WebSocket frames for you

Environment:
CentOS Linux release 7.3.1611 (Core)
node v8.16.0
nginx version: nginx/1.12.2

Error 1 - HTTP Status Code 404
The first three browsers gave at least an HTTP Response error code, Firefox didn't provide much info on the error message.

  1. In IE: SCRIPT12008: WebSocket Error: Incorrect HTTP response. Status code 404, Not Found
  2. In Edge: SCRIPT12008: SCRIPT12008: WebSocket Error: Incorrect HTTP response. Status code 404, Not Found
  3. In Chrome: WebSocket connection to 'wss://cherryshoe.com/wsapi' failed: Error during WebSocket handshake: Unexpected response code: 404
  4. In Firefox:
    • The connection to wss://cherryshoe.com/sockjs-node/314/jv0jnivp/websocket was interrupted while the page was loading.
    • Firefox can’t establish a connection to the server at wss://cherryshoe.com/wsapi/.

Problem and Solution:  The nginx web server was not configured for WebSocket support, had to add the following to the /wsapi location
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

Error 2 - HTTP Status code 502

The first three browsers gave at least an HTTP Response error code, Firefox didn't provide much info on the error message.

  1. In IE: SCRIPT12008: WebSocket Error: Incorrect HTTP response. Status code 502, Bad Gateway
  2. In Edge: SCRIPT12008: SCRIPT12008: WebSocket Error: Incorrect HTTP response. Status code 502, Bad Gateway
  3. In Chrome: WebSocket connection to 'wss://cherryshoe.com/wsapi/' failed: Error during WebSocket handshake: Unexpected response code: 502
  4. In Firefox:
    • The connection to wss://cherryshoe.com/sockjs-node/957/mxbslcja/websocket was interrupted while the page was loading.
    • Firefox can’t establish a connection to the server at wss://cherryshoe.com/wsapi/.
Problem and Solution 2:
I would get multiple successful client connections (one for Firefox, two or three for Chrome/IE/Edge), but then the next connection consistently received Status Code 502.  This error essentially kills any communication with the WebSocket Server itself (not just that specific client connection), which would require a restart of the Node server.

The problem was that in close event handler ws.on('close') I was calling close on the WebSocketServer itself (wss.close()).  I didn't actually want to close the WebSocketServer itself, only wanted to close the connection, but in the close event handler ws.on('close') the connection is already closed!

The client code message event handler onmessage would close the socket after it received a 'Done' message, which caused the WebSocket Server ws.on('close') to be called.
    let socket;

    ...
    socket setup code
    ...

    // this is over https, so have to make it web socket over https "wss:"
    socket = new WebSocket('wss://cherryshoe.com/wsapi/');

    // message received from web socket server
    socket.onmessage = function(evt) {

      // If data request is 'Done'
      socket.close([1000], "Work complete");
    };


The server code web socket close event handler was closing the WebSocket server:

var WebSocketServer = require('ws').Server;

...
code to setup server
...


var wss = new WebSocketServer({server: server});
wss.on('connection', function (ws, socket) {
    console.info("websocket connection open");

    // send status back to client
    ws.send('Connection established');

    // Handle when message is sent from client
    ws.on('message', async (requestData) => {
        // process the request code here
        ws.send('Done');
    });

    ws.on('error', function (ws, code, reason) {
      console.info("websocket connection error");
    });

    ws.on('close', function (ws, code, reason) {
      console.info("websocket connection close");
      wss.close();
    });
});


The solution was to remove the wss.close from the ws.on('close') code:

wss.on('connection', function (ws, socket) { 

   ...

   ws.on('close', function (ws, code, reason) {
      // connection is closed, but you don't actually want to close down the
      // WebSocketServer so do not do wss.close()!
      console.info("websocket connection close");
   });
   
   ...

});

1 comment:

I appreciate your time in leaving a comment!