WebSockets a brief explanation and practical use

WebSockets a brief explanation and practical use

The problem

During the development of one of the projects I was working on we faced a problem:

We need to let the user know about an incoming event in the fastest (and cleanest) way possible.

The no-brainer solution is polling, but is it the right choice? In this article, I'm going to talk about it, the reasons on why not using it, and present WebSockets as a practical solution to this problem.


Polling

What is it?

Polling is a technique that consists on fetching information on a regular schedule to check for data changes. An example of this is checking a thermometer reading every 10 seconds to set the AC settings.

In our case, polling would mean hitting an endpoint every x seconds until we get information regarding the expected event.

Benefits

  • It's easy to implement.
  • It requires no special support from the server.
  • It works on HTTP/1.0.

Weak points

  • We need to dedicate server resources to responding to the polling request, when most of those requests will have "negative" answers (no changes in the event). This is especially concerning as the app's user base grows.
  • If we decide to change the polling frequency we would need to recompile the code and deploy a new version of the application.
  • Changes are not instant, you have to wait until the next data polling to see them.

This last problem was the tipping point, as our application heavily relied on an almost instant response. In order to prevent that, we decided on using WebSockets.

Introducing: WebSockets

What is it?

WebSockets is a protocol that allows bidirectional communication between a client and a server over a TCP port.

It's important to point out that even though the WebSocket protocol uses ports 80 and 443 for unsecured and secured connections respectively, the WebSocket protocol is a separate protocol to HTTP. (Note: to start a WebSocket connection it's necessary to send an HTTP request, so there's that).

As WebSockets is a different protocol, if your server is configured to route HTTP and HTTPS requests to a specific port (for example if you're using AWS ElasticBeanstalk), you may need to change your configuration to route TCP and SSL connections instead. This should not affect what you already had, as HTTP runs over TCP and HTTPS over SSL. You should also find a way to prevent dropping connections after some time as most servers do, because you would be dropping active WebSocket connections.

Reasons to use WebSockets over polling

There are three main reasons why we decided on using WebSockets for this application:

  1. WebSockets allows the server to send information to the client without the client requesting it (as you may or may not know, in HTTP the server sends information only as a response to a previous request).
  2. Size of messages: Once a WebSocket connection gets established via HTTP, the messages do not need to send any type of headers, so (considering you send small messages) using WebSockets over polling reduces the amount of data transfer needed. So you are not only saving data by not needing to send requests from the client, but also saving data by not sending headers from the server.
  3. Client disconnection: WebSockets allow the server to detect when a client disconnects.

This last point was a selling point for us, as the application needed to send information only to connected users.


How we used WebSockets

There are many libraries that abstract WebSockets in order to make them friendlier to developers. Possibly the most popular one is socket.io.

In our case we used express-ws as it has a similar syntax to express, which is the framework we were already using to develop the app.

For the frontend, we used the native WebSockets API as we didn’t need complex behaviors.

Server side

In short, every time a user connects to the server, they establish a WebSocket connection as well.

// Receive socket connections
router.ws('/:userId/status'. (ws, req) => {
// The socket connected code would go here, not on an event handler
// Message received event, handler receives raw messages
ws.on('message', (message) => {
try {
const status = JSON.parse(message);
if (typeof status === 'boolean') {
event.emit('userStatusChange', { status, userId: req.params.userId });
}
} catch (error) {
// console.log(error);
}
});
// Socket closed event, client disconnected
ws.on('close', () => {
event.emit('userStatusChange', { status: false, userId: req.params.userId });
});
});

While no events are detected, the user navigates through the app as they wish. Once an event is detected, the server processes the event and sends the result to the client through a WebSocket message.

// Receive socket connections
router.ws("/:userId/status", (ws, req) => {
// When a dispatch call event is received
// This is not really related to sockets
event.on("dispatchCall", ({ roomName, phoneNumber }) => {
// readyState === 1 ensures the socket is opened
if (ws.readyState === 1) {
// Send the necessary information as a stringified JSON
ws.send(
JSON.stringify({
phoneNumber,
roomName,
type: "available call",
})
);
}
});
});

Client side

Once a message is received through the WebSocket, the client loads the payload of said message and displays a message with that information.

Once the message is displayed, the WebSocket has completed its mission (the socket remains open to receive subsequent messages).

// Create sockets with websockets API
const socket = new WebSocket(`${process.env.ROOT_SOCKET}/socket`);
// Message received handler, function receives raw message
socket.onmessage = (rawMessage) => {
const message = JSON.parse(rawMessage.data);
if (message.type === "available call") {
// Handle the event received, in this case using Redux
dispatch(
setAvailableRoom({
roomName: message.roomName,
phoneNumber: message.phoneNumber,
})
);
}
};

Final thoughts

There are a lot of practical applications for WebSockets: instant messaging, pushing notifications, keeping track of connection status, etc.

Though it may initially seem like WebSockets require a lot of technical knowledge, it's only necessary to have some basic knowledge of event driven development, which you will possibly have if you are developing in JavaScript.

It's important to mention that WebSockets add an asynchronous element to the application, messages are not necessarily received in a specific order and may be received in the middle of another process, so when using them you must think "What should I do if I receive a message through a socket at this moment?".

So if you ever come to a problem to solve similar to mine, I strongly recommend you going right with WebSockets, using socket.io, as it has a much larger community than express-ws, and simpler to test.