Spring WebSocket Example

By Arvind Rai, September 09, 2019
WebSocket protocol provides two-way communication channel between client and server over a single TCP connection. This TCP protocol works over HTTP and allows re-use of existing firewalls rules. In WebSocket protocol interaction, HTTP uses Upgrade header such as Upgrade: websocket. After successful handshake, the TCP socket remains open for both the client and the server to continue to send and receive message. The WebSocket protocol based application is useful where the combination of low latency, high frequency, and high volume is required, for example game, financial apps and chat application because they are much closer to real-time.
SockJS is a browser JavaScript library that provides a WebSocket-like object. SockJS handles the WebSocket communication even if browser does not support WebSocket protocol. SockJS first tries to use native WebSocket and if it fails then it uses a variety of browser-specific transport protocols and presents them through WebSocket-like abstractions.
STOMP is a Simple Text Orientated Messaging Protocol. Stomp client is a JavaScript library. Stomp client is instantiated using SockJS client object. Stomp client connects with STOMP message broker and can send and receive messages.
On this page we will create a chat application using Spring WebSocket. Find the complete example step-by-step.

1. Technologies Used

Find the technologies being used in our example.
1. Java 11
2. Spring 5.1.9.RELEASE
3. Spring Boot 2.1.7.RELEASE
4. Maven 3.5.2
5. Tomcat 9.0.10
6. Eclipse 2018-099

2. Project Structure

Find the project structure of our chat demo application.
Spring WebSocket Example

3. Maven Dependencies

Find the Maven dependencies of our project.
pom.xml
<?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>
	<groupId>com.concretepage</groupId>
	<artifactId>spring-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>
	<name>Spring</name>
	<description>Spring Demo Project</description>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.7.RELEASE</version>
		<relativePath />
	</parent>
	<properties>
		<context.path>spring-app</context.path>
		<java.version>11</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-data-jpa</artifactId>
		</dependency>
		<dependency>
		    <groupId>org.springframework</groupId>
		    <artifactId>spring-messaging</artifactId>
		</dependency>
		<dependency>
		    <groupId>javax.servlet</groupId>
		    <artifactId>jstl</artifactId>
		</dependency>		
	</dependencies>
	<build>
	  <plugins>
	    <plugin>
		  <groupId>org.apache.maven.plugins</groupId>
		  <artifactId>maven-war-plugin</artifactId>
		  <version>3.2.0</version>
		  <configuration>
			 <warName>${context.path}</warName>
		  </configuration>
		</plugin>
	  </plugins>
	</build>
</project> 

4. WebSocket Configuration

Find the WebSocket Java configuration class.
WebSocketConfig.java
package com.concretepage.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 configureMessageBroker(MessageBrokerRegistry registry) {
		registry.enableSimpleBroker("/topic");
		registry.setApplicationDestinationPrefixes("/chatApp");
	}

	@Override
	public void registerStompEndpoints(StompEndpointRegistry registry) {
		registry.addEndpoint("/chat").withSockJS();
	}
} 
1. EnableWebSocketMessageBroker: Enables broker-backed messaging over WebSocket. The @EnableWebSocketMessageBroker annotation is added at class level with @Configuration annotation.

2. WebSocketMessageBrokerConfigurer: Provides methods for configuring message handling with simple messaging protocols, for example STOMP, from WebSocket clients. WebSocketMessageBrokerConfigurer is implemented to customize the default configuration of @EnableWebSocketMessageBroker annotation. We are overriding its following methods.
a. configureMessageBroker() : Configures message broker options.
a. registerStompEndpoints() : Registers STOMP endpoints mapping each to a specific URL and enabling and configuring SockJS fallback options.

4.1 MessageBrokerRegistry

MessageBrokerRegistry configures message broker options. Find some of its methods.
enableSimpleBroker(String... destinationPrefixes): Enables simple message broker and configures prefixes to filter destinations, such as /topic or /queue.
enableStompBrokerRelay(String... destinationPrefixes): Enables a STOMP broker relay and configures the destination prefixes.
setApplicationDestinationPrefixes(String... prefixes): Configures one or more prefixes to filter destinations targeting application annotated methods.
setCacheLimit(int cacheLimit): Configures the cache limit to apply for registrations with the broker.

Now find the code snippet.
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
   registry.enableSimpleBroker("/topic");
   registry.setApplicationDestinationPrefixes("/chatApp");
} 
Destination prefixes in enableSimpleBroker() are used specified by STOMP such as /topic/ or /queue/. The meaning of these prefixes are decided by STOMP spec differently in different scenario. Generally in simple broker, the use of /topic/ prefix is for publish-subscribe (one-to-many) and the use of /queue/ is for point-to-point (one-to-one) message exchanges.
According to above MessageBrokerRegistry configuration,
The prefix /topic/ of URL will be used to subscribe the server by STOMP client.
The chatApp is the application destination prefix of URL to send the message by STOMP client.

4.2 StompEndpointRegistry

StompEndpointRegistry is a contract for registering STOMP over WebSocket endpoints. It has following methods.
addEndpoint(String... paths): Registers a STOMP over WebSocket endpoint at the given mapping path.
setErrorHandler(StompSubProtocolErrorHandler errorHandler): Registers error handler.
setOrder(int order): Sets the order of handler mapping used for STOMP.
setUrlPathHelper(UrlPathHelper urlPathHelper): Sets a UrlPathHelper.

In our demo, we are configuring StompEndpointRegistry as following.
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
   registry.addEndpoint("/chat").withSockJS();
} 

5. Create Controller: @MessageMapping and @SendTo

Find the controller of our chat application.
ChatAppController.java
package com.concretepage.controller;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import com.concretepage.domain.ChatInput;
import com.concretepage.domain.ChatOutput;

@Controller
public class ChatAppController {
	DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

	@RequestMapping("/start")
	public String start() {
		return "start";
	}

	@MessageMapping("/chat")
	@SendTo("/topic/output")
	public ChatOutput chat(ChatInput input) {
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		ChatOutput output = new ChatOutput();
		output.setUser(input.getUser());
		output.setMessage(input.getMessage());
		output.setDateTime(formatter.format(ZonedDateTime.now()));
		return output;
	}
} 
@MessageMapping: Performs mapping of Spring Message with message handling methods.
@SendTo: Converts method return value to Spring Message and send it to specified destination.

Find the domain classes used in our demo application.
ChatInput.java
package com.concretepage.domain;
public class ChatInput {
	private String user;
	private String message;
	//Setters and getters
} 
ChatOutput.java
package com.concretepage.domain;
public class ChatOutput {
	private String user;
	private String message;
	private String dateTime;
	//Setters and getters	
} 
Now find the Java configuration to enable Spring web application.
WebConfig.java
package com.concretepage.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.JstlView;
import org.springframework.web.servlet.view.UrlBasedViewResolver;

@Configuration
@ComponentScan("com.concretepage")
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
	@Bean
	public UrlBasedViewResolver setupViewResolver() {
		UrlBasedViewResolver resolver = new UrlBasedViewResolver();
		resolver.setPrefix("/WEB-INF/view/");
		resolver.setSuffix(".jsp");
		resolver.setViewClass(JstlView.class);
		return resolver;
	}
	@Override
	public void addResourceHandlers(ResourceHandlerRegistry registry) {
		registry.addResourceHandler("/resources/**").addResourceLocations("/resources/js/");
	}
} 
WebAppInitializer.java
package com.concretepage.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
	@Override
	protected Class<?>[] getRootConfigClasses() {
		return new Class[] { WebConfig.class };
	}
	@Override
	protected Class<?>[] getServletConfigClasses() {
		return null;
	}
	@Override
	protected String[] getServletMappings() {
		return new String[] { "/" };
	}
} 


6. WebSocket Client using SockJS and STOMP

Find the WebSocket client using SockJS and STOMP.
chat-app.js
var stompClient = null; 

function connect() {
    var socket = new SockJS('/spring-app/chat');
	stompClient = Stomp.over(socket);
    stompClient.connect({}, function(frame) {
        setConnected(true);
        console.log('Connected: ' + frame);
        stompClient.subscribe('/topic/output', function(output){
        	chatOutput(JSON.parse(output.body));
        });
    });
}

function disconnect() {
    stompClient.disconnect();
    setConnected(false);
    console.log("Disconnected");
}

function setConnected(connected) {
    document.getElementById('connect').disabled = connected;
    document.getElementById('disconnect').disabled = !connected;
    document.getElementById('chatBlock').style.visibility = connected ? 'visible' : 'hidden';
    document.getElementById('chatResponse').innerHTML = '';
}

function sendMssage() {
    var user = document.getElementById('user').value;
    var message = document.getElementById('message').value;
    stompClient.send("/chatApp/chat", {}, JSON.stringify({ 'user': user, 'message': message }));
    document.getElementById('message').value="";
}

function chatOutput(jsonMsg) {
    var response = document.getElementById('chatResponse');
    var p = document.createElement('p');
	message = jsonMsg.user + " ("+ jsonMsg.dateTime +"): " + jsonMsg.message;
    p.appendChild(document.createTextNode(message));
    response.appendChild(p);
} 
start.jsp
<!DOCTYPE html>
<html>
<head>
    <title>Chat App</title>
    <script src="resources/lib/sockjs.min-1.4.0.js"></script>
    <script src="resources/lib/stomp.min-2.3.3.js"></script>
    <script src="resources/chat-app.js"></script>
</head>
<body>
<h1>Chat App</h1>
<div>
    <div>
        <button id="connect" onclick="connect();">Connect to Chat Room</button>  
        <button id="disconnect" disabled="disabled" onclick="disconnect();">Disconnect</button><br/><br/>
    </div>
    <div id="chatBlock" style="visibility:hidden">
        <label>User Name: </label><input type="text" id="user"/><br/><br/>
        <label>Message: </label><textarea id="message" /></textarea><br/><br/>
        <button id="sendNum" onclick="sendMssage();">Send</button>
        <p id="chatResponse"></p>
    </div>
</div>
</body>
</html> 

7. WebSocket Flow of Messages

After code deployment, access application using URL
http://localhost:8080/spring-app/start 
1. Click on the Connect to Chat Room button. The following HTTP request will execute.
/spring-app/chat/* 
The following URL will be subscribed to get response.
/topic/output 
A WebSocket request begins with an HTTP request that uses the HTTP Upgrade header to upgrade or to switch WebSocket protocol. Find the sample request header for such interaction.
WebSocket Request Header
Sec-WebSocket-Version: 13
Origin: http://localhost:8080
Sec-WebSocket-Extensions: permessage-deflate
Sec-WebSocket-Key: K2ev2DlQSUTDPP2iAQZ5rQ==
Connection: keep-alive, Upgrade
Cookie: JSESSIONID=92AFFFD5E72689B2D0009B907707FA81
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
 
The response header does not contain 200 status code. We receive 101 Switching Protocols status code. The response header will be as following.
WebSocket Response Header
Upgrade: websocket
Connection: upgrade
Sec-WebSocket-Accept: 6k7RTzp96YreGzxBkgi1UUDud14=
Sec-WebSocket-Extensions: permessage-deflate 
2. Fill data in user name and message field and click on Send button. Message will be sent to WebSocket server using the below URL.
/chatApp/chat 
The response will be sent on subscribed destination i.e.
/topic/output 

8. Run Application

To test our demo application, find the steps.
1. Download source code from the download link given at the end of the article.
2. Go to the root directory of the project using command prompt.
3. Build the project using Maven with following command.
mvn clean package 
We will find WAR file in the target directory.
4. Deploy the WAR file in tomcat.
5. Access the following URL.
http://localhost:8080/spring-app/start 
Open two windows and click connect and chat. Find the print screen.
Spring WebSocket Example

9. References

Spring Doc: WebSockets
Using WebSocket to build an interactive web application

10. Download Source Code

POSTED BY
ARVIND RAI
ARVIND RAI
LEARN MORE








©2024 concretepage.com | Privacy Policy | Contact Us