Running socket.io and ws servers on the same port

If, like me, you are migrating a Node.js WebSocket server application from socket.io to using the plain ws package, then you may want to use both packages at the same time, on the same port, during the upgrade, to allow for backwards compatibility during the upgrade.

The following code shows how this can be done:

const http = require('http');
const io = require('socket.io');
const ioc = require('socket.io-client');
const WebSocket = require('ws');

const httpServer = http.createServer();
const ioServer = io(httpServer);
const wsServer = new WebSocket.Server({ noServer: true });

const ioHandleUpgrade = httpServer._events.upgrade;
delete httpServer._events.upgrade;

httpServer.on('upgrade', function (req, socket, head) {
    if (req.url.indexOf('socket.io') > -1) {
        ioHandleUpgrade(req, socket, head);

    } else {
        wsServer.handleUpgrade(req, socket, head, (webSocket) => {
            wsServer.emit('connection', webSocket, req);
        });
    }
});

Here we replace the upgrade event handler added by socket.io to our HTTP server with a handler that inspects the URL and passes the request either to the original socket.io handler or to the ws server instance.

We can test that clients are routed correctly by adding some request handlers to our servers:

ioServer.on('connection', (socket) => {
    console.log('socket.io client connected');

    socket.on('message', (data) => {
        console.log('socket.io client message', data);
    });

    socket.on('disconnect', () => {
        console.log('socket.io client disconnected');
    });
});

wsServer.on('connection', (socket) => {
    console.log('ws client connected');

    socket.on('message', (data) => {
        console.log('ws client message', data);
    });

    socket.on('close', () => {
        console.log('ws client disconnected');
    });
});

We can then start the server and send requests from socket.io and ws clients:

httpServer.on('listening', () => {
    const ioClient = ioc('ws://localhost:8080/');

    ioClient.on('connect', () => {
        ioClient.send('foo');
        setTimeout(() => ioClient.close(), 100);
    });

    const wsClient = new WebSocket('ws://localhost:8080/');

    wsClient.on('open', () => {
        wsClient.send('foo');
        setTimeout(() => wsClient.close(), 100);
    });

    setTimeout(() => httpServer.close(), 1000);
});

httpServer.listen(8080);

When we run our simple app we can see that messages are being correctly received by the relevant WebSocket servers:

$ node websocket.js
socket.io client connected
ws client connected
ws client message foo
socket.io client message foo
ws client disconnected
socket.io client disconnected