Define the domain class WebSocketChatMessage as follows-
package com.javainuse.domain;
public class WebSocketChatMessage {
private String type;
private String content;
private String sender;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
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;
}
}
Define the WebSocket Configuration class.
@Configuration tells that it is a Spring configuration class.
@EnableWebSocketMessageBroker enables WebSocket message handling, backed by a message broker. Here we are using STOMP as a mesage broker.
The method configureMessageBroker() enables a rabbitmq message broker to carry
the messages back to the client on destinations prefixed with "/topic" and "/queue".
Also here we have configured that all messages with "/app" prefix will be routed to @MessageMapping-annotated methods in controller class.
For example "/app/chat.sendMessage" is the endpoint that the WebSocketController.sendMessage()
method is mapped to handle.
package com.javainuse.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.*;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketChatConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/websocketApp").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
registry.enableStompBrokerRelay("/topic").setRelayHost("localhost").setRelayPort(61613).setClientLogin("guest")
.setClientPasscode("guest");
}
}
Define the WebSocker Listener class.
This class listens to events such as a new user joining the chat or an user leaving the chat.
package com.javainuse.config;
import org.springframework.beans.factory.annotation.Autowired;
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.SessionConnectedEvent;
import org.springframework.web.socket.messaging.SessionDisconnectEvent;
import com.javainuse.domain.WebSocketChatMessage;
@Component
public class WebSocketChatEventListener {
@Autowired
private SimpMessageSendingOperations messagingTemplate;
@EventListener
public void handleWebSocketConnectListener(SessionConnectedEvent event) {
System.out.println("Received a new web socket connection");
}
@EventListener
public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) {
StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage());
String username = (String) headerAccessor.getSessionAttributes().get("username");
if(username != null) {
WebSocketChatMessage chatMessage = new WebSocketChatMessage();
chatMessage.setType("Leave");
chatMessage.setSender(username);
messagingTemplate.convertAndSend("/topic/public", chatMessage);
}
}
}
Define the controller class.
Previously we have configured the websocket such that all messages coming from the client with prefix "/app" will be routed to the appropriate message handling methods annotated with @MessageMapping.
For example, a message with destination /app/chat.newUser will be routed to the newUser() method, and a message with destination /app/chat.sendMessage will be routed to the sendMessage() method.
package com.javainuse.controller;
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 com.javainuse.domain.WebSocketChatMessage;
@Controller
public class WebSocketChatController {
@MessageMapping("/chat.sendMessage")
@SendTo("/topic/javainuse")
public WebSocketChatMessage sendMessage(@Payload WebSocketChatMessage webSocketChatMessage) {
return webSocketChatMessage;
}
@MessageMapping("/chat.newUser")
@SendTo("/topic/javainuse")
public WebSocketChatMessage newUser(@Payload WebSocketChatMessage webSocketChatMessage,
SimpMessageHeaderAccessor headerAccessor) {
headerAccessor.getSessionAttributes().put("username", webSocketChatMessage.getSender());
return webSocketChatMessage;
}
}
Finally Define the Spring Boot Class with @SpringBootApplication annotation
package com.javainuse;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootChatApplication {
public static void main(String[] args) {
SpringApplication.run(
SpringBootChatApplication.class , args);
}
}
Define the index.html. Here we have defined the UI for our chat application.
Also it makes use of the sockjs and stomp libraries.
The HTML file contains the user interface for displaying the chat messages. It includes sockjs and stomp javascript libraries.
SockJS is a browser JavaScript library that provides a WebSocket-like object. SockJS gives you a coherent, cross-browser, Javascript API which creates a low latency, full duplex, cross-domain communication channel between the browser and the web server.
STOMP JS is the stomp client for javascript.
<!DOCTYPE html>
<html>
<head>
<meta name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
<title>JavaInUse Chat Application | JavaInUse</title>
<link rel="stylesheet" href="/css/style.css" />
<link
href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css"
rel="stylesheet" id="bootstrap-css">
<script
src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
</head>
<body>
<div id="welcome-page">
<div class="welcome-page-container">
<h1 class="title">Welcome - To join the chat group enter your
name</h1>
<form id="welcomeForm" name="welcomeForm">
<div class="form-group">
<input type="text" id="name" placeholder="name"
class="form-control" />
</div>
<div class="form-group">
<button type="submit" onclass="accent username-submit">Lets
Begin</button>
</div>
</form>
</div>
</div>
<div id="dialogue-page" class="hidden">
<div class="dialogue-container">
<div class="dialogue-header">
<h2>JavaInUse Chat Application</h2>
</div>
<ul id="messageList">
</ul>
<form id="dialogueForm" name="dialogueForm" nameForm="dialogueForm">
<div class="form-group">
<div class="input-group clearfix">
<input type="text" id="chatMessage"
placeholder="Enter a message...." autocomplete="off"
class="form-control" />
<button type="submit" class="glyphicon glyphicon-share-alt">Send</button>
</div>
</div>
</form>
</div>
</div>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.1.4/sockjs.min.js"></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
<script src="/js/script.js"></script>
</body>
</html>
Define the javascript file.
The
stompClient.subscribe() function takes a callback method which is called whenever a message arrives on the subscribed topic.
The
connect() function makes use of the SockJS and stomp client to establish connection to the to the /websocketApp endpoint that we configured in Spring Boot application.
The client subscribes to /topic/javainuse destination.
'use strict';
document.querySelector('#welcomeForm').addEventListener('submit', connect, true)
document.querySelector('#dialogueForm').addEventListener('submit', sendMessage, true)
var stompClient = null;
var name = null;
function connect(event) {
name = document.querySelector('#name').value.trim();
if (name) {
document.querySelector('#welcome-page').classList.add('hidden');
document.querySelector('#dialogue-page').classList.remove('hidden');
var socket = new SockJS('/websocketApp');
stompClient = Stomp.over(socket);
stompClient.connect({}, connectionSuccess);
}
event.preventDefault();
}
function connectionSuccess() {
stompClient.subscribe('/topic/javainuse', onMessageReceived);
stompClient.send("/app/chat.newUser", {}, JSON.stringify({
sender : name,
type : 'newUser'
}))
}
function sendMessage(event) {
var messageContent = document.querySelector('#chatMessage').value.trim();
if (messageContent && stompClient) {
var chatMessage = {
sender : name,
content : document.querySelector('#chatMessage').value,
type : 'CHAT'
};
stompClient.send("/app/chat.sendMessage", {}, JSON
.stringify(chatMessage));
document.querySelector('#chatMessage').value = '';
}
event.preventDefault();
}
function onMessageReceived(payload) {
var message = JSON.parse(payload.body);
var messageElement = document.createElement('li');
if (message.type === 'newUser') {
messageElement.classList.add('event-data');
message.content = message.sender + 'has joined the chat';
} else if (message.type === 'Leave') {
messageElement.classList.add('event-data');
message.content = message.sender + 'has left the chat';
} else {
messageElement.classList.add('message-data');
var element = document.createElement('i');
var text = document.createTextNode(message.sender[0]);
element.appendChild(text);
messageElement.appendChild(element);
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);
document.querySelector('#messageList').appendChild(messageElement);
document.querySelector('#messageList').scrollTop = document
.querySelector('#messageList').scrollHeight;
}
Define the CSS-
{
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
html, body {
height: 100%;
overflow: hidden;
}
body {
margin: 0;
padding: 0;
font-weight: 400;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 1rem;
line-height: 1.58;
color: #333;
background-color: #f4f4f4;
height: 100%;
}
.clearfix:after {
display: block;
content: "";
clear: both;
}
.hidden {
display: none;
}
input {
padding-left: 10px;
outline: none;
}
h1, h2, h3, h4, h5, h6 {
margin-top: 20px;
margin-bottom: 20px;
}
h1 {
font-size: 1.7em;
}
a {
color: #128ff2;
}
button {
box-shadow: none;
border: 1px solid transparent;
font-size: 14px;
outline: none;
line-height: 100%;
white-space: nowrap;
vertical-align: middle;
padding: 0.6rem 1rem;
border-radius: 2px;
transition: all 0.2s ease-in-out;
cursor: pointer;
min-height: 38px;
}
button.default {
background-color: #e8e8e8;
color: #333;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.12);
}
button.primary {
background-color: #128ff2;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.12);
color: #fff;
}
}
button.accent {
background-color: #ff4743;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.12);
color: #fff;
}
#welcome-page {
text-align: center;
}
.welcome-page-container {
background-color: grey;
width: 100%;
max-width: 500px;
display: inline-block;
margin-top: 42px;
vertical-align: middle;
position: relative;
padding: 35px 55px 35px;
min-height: 250px;
position: absolute;
top: 50%;
left: 0;
right: 0;
margin: 0 auto;
margin-top: -160px;
}
#dialogue-page {
position: relative;
height: 100%;
}
.dialogue-container {
background-color: green;
margin: 10px 0;
max-width: 700px;
margin-left: auto;
margin-right: auto;
box-shadow: 0 1px 11px rgba(0, 0, 0, 0.27);
margin-top: 30px;
height: calc(100% - 60px);
max-height: 600px;
position: relative;
}
#dialogue-page ul {
list-style-type: none;
background-color: #FFF;
margin: 0;
overflow: auto;
overflow-y: scroll;
padding: 0 20px 0px 20px;
height: calc(100% - 150px);
}
#dialogue-page #dialogueForm {
padding: 20px;
}
#dialogue-page ul li {
line-height: 1.5rem;
padding: 10px 20px;
margin: 0;
border-bottom: 1px solid #f4f4f4;
}
#dialogue-page ul li p {
margin: 0;
}
#dialogue-page .event-data {
width: 100%;
text-align: center;
clear: both;
}
#dialogue-page .event-data p {
color: #777;
font-size: 14px;
word-wrap: break-word;
}
#dialogue-page .message-data {
padding-left: 68px;
position: relative;
}
#dialogue-page .message-data i {
position: absolute;
width: 42px;
height: 42px;
overflow: hidden;
left: 10px;
display: inline-block;
vertical-align: middle;
font-size: 18px;
line-height: 42px;
color: #fff;
text-align: center;
border-radius: 50%;
font-style: normal;
text-transform: uppercase;
}
#dialogue-page .message-data span {
color: #333;
font-weight: 600;
}
#dialogue-page .message-data p {
color: #43464b;
}
#dialogueForm .input-group input {
border: 0;
padding: 10px;
background: whitesmoke;
float: left;
width: calc(100% - 85px);
}
#dialogueForm .input-group button {
float: left;
width: 80px;
height: 38px;
margin-left: 5px;
}
.dialogue-header {
text-align: center;
padding: 15px;
border-bottom: 1px solid #ececec;
}
.dialogue-header h2 {
margin: 0;
font-weight: 500;
}
@media screen and (max-width: 730px) {
.dialogue-container {
margin-left: 10px;
margin-right: 10px;
margin-top: 10px;
}
}
We are done with the required Java code. Now lets start RabbitMQ. As we
had explained in detail in the
Getting started with
RabbitMQ perform the steps to start the RabbitMQ.
We will need to perform one additional step with RabbitMQ - Install STOMP plugin for RabbitMQ so that it can work
with STOMP Messages
Next start the Spring Boot Chat application by running it as a Java Application.
Hit the url as follows-
http://localhost:8080
Enter the username
We are then shown the chat window.
If we got the rabbitMQconsole we can see it has created a queue.
Download Source Code
Download it -
Spring Boot + WebSocket + RabbitMQ Chat Example