0% found this document useful (0 votes)
340 views21 pages

A Tutorial On Implementing A Chat Application Like Whatsapp Using Websocket and Spring

This document provides an overview of how to implement a group chat application like WhatsApp using WebSocket and Spring. It discusses what WebSockets are and how they allow for bidirectional communication between client and server without closing the connection. It then describes how to set up a Spring Boot project to enable WebSocket messaging, create a controller to handle message events, define a message model, and configure Stomp endpoints and an in-memory message broker. The goal is to allow clients to subscribe to a chat room topic to receive messages in real-time from other connected clients.

Uploaded by

Lincoln
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
340 views21 pages

A Tutorial On Implementing A Chat Application Like Whatsapp Using Websocket and Spring

This document provides an overview of how to implement a group chat application like WhatsApp using WebSocket and Spring. It discusses what WebSockets are and how they allow for bidirectional communication between client and server without closing the connection. It then describes how to set up a Spring Boot project to enable WebSocket messaging, create a controller to handle message events, define a message model, and configure Stomp endpoints and an in-memory message broker. The goal is to allow clients to subscribe to a chat room topic to receive messages in real-time from other connected clients.

Uploaded by

Lincoln
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 21

Skip to content

Search
The Null Pointer Exception
All thing coding
Open Search

A Tutorial on Implementing a Chat


Application like Whatsapp using WebSocket
and Spring

What is Websocket?
When we run a server side application, we run it on a particular physical port e.g. 8080, 8081.
And to access the server side application, we use an IP. Similarly, when we log in to our browser
and ask for a particular site, we send to the request our computer’s IP as well as dynamically
generated port number. So, we have four items which helps us complete communication between
our computer and the server and these four items are unique for every request.
1. Server IP address => It is hidden in URL given to client and known to the
client.

2. Server port => It is also hidden in URL given to us and to the client.

3. Client IP address => Unique for every client

4. Client port => Unique for every client and is generated dynamically
When a client wants to connect to the server, a TCP socket is created to represent that connection
at server side. Then, the client sends a packet from the client IP address and from the unique
client port number. When the server gets the packet on its own port number, it stores the client IP
address and the particular client port number. This separates that client’s traffic from all the other
currently connected sockets. Server now triggers an event for that particular socket e.g. fetch the
nearest cabs.
The server now wants to send a response to that client. Server derives the client’s IP address and
client port number from its stored data and sends it back.
HTTP requests that we are more familiar with does what we just described above. After the
response is sent back, it closes the connection. At all times, clients request for the data to server
and server returns back the data. Also, there is high overload of initiating a connection.
What happens when we chat or make a tool like google docs?
Client can ask for the data by making a HTTP request at a repeated interval. But this won’t be
near time, comes at high overhead of making connection every time and developers are left with
lots of corner cases to solve.
– My friend Prathamesh, Amit and friends at work
Now, WebSocket says don’t close that connection until client says so. WebSocket connections
start out with an HTTP connection and contains an “upgrade” header requesting the server to
upgrade the protocol from HTTP to WebSocket. If the server agrees to the upgrade, then it
returns a response that indicates that the protocol will be changed to the WebSocket protocol. In
a way, both server and client agrees to change the way they were talking, from HTTP to
WebSocket . Meanwhile the same port is servicing other WebSocket as well as HTTP request.
After a WebSocket connection is established, server and client can talk bidirectionally and in any
order; there is no concept of request and response.
What is STOMP?
STOMP is an abbreviation of Simple Text-Orientated Messaging Protocol. It defines an message
semantics so that any of the available STOMP clients can communicate with any STOMP
message broker. This enables us to provide easy and widespread messaging interoperability
among languages and platforms. The said semantics are for following operations:
 Connect
 Subscribe
 Unsubscribe
 Send
 Transaction
Implementation of Group Chat Using Spring boot
We will implement a whatspp group like chat. This chat app will have following two screen.
This project can be easily converted to peer to peer chat. View or clone this project at github.
Login screen
chat screen
Generate the project
Log on to https://start.spring.io/. You will see following screen.
Spring Project generator
Enter your preference. I generated a maven project. And added Websocket ad the dependency.
You can fill up your own artifact id and group id. Click Generate Project and you will see a
project getting downloaded soon.
Enabling this project as WebSocket Project
Spring allows us to create a configuration class that enables the project as WebSocket Project.
Following is my configuration class:

package com.nulpointerexception.npechatroom;
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 WebSocketConfiguration implements WebSocketMessageBrokerConfigurer {
/**
*
* @param config
* Here we have enabled simple in memory message broker. We can register rabbit MQ also as
message broker
* by using the MessageBrokerRegistry config methods/
*/
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/chat-room");
config.setApplicationDestinationPrefixes("/chat-app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/sock").setAllowedOrigins("*").withSockJS();
}
}

view raw WebSocketConfiguration.java hosted with by GitHub


WebSocketConfiguration class is annotated with @Configuration to indicate that it is a Spring
configuration class. It is also annotated @EnableWebSocketMessageBroker that enables this
project for WebSocket message handling, backed by a message broker. This class implements
WebSocketMessageBrokerConfigurer which has methods to tell the project which
MessageBroker has to be used, what is the endpoint for our webSocket. In our case, we have told
project to use in-memory message broker. We could have configured external message broker
such as rabbit MQ using the configureMessageBroker() method. We have also configured a
message broker address as /chat-room which is where client will subscribe themselves. That
means any message that will be sent to the /chat-room will be automatically read by all the
subscribed clients.
It also designates the /chat-app prefix for messages that are bound for the server. For example,
when we will send message, this prefix will be added to our send message address. The
registerStompEndpoints() method registers the /sock endpoint to enable SockJS fallback options
so as to use alternate transports if WebSocket is not available. This will be more clear when we
write about the client.
Creating a event-handling controller
STOMP events can be routed to @Controller classes. For example, we will want methods
which can be exposed to the client to add user to the chat or send message. Add this controller:

package com.nulpointerexception.npechatroom;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.DestinationVariable;
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.messaging.simp.SimpMessageSendingOperations;
import org.springframework.stereotype.Controller;
import static java.lang.String.format;
@Controller
public class ChatRoomController {
private static final Logger logger = LoggerFactory.getLogger(ChatRoomController.class);
@Autowired
private SimpMessageSendingOperations messagingTemplate;
@MessageMapping("/chat/{roomId}/sendMessage")
public void sendMessage(@DestinationVariable String roomId, @Payload Message
chatMessage) {
logger.info(roomId+" Chat messahe recieved is "+chatMessage.getContent());
messagingTemplate.convertAndSend(format("/chat-room/%s", roomId), chatMessage);
}
@MessageMapping("/chat/{roomId}/addUser")
public void addUser(@DestinationVariable String roomId, @Payload Message chatMessage,
SimpMessageHeaderAccessor headerAccessor) {
String currentRoomId = (String) headerAccessor.getSessionAttributes().put("room_id", roomId);
if (currentRoomId != null) {
Message leaveMessage = new Message();
leaveMessage.setType(Message.MessageType.LEAVE);
leaveMessage.setSender(chatMessage.getSender());
messagingTemplate.convertAndSend(format("/chat-room/%s", currentRoomId), leaveMessage);
}
headerAccessor.getSessionAttributes().put("name", chatMessage.getSender());
messagingTemplate.convertAndSend(format("/chat-room/%s", roomId), chatMessage);
}
}

view raw ChatRoomController.java hosted with by GitHub


/Here, The @MessageMapping annotation ensures that if a message is sent to destination
/chat/{roomId}/sendMessage, sendMessage() method is called. Here, the destination is
dynamically generated. The method in our case, sends the message to the message broker at the
/chat-room/{roomId}. Now all the subscribed client at the said topic will get message
automatically.
Creating a model of event

package com.nulpointerexception.npechatroom;
public class Message {
public enum MessageType {
CHAT, JOIN, LEAVE
}
private MessageType messageType;
private String content;
private String sender;
public MessageType getType() {
return messageType;
}
public void setType(MessageType messageType) {
this.messageType = messageType;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getSender() {
return sender;
}
public void setSender(String sender) {
this.sender = sender;
}
}

view raw Message.java hosted with by GitHub


This is how we will send the event to the controller.
Dependencies

<?xml version="1.0" encoding="UTF-8"?>


<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-
4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.nulpointerexception</groupId>
<artifactId>npe-chatroom</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>npe-chatroom</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

view raw pom.xml hosted with by GitHub


Main Class

package com.nulpointerexception.npechatroom;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class NpeChatroomApplication {
public static void main(String[] args) {
SpringApplication.run(NpeChatroomApplication.class, args);
}
}

view raw NpeChatroomApplication.java hosted with by GitHub


Browser HTML page to serve as client

<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
integrity="sha384-
ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
crossorigin="anonymous">
<title>Hello, world!</title>
</head>
<body>
<div id="userJoin" class="container">
<br>
<br>
<div class="card">
<div class="card-body">
<h1>My Chat App Example - nulPointerException.com</h1>
<a class="btn btn-primary" href="https://nulpointerexception.com/" role="button">More
tutorials at nulPointerException.com</a>
</div>
</div>
<br>
<br>
<form id="userJoinForm" name="userJoinForm">
<div class="form-group">
<label for="name">Enter Name:</label>
<input type="text" class="form-control" id="name" aria-describedby="name"
placeholder="Enter name">
</div>
<div class="form-group">
<label for="room">Enter Room:</label>
<input type="text" class="form-control" id="room" aria-describedby="exampleInputRoom"
placeholder="Enter room">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
<div id="chatPage" class="container d-none">
<div class="card">
<div class="card-body">
<h1>My Chat App Example - nulPointerException.com</h1>
<a class="btn btn-primary" href="https://nulpointerexception.com/" role="button">More
tutorials at nulPointerException.com</a>
</div>
</div>
<div class="chat-header">
<h2>Chatroom [<span id="room-id-display"></span>]</h2>
</div>
<div class="waiting">
We are waiting to enter the room.
</div>
<div class="card">
<div class="card-body">
<ul id="messageArea">
</div>
</div>
</ul>
<form id="messagebox" name="messagebox">
<div class="form-group">
<label for="message">Enter Message:</label>
<input type="text" class="form-control" id="message" aria-describedby="name"
placeholder="Enter message to chat ....">
</div>
<button type="submit" class="btn btn-primary">Send</button>
</form>
</div>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.3.0/sockjs.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.js"></script>
<script src="https://cdn.jsdelivr.net/npm/js-cookie@2/src/js.cookie.min.js"></script>
<script src="/js/mychat.js"></script>
</body>
</html>

view raw index.html hosted with by GitHub


Associated JS file

'use strict';
var stompClient = null;
var usernamePage = document.querySelector('#userJoin');
var chatPage = document.querySelector('#chatPage');
var room = $('#room');
var name = $("#name").val().trim();
var waiting = document.querySelector('.waiting');
var roomIdDisplay = document.querySelector('#room-id-display');
var stompClient = null;
var currentSubscription;
var topic = null;
var username;
function connect(event) {
var name1 = $("#name").val().trim();
Cookies.set('name', name1);
usernamePage.classList.add('d-none');
chatPage.classList.remove('d-none');
var socket = new SockJS('/sock');
stompClient = Stomp.over(socket);
stompClient.connect({}, onConnected, onError);
event.preventDefault();
}
function onConnected() {
enterRoom(room.val());
waiting.classList.add('d-none');
}
function onError(error) {
waiting.textContent = 'uh oh! service unavailable';
}
function enterRoom(newRoomId) {
var roomId = newRoomId;
Cookies.set('roomId', room);
roomIdDisplay.textContent = roomId;
topic = `/chat-app/chat/${newRoomId}`;
currentSubscription = stompClient.subscribe(`/chat-room/${roomId}`, onMessageReceived);
var username = $("#name").val().trim();
stompClient.send(`${topic}/addUser`,
{},
JSON.stringify({sender: username, type: 'JOIN'})
);
}
function onMessageReceived(payload) {
}
function sendMessage(event) {
var messageContent = $("#message").val().trim();
var username = $("#name").val().trim();
var newRoomId = $('#room').val().trim();
topic = `/chat-app/chat/${newRoomId}`;
if(messageContent && stompClient) {
var chatMessage = {
sender: username,
content: messageContent,
type: 'CHAT'
};
stompClient.send(`${topic}/sendMessage`, {}, JSON.stringify(chatMessage));
document.querySelector('#message').value = '';
}
event.preventDefault();
}
function onMessageReceived(payload) {
var message = JSON.parse(payload.body);
var messageElement = document.createElement('li');
var divCard = document.createElement('div');
divCard.className = 'card';
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);
messageElement.appendChild(avatarElement);
var usernameElement = document.createElement('span');
var usernameText = document.createTextNode(message.sender);
usernameElement.appendChild(usernameText);
messageElement.appendChild(usernameElement);
var divCardBody = document.createElement('div');
divCardBody.className = 'card-body';
divCardBody.appendChild(messageElement);
divCard.appendChild(divCardBody);
}
var textElement = document.createElement('p');
var messageText = document.createTextNode(message.content);
textElement.appendChild(messageText);
messageElement.appendChild(textElement);
var messageArea = document.querySelector('#messageArea');
messageArea.appendChild(divCard);
messageArea.scrollTop = messageArea.scrollHeight;
}
$(document).ready(function() {
userJoinForm.addEventListener('submit', connect, true);
messagebox.addEventListener('submit', sendMessage, true);
});

view raw mychat.js hosted with by GitHub


Final Project Structure
Run the project
In the project directory:
mvn package
java -jar target/npe-chatroom-0.0.1-SNAPSHOT.jar
You got to replace your jar file name in case its different.
Debugging
Posting screenshot of my terminal log while the application starts. Please notice the highlighted
log in your logs.
Clone the project from github
Github Link of the project

Reference and Further Reading


STOMP
SO answer on understanding websocket
Callicoder
ddycai on github
Spring using WebSocket
A sincere thanks to Rajeev Singh at Callicoder for his awesome tutorial.
Share this:
 Twitter
 Facebook

Loading...

Published by Harsh Vardhan


Aspiring story teller and painter View all posts by Harsh Vardhan
2019
Programming
a beginner tutorial on chat, A Tutorial to implement chat, chat application using java, chat using
java, example of websocket, how to implement a websocket, implement chatroom, implement
group chat, implement whatspp like chat, Implementing a Chat Application, java tutorial, spring
tutorial, spring tutorial using websocket, tutorial on Websocket, what is stomp, What is
Websocket, Whatsapp like chat, whatsapp like chat using java
Post navigation
A Beginner Guide To Decorator Pattern
A Beginner Tutorial For Elasticsearch – 1
2 thoughts on “A Tutorial on Implementing a Chat
Application like Whatsapp using WebSocket and Spring”
Add yours
1. Mariusz
2019 at 6:45 am

Github link is empty


Liked by 1 person
Reply

1. HV
2019 at 7:52 am
Sorry about that. It’s updated.
Like
Reply
Leave a Reply

Follow Blog via Email


Enter your email address to follow this blog and receive notifications of new posts by email.

Follow Blog via Email


Enter your email address to follow this blog and receive notifications of new posts by email.
Join 22 other followers

Blog at WordPress.com.
Up ↑

You might also like