Remember when we had to keep hitting refresh to see if we had new messages or to check if our favorite team had scored? It seems so outdated now, right? We live in a world where we want everything to happen instantly. We chat, we watch live games, we track stocks - and we expect all of this to update in real-time, just like in the real world.
This need for speed and instant updates pushed tech folks to think outside the box. The old ways of doing things on the web just weren't cutting it anymore. They were slow, inefficient, and let's face it, pretty frustrating for users.
This is where WebSockets stepped in and revolutionized things. WebSockets introduced a new approach: instead of repeatedly asking the server for updates, why not establish a constant connection that allows instant information exchange?
This innovative method of web communication paved the way for more dynamic, responsive online experiences. It meant websites and apps could now provide real-time updates without the need for constant manual refreshing, making our digital interactions feel much more immediate and lively.
WebSockets are a communication protocol that allows for full-duplex (two-way) communication between a client and a server over a single, long-lived TCP connection. Unlike traditional HTTP requests, which require the client to initiate each interaction, WebSockets enable the server to send data to the client as soon as it becomes available, without waiting for the client to request it.
This real-time communication is particularly useful for applications that require frequent updates, such as live chats, online gaming, or real-time data feeds.
Note: WebSockets start with a standard HTTP request to establish the connection, which is then upgraded to the WebSocket protocol, maintaining the connection open for continuous data exchange.
WebSocket connection process step by step:
Initial HTTP Request:The client (usually a web browser) sends a standard HTTP request to the server. This request includes special headers that indicate the client wants to establish a WebSocket connection.
Upgrade Request:The key header in this request is "Upgrade: websocket". This tells the server that the client wants to switch from the HTTP protocol to the WebSocket protocol.
Server Evaluation:The server receives this request and checks if it supports WebSockets. If it does, it prepares to upgrade the connection.
Server Response:If the server agrees to the upgrade, it sends back an HTTP response with a status code of 101 (Switching Protocols). This response also includes headers confirming the protocol switch.
Protocol Switch:At this point, the connection is upgraded from HTTP to WebSocket. The same TCP connection is kept open, but the communication protocol changes.
WebSocket Connection Established:Now, both the client and server can send messages to each other at any time, without the need for new requests or responses.
Ongoing Communication:The WebSocket connection remains open, allowing for real-time, bidirectional communication. Either side can send messages at any time.
Connection Closure:The connection stays open until either the client or server decides to close it, or if there's a network issue.
This process allows for a seamless transition from the widely supported HTTP protocol to the more dynamic WebSocket protocol, enabling real-time communication while still being compatible with existing web infrastructure.
Analogy for Better Understanding:
Think of web sockets like a phone call between you and a pizza place.
In the old way of doing things online (without web sockets), it's like you have to keep calling the pizza place every few minutes:
"Is my pizza ready yet?"
"No, not yet."
hangs up
A few minutes later...
"Is my pizza ready now?"
"Still no."
hangs up
This process repeats until finally:
"Is my pizza ready?"
"Yes, it's ready!"
That's a lot of calls, right? It's inefficient and annoying for both you and the pizza place.
Now, with web sockets, it's like you call the pizza place once and just keep the line open:
You: "I'd like to order a pizza."
Pizza Place: "Great, we'll start making it."
(line stays open) ... (20 minutes later)
Pizza Place: "Your pizza is ready!"
You: "Awesome, I'll come pick it up."
In this scenario, you don't have to keep calling. The pizza place can tell you immediately when your pizza is ready. And if you have any questions while waiting, you can ask without having to call again.
This is how web sockets work on websites. They keep an open connection between your browser and the website's server, allowing instant, two-way communication without constantly asking for updates.
Some Real-World Use Cases:
Chat Apps (like WhatsApp or Facebook Messenger): Messages show up right away when someone sends them, without needing to refresh or check for new messages.
Live Sports Apps (like ESPN): Scores and stats update instantly during a game, keeping fans up-to-date in real time.
Online Multiplayer Games (like Fortnite): Players see each other's moves immediately, making the game smooth and real-time.
Stock Trading Apps (like Robinhood): Stock prices update in real-time, allowing traders to see price changes and execute trades instantly.
Ride-Sharing Apps (like Uber or Lyft): Passengers can watch their driver's location update in real-time on a map as they get closer.
Live Polling Apps (like Slido): Audience members can vote on questions during presentations, with results updating instantly for everyone to see.
Note: WebSockets are primarily designed to be used with web browsers and web servers.
Comparison with RestAPI
The internet has come a long way since its early days, and how we send and receive data online has changed a lot. As web applications have become more complex, developers have needed new ways to handle communication between users' devices and servers.
Two key technologies in this area are REST APIs and WebSockets. Both are used to exchange information between clients (like your web browser) and servers, but they work quite differently.REST APIs have been a go-to choice for many years. They're straightforward and work well for many traditional web applications. When you use a REST API, your app sends a request to a server and waits for a response.
WebSockets are a comparitively newer technology. They allow for ongoing, two-way communication between the client and server. This makes them great for applications that need real-time updates.
Some significant differences are:
Connection:
REST API: Creates a new connection for each request and closes it after receiving a response.
WebSocket: Establishes a persistent connection that stays open until either the client or server closes it.
Communication speed:
REST API: Has a slight delay between requests and responses due to connection overhead.
WebSocket: Offers faster communication with minimal latency once the connection is established.
Data flow:
REST API: Follows a request-response pattern, where the client always initiates communication.
WebSocket: Allows bidirectional communication, meaning both the client and server can send data at any time.
Real-time capabilities:
REST API: Requires polling (repeated requests) for real-time updates, which can be inefficient.
WebSocket: Enables real-time updates without polling, as data can be pushed instantly from server to client.
Resource efficiency:
REST API: May consume more resources due to creating and closing connections for each request.
WebSocket: More efficient for frequent communication, as it reuses a single connection.
Use cases:
REST API: Ideal for standard create, read, update, and delete operations when real-time updates aren't critical.
WebSocket: Better for applications requiring live data, chat functionality, or frequent updates.
Protocol:
REST API: Uses HTTP/HTTPS protocol.
WebSocket: Uses the WS/WSS protocol, which is designed for full-duplex communication.
Short Polling, Long Polling and WebSocket
In the world of web development, engineers have grappled with questions like: How can we keep users up-to-date with the latest information? Should we constantly ask for updates, wait a bit longer between checks, or keep a direct line open?
This is where Short Polling, Long Polling, and WebSockets come into play. Each of these techniques represents a different strategy for keeping web applications current and responsive. They range from frequently asking for updates, to patiently waiting for new information, to maintaining an always-open connection.
Short Polling
Short polling is the simplest method of getting updates from a server, but it's less efficient compared to WebSockets. In short polling, the client repeatedly sends requests to the server at fixed intervals to check for new data.
Each request opens a new HTTP connection, which is then closed after the server responds. This approach can lead to unnecessary server load and network traffic, especially when there are no updates available. The client continues to send requests even when there's no new data, resulting in wasted resources. Additionally, short polling may introduce latency in receiving real-time updates, as the client only checks for new data at predetermined intervals. This can be problematic for applications requiring immediate responsiveness.
Long Polling
Long polling improves upon short polling but still falls short of the efficiency offered by WebSockets. In long polling, the client sends a request to the server, which keeps the connection open until new data is available or a timeout occurs.
Once the server responds, the client immediately sends a new request. This reduces unnecessary requests compared to short polling, but it still relies on client-initiated communications and can face issues with timeouts and managing multiple open connections.
While more efficient than short polling, long polling doesn't match the real-time capabilities and bidirectional nature of WebSockets.
Web Socket
WebSockets provide the most efficient real-time communication among these methods. While WebSockets require more initial setup than polling methods, they offer superior performance and efficiency for real-time data exchange, making them the preferred choice for many modern web applications needing constant, bidirectional communication.
Summary:
Short polling is simple but inefficient
Long polling is more efficient but can be complex to manage
WebSockets offer the most efficient real-time communication but require more initial setup</aside>
WebSocket Protocols and Standards
The WebSocket Protocol, officially defined in RFC 6455, is the internet standard that makes real-time, two-way communication possible between web browsers and servers. This document lays out the rules for how WebSockets should work, ensuring that developers everywhere can create compatible, real-time web applications.
WebSocket API
The WebSocket API is the foundation for WebSocket communication in web applications. It defines how to create and manage WebSocket connections. While Spring Boot abstracts much of this, understanding the basics is valuable:
WebSocket object: The core component for establishing a connection
Key methods:
send(): Used to transmit data to the server
close(): Terminates the WebSocket connection
Important events:
onopen: Triggered when the connection is established
onmessage: Fires when a message is received from the server
onclose: Occurs when the connection is closed
onerror: Happens if an error arises during communication
In Spring Boot, you'll typically work with higher-level abstractions of these concepts, but knowing the underlying API helps in understanding the framework's behavior.
WebSocket Sub-protocols
WebSocket provides a full-duplex communication channel, but it doesn't specify how messages should be formatted or what they should contain. Sub-protocols are standardized ways of structuring the data sent over WebSocket connections. They provide higher-level APIs on top of WebSocket.
For example:
a) STOMP (Simple Text Oriented Messaging Protocol)
Widely supported in Spring Boot
Text-based protocol for message-oriented communication
Allows for easy implementation of publish-subscribe patterns
Spring provides annotations like @MessageMapping for handling STOMP messages
b) MQTT (Message Queuing Telemetry Transport)
Lightweight protocol, ideal for IoT applications
Can be used with Spring Boot, though less common than STOMP
Designed for low-bandwidth, high-latency networks
For most Spring Boot developers, STOMP is the preferred sub-protocol due to its excellent integration with Spring's messaging abstractions. It simplifies the implementation of real-time features in web applications.
SockJS is a library that helps handle WebSocket connections consistently across different web browsers. If a browser doesn't support WebSockets, SockJS automatically uses alternative methods to maintain real-time communication. This ensures that your application works smoothly for all users, regardless of their browser capabilities.
Integrating Websocket with SpringBoot
Objective:Developing an Interactive Real-Time Group Chat Application
In this part of the article, we'll walk through creating a chat application using WebSocket. We'll start by setting up WebSocket connections (<span class="pink">WebSocketConfig</span>), defining how messages are structured (<span class="pink">ChatMessage</span>), managing how users interact (<span class="pink">ChatController</span>), and ensuring smooth handling of user disconnections (<span class="pink">WebSocketEventListener</span>). Our aim is to show you how these pieces fit together to enable real-time messaging and enhance the overall user experience within the chat application.
We're starting our chat application by creating a WebSocket message broker in WebSocketConfig class. This is a key part of our app that handles communication between users.
The message broker does three main things:
It gets messages from users when they type and send them.
It sends these messages to everyone who's currently using the chat. This way, all users see new messages right away.
When someone leaves the chat, it tells everyone else that this person has gone.
This setup makes our chat work in real-time. Users don't have to refresh the page to see new messages or to know when someone has left. Everything updates instantly.
By building this message broker, we're making sure our chat app can handle lots of users talking at once, send messages instantly, and keep everyone up to date on who's in the chat.
Spring Web: Provides tools and features for building web applications and RESTful services.
Lombok: Simplifies Java code by generating boilerplate code like getters, setters, and constructors at compile time.
WebSocket: Facilitates building Servlet-based WebSocket applications with SockJS and STOMP for real-time, bi-directional data transfer.
Step 2: Setting Up the Server-Side Components
WebSocketConfig.java
package com.unlogged.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").setAllowedOriginPatterns("*").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
registry.enableSimpleBroker("/topic");
}
}
Explanation for the above code:
Purpose: The <span class="pink">WebSocketConfig</span> class sets up how clients connect to the server for real-time communication.
Key Points:
Endpoint (<span class="pink">/ws</span>): Clients use this endpoint to start talking to the server over WebSocket.
SockJS Support: It helps ensure connections work smoothly across different web browsers and network setups, even if direct WebSocket support isn’t available.
Message Routing:
<span class="pink">/app Prefix</span>: Handles messages sent from clients to the server.
<span class="pink">/topic Prefix</span>: Sends messages from the server to all connected clients at once.
This setup makes sure that clients and the server can exchange messages instantly, no matter the browser or network conditions, improving how real-time features work in web applications.
Purpose: The <span class="pink">ChatMessage</span> class is used to store and manage messages within a chat application. It keeps track of:
Content: The actual text or information of the message.
Sender: Who sent the message.
Type: The category of the message, like regular chat, joining a chat, or leaving.
MessageType.java
package com.unlogged.chat;
public enum MessageType {
CHAT,
JOIN,
LEAVE
}
Explanation for the above code:
Purpose: The <span class="pink">MessageType</span> enum defines different types of messages you can have in a chat:
CHAT: For regular messages exchanged between users.
JOIN: When someone joins the chat.
LEAVE: When someone leaves the chat.
ChatController.java
package com.unlogged.chat;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
@Controller
public class ChatController {
// Sends chat messages to everyone in the chat room
@MessageMapping("/chat.sendMessage")
@SendTo("/topic/public")
public ChatMessage sendMessage(@Payload ChatMessage chatMessage) {
return chatMessage;
}
// Adds a new user to the chat session
@MessageMapping("/chat.addUser")
@SendTo("/topic/public")
public ChatMessage addUser(@Payload ChatMessage chatMessage, SimpMessageHeaderAccessor headerAccessor) {
headerAccessor.getSessionAttributes().put("username", chatMessage.getSender());
return chatMessage;
}
}
Explanation for the above code:
Purpose:
The <span class="pink">ChatController</span> class manages how messages and user actions are handleded.
It performs two main functions:
Sending Messages: The <span class="pink">sendMessage</span> method handles messages sent by users, ensuring they reach the desired recipients.
Adding Users: The <span class="pink">addUser</span> method manages the process of adding users to the chat session, storing their information for session management.
In WebSocket-based chat applications, annotations like @MessageMapping direct messages to specific endpoints (/chat.sendMessage and /chat.addUser). These messages are then broadcasted to all connected clients through /topic/public, for seamless real-time communication and user interaction.
WebSocketEventListener.java
package com.unlogged.config;
import com.unlogged.chat.ChatMessage;
import com.unlogged.chat.MessageType;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.messaging.simp.SimpMessageSendingOperations;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.messaging.SessionDisconnectEvent;
@Component
@AllArgsConstructor
@Slf4j
public class WebSocketEventListener {
private final SimpMessageSendingOperations messagingTemplate;
// Listens for when a WebSocket session disconnects
@EventListener
public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) {
StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage());
String username = (String) headerAccessor.getSessionAttributes().get("username");
if (username != null) {
log.info("User Disconnected : " + username);
var chatMessage = ChatMessage.builder()
.type(MessageType.LEAVE)
.sender(username)
.build();
// Send the leave message to all clients subscribed to the public topic
messagingTemplate.convertAndSend("/topic/public", chatMessage);
}
}
}
Explanation for the above code:
Purpose:
The <span class="pink">WebSocketEventListener</span> class monitors and manages disconnections within a WebSocket-based chat application. When a user disconnects from the chat:
It captures the username associated with the disconnected session.
Logs this event to keep a record of user activity.
Constructs a "leave" message using the <span class="pink">ChatMessage</span> class, specifying the user's departure.
Broadcasts this message to all connected clients via <span class="pink">/topic/public</span>, ensuring all participants are informed about the departure and maintaining clear communication within the chat environment.
Step3: Designing the User Interface and Client-Side Logic
For testing chat apps that use WebSockets, we use web browsers instead of tools like Postman.Browsers support WebSockets directly, making it simple to test full-duplex connections.Unlike Postman, which can't simulate multiple users well or show real-time updates, browsers provide a more realistic testing environment. This helps us confirm that the chat app works smoothly in real situations, ensuring it delivers a great user experience.
We have provided basic HTML and JavaScript code to handle frontend functionality. To enhance the visual appeal of the HTML page, you can create CSS file also. It's important to note that our main focus in this article is on backend development using Spring Boot. We won't be delving into the details of frontend development or design.
These frontend files should be placed in the static folder within the resources directory of your Spring Boot project.
usernameForm: Allows users to enter their username before joining the chat.
messageForm: Enables users to type and send messages in the chat room.
These forms provide the basic user interface for entering the chat and participating in conversations.
main.js
'use strict';
var usernamePage = document.querySelector('#username-page');
var chatPage = document.querySelector('#chat-page');
var usernameForm = document.querySelector('#usernameForm');
var messageForm = document.querySelector('#messageForm');
var messageInput = document.querySelector('#message');
var messageArea = document.querySelector('#messageArea');
var connectingElement = document.querySelector('.connecting');
var stompClient = null;
var username = null;
var colors = [
'#2196F3', '#32c787', '#00BCD4', '#ff5652',
'#ffc107', '#ff85af', '#FF9800', '#39bbb0'
];
// Function to connect to the WebSocket server
function connect(event) {
username = document.querySelector('#name').value.trim();
if(username) {
usernamePage.classList.add('hidden');
chatPage.classList.remove('hidden');
var socket = new SockJS('/ws');
stompClient = Stomp.over(socket);
stompClient.connect({}, onConnected, onError);
}
event.preventDefault();
}
// Function called when WebSocket connection is successful
function onConnected() {
stompClient.subscribe('/topic/public', onMessageReceived);
stompClient.send("/app/chat.addUser",
{},
JSON.stringify({sender: username, type: 'JOIN'})
);
connectingElement.classList.add('hidden');
}
// Function called when there's an error in WebSocket connection
function onError(error) {
connectingElement.textContent = 'Could not connect to WebSocket server. Please refresh this page to try again!';
connectingElement.style.color = 'red';
}
// Function to send a message via WebSocket
function sendMessage(event) {
var messageContent = messageInput.value.trim();
if(messageContent && stompClient) {
var chatMessage = {
sender: username,
content: messageInput.value,
type: 'CHAT'
};
stompClient.send("/app/chat.sendMessage", {}, JSON.stringify(chatMessage));
messageInput.value = '';
}
event.preventDefault();
}
// Function called when a message is received from the WebSocket server
function onMessageReceived(payload) {
var message = JSON.parse(payload.body);
var messageElement = document.createElement('li');
if(message.type === 'JOIN') {
messageElement.classList.add('event-message');
message.content = message.sender + ' joined!';
} else if (message.type === 'LEAVE') {
messageElement.classList.add('event-message');
message.content = message.sender + ' left!';
} else {
messageElement.classList.add('chat-message');
var avatarElement = document.createElement('i');
var avatarText = document.createTextNode(message.sender[0]);
avatarElement.appendChild(avatarText);
avatarElement.style['background-color'] = getAvatarColor(message.sender);
messageElement.appendChild(avatarElement);
var usernameElement = document.createElement('span');
var usernameText = document.createTextNode(message.sender);
usernameElement.appendChild(usernameText);
messageElement.appendChild(usernameElement);
}
var textElement = document.createElement('p');
var messageText = document.createTextNode(message.content);
textElement.appendChild(messageText);
messageElement.appendChild(textElement);
messageArea.appendChild(messageElement);
messageArea.scrollTop = messageArea.scrollHeight;
}
function getAvatarColor(messageSender) {
var hash = 0;
for (var i = 0; i < messageSender.length; i++) {
hash = 31 * hash + messageSender.charCodeAt(i);
}
var index = Math.abs(hash % colors.length);
return colors[index];
}
usernameForm.addEventListener('submit', connect, true);
messageForm.addEventListener('submit', sendMessage, true);
Brief overview for the above code:
connect(): Establishes a WebSocket connection when a user submits their username.
onConnected(): Subscribes to public chat and announces user join after successful connection.
onError(): Displays an error message if WebSocket connection fails.
sendMessage(): Sends a chat message to the server when user submits the message form.
onMessageReceived(): Handles incoming messages, creating and displaying message elements.
getAvatarColor(): Assigns a consistent color to each user's avatar based on their username.
Testing our chat application
Demo:
When a user leaves the group chat:
Limitations of WebSocket
Everything has two sides, and WebSockets are no exception. While they offer powerful real-time capabilities, they also come with their own set of challenges. Here are the main drawbacks one should keep in mind while using them:
Server Strain:WebSockets keep connections open constantly, which can put more stress on servers. This is like keeping many phone lines open at once, even when you're not talking. It can make servers work harder and potentially slow things down if there are too many users.
Setup Complexity:Setting up WebSockets is more complicated than regular web requests. It's like installing a complex intercom system instead of just using doorbells. This can make it harder for developers to build and maintain the application.
Connection Problems:Some networks and firewalls don't play nice with WebSockets. It's similar to how some buildings might block certain types of phone calls. This can cause connection issues for some users, making the app unreliable for them.
Overkill for Simple Tasks:For apps that don't need constant updates, WebSockets can be unnecessary. It's like using a fire hose to water a small plant. This can make simple apps more complex than they need to be, potentially causing performance issues.