Tired of Slow Loading Pages? Speed Up Your Web App with This Technique

Have you recognized a sluggish component in your web app and want it to be more snappy? I was in the same boat until I tried Websocket. It helped me achieve faster load times and get real-time updates for my web app's feed. I'll be sharing my learnings, so stay tuned!

Tired of Slow Loading Pages? Speed Up Your Web App with This Technique

Users want their digital interactions to be real-time. Whether it is sports scoreboards, financial information or IoT device communication they feel everything should be live.

Who wouldn't enjoy seeing what they wanted to see in the first place without any additional action?

To create real-time digital experiences, I initially used HTTP-based solutions like short-polling and long-polling. However, these methods still introduced some delay and involved many API calls, which wasn't ideal.

I switched to WebSockets, which improved the user interface with real-time data and faster updates.

So how does WebSocket excel in terms of latency when compared to others? Let's hop right on to it.

Why is WebSocket faster than alternatives?

Long Polling performs poorly as the number of clients increases due to the overhead of repetitious HTTP connections.

Server-sent events perform greater than Long Polling, utilizing a single HTTP connection for server-to-client updates and is a good competitor for WebSockets.

WebSocket have low latency hence faster comparatively due to the absence of multiple requests.

Alt text

There are many other nuances and details when it comes to production deployment.
More tests and details can be found in the paper.

HTTP is request-driven, meaning each operation will be a request to the server and the server chooses to accept it or reject it, the authentication and processing increases the latency and bandwidth.

In the following example, I've considered comparing Long Polling and WebSockets.

Long Polling takes HTTP request/response polling and makes it more efficient. In Long Polling server holds the client connection as long as possible until a timeout threshold is reached.

Upon receipt of the server response, the client usually issues another request immediately. It takes 26.8 ms on average for the response to reach other clients.

Alt text

WebSocket is event-driven, meaning the authentication will only happen once. It eliminates the need for a new connection with every request.

WebSocket keeps the connection alive, letting the server and the client to stream messages to each other. The latency is drastically reduced due to the absence of headers, and authentication for each event. WebSocket also makes an HTTP request, we will come back to it later.

Alt text

If we consider using HTTP Request for a chat application, the client will need to continuously keep on requesting the server to get new messages if there are any.
This is a sub-optimal way to show real-time data as there is an ambiguity in making the decision at what interval we need to fetch data from the server.

Alt text

In the case of WebSockets, there is no need to fetch data at intervals because the server pushes if there is new data to each of the clients which is configured to be shared.

Alt text

How are WS any different from other solutions?

Typically, to have up-to-date information on a page, users might have to perform actions such as:

  1. Reload the page in the browser
  2. Refreshing the parts of the user interface, if the option is available.

When the action is performed the client would request the server for data, and when the request is succeeded the client loads the data onto the page.

So each time the user wants to check updates they might want to refresh the page.

There are many solutions such as Comet, Long Polling, Server Sent Events, and WebSocket.

Comet's usage has been reduced in recent years due to the server load and many other reasons but it was popular in the early 2000s.

Most of their limitations from using HTTP were from the underlying transport protocol.
The problem is that HTTP was initially designed to serve hypermedia resources in a request-response fashion.

It hadn't been optimized to power real-time apps that usually involve high-frequency or ongoing client-server communication, and the ability to react instantly to changes.

Hacking HTTP-based technologies to emulate the real-time web was bound to lead to all sorts of drawbacks.

  1. Limited scalability
  2. Latency
  3. No bidirectional communication

To deal with these problems WebSockets can be used. WebSocket is a protocol for real-time, bidirectional communication between web clients and servers, providing a full-duplex communication channel over a single, long-lived connection.

A WebSocket connection starts as an HTTP request/response handshake; beyond this handshake,

WebSocket and HTTP are fundamentally different. Handshake is the entry point for a WebSocket connection between a Client and a Server.

The following image shows the HTTP one-way communication made from the client and server to and fro for each need, and WebSocket making a single connection from client to server and having a stable connection to interact, this is a bi-directional communication.

Alt text

Unpleasant Truths about WebSockets

  1. Statefullness: WebSockets maintain a stateful connection, which can complicate code and infrastructure.
  2. Single Source of Truth: Managing state across multiple instances becomes a challenge, which pushes to have a single source of truth, such as Redis, to maintain session information.
  3. Fallback Strategy: Not all proxies support WebSockets, and some networks block specific ports. This requires a fallback strategy, which can impact the availability and resource usage of your system.
  4. Connection Management: WebSockets require a strategy for restoring connections from the client side.
  5. Cost and Time: Building and maintaining a reliable WebSocket system in-house can be costly and time-consuming, with significant engineering effort required.

1 powerful reason a day nudging you to read
so that you can read more, and level up in life.

Sent throughout the year. Absolutely FREE.

How is connection established in WS?

The Process of establishing a connection is known as Handshake.

The client always initiates the handshake; it sends a GET request to the server,

indicating that it wants to upgrade the connection from HTTP to WebSockets.

Sec-WebSocket-Key is passed by the client to the server which is essential to make a handshake and also let the server know it is a WebSocket connection.

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com

The server must return an HTTP 101 Switching Protocols response code for the WebSocket

connection to be established.

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat

The server accepts the key by returning Sec-WebSocket-Accept which indicates the connection is successful. If the Key is not present in the request server can return an HTTP 400 Bad Request.

Alt text

Once the server returns 101, the WebSocket connection can be used for ongoing, bidirectional, full-duplex communications between server and client.

The green arrow indicates the client is sending message to the server.
The red arrow is the message incoming to the client from the server.

Alt text

How does the server handle requests?

I am going to use Node.js and ws library for the example.

Alt text

We can setup the ws server on port 7071

const WebSocket = require("ws");
const wss = new WebSocket.Server({ port: 7071 });

Create a map to store the clients for prolonged connection

const wsClients = new Map();

Subscribe to the wss connection event using the wss.on function, which provides a

callback. This will be fired whenever a new WebSocket client connects to the server:

wss.on("connection", (ws) => {
    const id = getName()
    const color = getColor()
    const metadata = { id, color }
    console.log(`New WebSocket client: ${id}`)
    wsClients.set(ws, metadata)
}

Using the newly connected WebSocket instance, we subscribe to that instance's message

event, and provide a callback function that will be triggered whenever this specific client

sends a message to the server.

ws.on("message", (messageAsString) => {
    const data = JSON.parse(messageAsString)
    console.log("Message received:", data.message)
}

We need to broadcast the received message to all the clients.

[...wsClients.keys()].forEach((client) => {
  const clientMetadata = wsClients.get(client);
  console.log("Broadcast to: ", clientMetadata.id);
  client.send(JSON.stringify(data));
});

When a client closes a connection close event is triggered. We can remove the client from the array as we don't need to communicate with it anymore.

ws.on("close", () => {
  console.log("WebSocket connection closed");
  wsClients.delete(ws);
});

How client communicates with the server?

I have a HTML file for the chat box, index.html
Alt text

<head>
  <script src="index.js" type="module"></script>
</head>
<body id="box">
  <h3>WebSocket</h3>
  <div class="input-group">
    <input type="text" id="ws-messageInput" />
    <button id="ws-sendButton">Send</button>
  </div>
  <div id="ws-messagesBox"></div>
</body>

We need to connect to the server as soon as client is available, index.js

const wsMessageInput = document.getElementById("ws-messageInput");
const wsSendButton = document.getElementById("ws-sendButton");

let ws; // Declare ws in the global scope

(async function () {
  ws = await connectToServer(); // Initialize ws after connection

  async function connectToServer() {
    const ws = new WebSocket("ws://localhost:7071/ws");
    return new Promise((resolve, reject) => {
      const timer = setInterval(() => {
        if (ws.readyState === 1) {
          clearInterval(timer);
          resolve(ws);
        }
      }, 10);
    });
  }
})();

To send a message on click of send button

wsSendButton.addEventListener("click", function () {
  const message = wsMessageInput.value;
  wsMessageInput.value = "";

  if (ws.readyState === WebSocket.OPEN) {
    const messageObject = { message: message, senderTime: new Date() };
    ws.send(JSON.stringify(messageObject));
  } else {
    // Handle cases when WebSocket is not ready
    console.error("WebSocket not connected yet.");
  }
});

To show the messages which the client received
Alt text

const wsMessagesBox = document.getElementById("ws-messagesBox");
(async function () {
 ws = await connectToServer(); // Initialize ws after connection

 ws.onmessage = (webSocketMessage) => {
   const receiverTime = new Date();
   console.log(webSocketMessage);
   const messageBody = JSON.parse(webSocketMessage.data);
   const messageElement = document.createElement("p");
   messageElement.append(messageBody.message);
   wsMessagesBox.appendChild(messageElement);
 };

 async function connectToServer() {
 ...
 }
})();

References

I have used some of the materials from these Research and Blogs for my understanding and representation of WebSockets.

Thanking all of the owners for their contributions.

  1. Ø. R. Tangen. Real-Time Web with WebSocket. Master’s Thesis. University of Oslo, May 2015.
  2. The WebSocket Handbook
  3. Woe unto you for using a Websocket
  4. WebSocket Libraries for your language
  5. Friendly, Friendly, et al. "Speed Comparison OF Websocket And HTTP In IOT Data Communication." INFOKUM 10.5 (2022): 46-51.

FeedZap: Read 2X Books This Year

FeedZap helps you consume your books through a healthy, snackable feed, so that you can read more with less time, effort and energy.