A Tutorial On Implementing A Chat Application Like Whatsapp Using Websocket and Spring
A Tutorial On Implementing A Chat Application Like Whatsapp Using Websocket and Spring
Search
The Null Pointer Exception
All thing coding
Open Search
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.
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();
}
}
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);
}
}
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;
}
}
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);
}
}
<!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>
'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);
});
1. HV
2019 at 7:52 am
Sorry about that. It’s updated.
Like
Reply
Leave a Reply
Blog at WordPress.com.
Up ↑