Let us again walk through creating websocket connection in spring boot but this time with STOMP protocol. We will be creating a sample example project to perform all the communications over websocket protocol between a client and a server. We will also integrate Spring security to secure our websocket connections that restricts websocket server reply to a single client(queue) instead of broadcasting to all clients(topic). Here, we will be using spring boot configurations to configure websocket connection with STOMP, sockJS and spring security.
Here is my previous post that describes spring websocket connection without STOMP and Spring boot websocket angular example.
Table of Contents
- 1. What is WebSocket
- 2. What is STOMP
- 3. Spring Boot Websocket Project Setup
- 4. Maven Dependencies
- 5. Spring Boot Websocket Configuration
- 6. Websocket Listeners and Interceptors
- 7. Websocket Endpoint
- 8. Websocket Client
- 9. Websocket SockJS Config
- 10. Run Websocket Application
What is WebSocket
Like http, websocket is also a communication protocol which provides bi-directional, full-duplex communication channel between a server and client. Once, a WebSocket connection is established between a client and a server, both the parties can exchange information endlessly until the connection is closed by anyone of the parties.
This is the main reason why WebSocket is preferred over HTTP where the client and server need to exchange information at high frequency and with low latency because HTTP connection is closed once a request is served by the server and there is a time constraint to open an HTTP connection again.
Also, the WebSocket protocol is bi-directional i.e. a client can subscribe for some event and server can publish the event to the client based on the availability of the event in the server.
While talking about WebSocket, let us also talk about Server Sent Events introduced in HTML 5. Many times, we may not really require WebSocket connection and these server sent events might serve our purpose.
Server-Sent Events (SSE) is a server push technology enabling a browser to receive automatic updates from a server via HTTP connection. In a server-sent event, a web page automatically gets updates from a server and the server produces the response in a text/event-stream format.
What is STOMP
STOMP stands for Streaming Text Oriented Messaging Protocol. As per wiki, STOMP is a simple text-based protocol, designed for working with message-oriented middleware (MOM).
STOMP provides an interoperable wire format that allows STOMP clients to talk with any message broker supporting the protocol.Spring provides default support for it but you are open to choose any other messaging protocol such as RabbitMQ or ActiveMQ.
Spring Boot Websocket Project Setup
- 1. JDK 8
- 2. Spring Boot
- 3. Intellij Idea/ eclipse
- 4. Tomcat(embedded with Spring)
- 5. Maven
Maven Dependencies
Below is the maven dependencies that we require to get started with this example.
spring-boot-starter-websocket - Provides useful defaults for websocket.
pom.xml<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.3.3.RELEASE</version> </parent> <properties> <tomcat.version>8.0.3</tomcat.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-security</artifactId> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> </dependency> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </dependencies>
Other Interesting Posts
- Maintaining Spring Session during Websocket Connection
- Websocket spring Boot Integration without STOMP
- Password Encoding using Bcrypt Encoder
- Spring Boot Security Redirect after Login with complete JavaConfig
- Spring Security Hibernate Example with complete JavaConfig
- Securing REST API with Spring Security Basic Authentication
- Spring WebFlux Reactive REST API
- Spring MVC Angularjs Integration with complete JavaConfig
Spring Boot Websocket Configuration
Below are the steps that we will follow to integrate websocket with spring boot app.
- Register STOMP endpoint at
/greeting
. This endpoint will be used by the client app (app.js) to connect to STOMP. At this point, handover to websocket protocol from http protocol is done. Hence, this is the point where the Principal on the HttpServletRequest will be handed off to WebSockets. - Next, Enable a simple message broker and configure one or more prefixes to filter destinations targeting the broker (e.g. destinations prefixed with "/topic" or "/queue"). The client app will subscribe mesages at endpoints starting with these configured endpoint.
- Now, set application destination prefixes e.g.
/app
. The client will send messages at this endpoint. For example, if client sends message at/app/message
, the endpoint configured at/message
in the spring controller will be invoked.
After these configuration steps, we can write our controller class to start the communication over websocket protocol. Now, let us implement this steps in spring boot app.
First of all let us configure our STOMP message broker. In WebSocketConfig.java
we have defined our message broker STOMP endpoint and websocket application endpoint.
@Configuration : It indicates that it is a Spring configuration class.
@EnableWebSocketMessageBroker : It enables WebSocket message handling, backed by a message broker. Here we are using STOMP as a mesage broker.
The method configureMessageBroker() enables a simple memory-based message broker to carry the messages back to the client on destinations prefixed with "/topic" and "/queue".
It also designates the "/app" prefix for messages that are bound for @MessageMapping-annotated methods in controller class. This prefix will be used to define all the message mappings; for example, "/app/message" is the endpoint that the WebSocketController.processMessageFromClient() method is mapped to handle.
Similarly,registerStompEndpoints() enables STOMP support and registers stomp endoints at "/greeting".Doing so, all the websocket messages will be channelised through STOMP and this also adds an extra layer of security to the websocket endpoint.
Remember that, while creating websocket connection from javascipt, we will be using this particular stomp endpoint only.
In the configuration below to enable SockJs support to provide deafult falback option, we need to make following changes.
registry.addEndpoint("/greeting").withSockJS();
The advantage of using sockJS here is whenever the websocket connection is disconnected or the websocket connection can not be established,then the connection will be downgraded to HTTP and the communication between client and server can still continue.
WebSocketConfig.javapackage com.devglan; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; @Configuration @EnableWebSocketMessageBroker public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/topic/", "/queue/"); config.setApplicationDestinationPrefixes("/app"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/greeting");; } }
Websocket Listeners and Interceptors
Apart from above configuration, spring boot also provides mechanism to register different listeners or interceptors and this configuration can be added in registerStompEndpoints() methods defined above.
SessionConnectEvent - Whenever a WebSocket client using a Simple Messaging Protocolas raises a connect request.
SessionConnectEvent -Whenever a websocket client is connected.
SessionConnectEvent -A connected event represents the server response to a client's connect request.
SessionSubscribeEvent -Whenever a connected client sends a subscription request to server.
SessionUnsubscribeEvent -Whenever a connected client sends a request to to remove a subscription.
There are lot of such event provided by spring websocket API.Also, there are many interceptors provided by spring which can be configured in above config class.Following is an example to register HandshakeInterceptor from my one of the post.
public class HttpHandshakeInterceptor implements HandshakeInterceptor { @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Mapattributes) throws Exception { if (request instanceof ServletServerHttpRequest) { ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request; HttpSession session = servletRequest.getServletRequest().getSession(); attributes.put("sessionId", session.getId()); } return true; } public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) { } }
Above interceptor can be registered as below:
registry.addEndpoint("/greeting").addInterceptors(new HttpHandshakeInterceptor());
Websocket Endpoint
Now let us define our controller and map the websocket connection endpoint.
@MessageMapping: This isto map the message headed for the url /message
@SendToUser: indicates that the return value of a message-handling method should be sent as a org.springframework.messaging.Message
to the specified destination(s) prepended with "/user/{username}" where the user name is extracted from the headers of the input message being handled.
@MessageExceptionHandler: throw any exceptions caused by STOMP to the end user on the /queue/errors destination.
@Payload: to extract the payload of a message and optionally convert it using spring MessageConverter.
WebSocketController.javapackage com.devglan.controller; import java.security.Principal; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.handler.annotation.MessageExceptionHandler; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.Payload; import org.springframework.messaging.simp.SimpMessageSendingOperations; import org.springframework.messaging.simp.annotation.SendToUser; import org.springframework.stereotype.Controller; import com.google.gson.Gson; @Controller public class WebSocketController { @Autowired private SimpMessageSendingOperations messagingTemplate; @MessageMapping("/message") @SendToUser("/queue/reply") public String processMessageFromClient(@Payload String message, Principal principal) throws Exception { String name = new Gson().fromJson(message, Map.class).get("name").toString(); //messagingTemplate.convertAndSendToUser(principal.getName(), "/queue/reply", name); return name; } @MessageExceptionHandler @SendToUser("/queue/errors") public String handleException(Throwable exception) { return exception.getMessage(); } }
convertAndSendToUser() facilitates same operation as of @SendToUser.This is used when you want to send message to a particular user and this message will be sent to a queue. There is also convertAndSend() which will broadcast message to a topic.
messagingTemplate.convertAndSendToUser(principal.getName(), "/queue/reply", name) will be transformed to the destination of "/queue/user/reply-/user/queue/reply
and it will be transformed into "/queue/user/reply-
Spring Boot WebSocket Authentication
WebSockets reuse the same authentication information that is found in the HTTP request when the WebSocket connection was made. This means that the Principal on the HttpServletRequest will be handed off to WebSockets.
Hence, any authenticattion informatioon that you want to send for websocket connections should be sent during handshake - new WebSocket("ws://localhost:8080/sockets/greeting?token=1234")
In our controller method, we have principal object injected. The application sends the message to the recipient using
messagingTemplate.convertAndSendToUser(principal.getName(), "/queue/reply", name);
The message gets turned into the destination of "/queue/user/reply-
To configure authorization using Java Configuration, simply extend the AbstractSecurityWebSocketMessageBrokerConfigurer and configure the MessageSecurityMetadataSourceRegistry. You can follpw this official documentation for configuration details.
We wll be using spring boot feature to run our application. The spring boot maven plugin will collect all the jars on the classpath and builds a single, runnable jar file, which makes it more convenient to execute and transport our service. It searches for the public static void main()
method to flag as a runnable class and provides built-in dependency resolver that sets the version number to match spring boot dependencies.
SpringBootServletInitializer enables process used in Servlet 3.0 using web.xml
@SpringBootApplication: This is a convenience annotation that is equivalent to declaring @Configuration
,@EnableAutoConfiguration
and @ComponentScan
.
package com.devglan.config; import org.springframework.boot.SpringApplication; import org.springframework.boot.context.web.SpringBootServletInitializer; @SpringBootApplication public class Application extends SpringBootServletInitializer { private static ClassapplicationClass = Application.class; public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Websocket Client
app.jsThe connect()
will create a web socket connection. Remember the mapping we did in WebSocketConfig.java class for /greeting
.
The function onmessage()
will execute itself once any message is broadcasted by server
function showGreeting()
will be simply showing the message received by the server broadcast.
send()
function will construct a JSON message and sends message to the server.
function connect() { var socket = new WebSocket('ws://localhost:8080/greeting'); ws = Stomp.over(socket); ws.connect({}, function(frame) { ws.subscribe("/user/queue/errors", function(message) { alert("Error " + message.body); }); ws.subscribe("/user/queue/reply", function(message) { showGreeting(message.body); }); }, function(error) { alert("STOMP error " + error); }); } function disconnect() { if (ws != null) { ws.close(); } setConnected(false); console.log("Disconnected"); } function sendName() { var data = JSON.stringify({ 'name' : $("#name").val() }) ws.send("/app/message", {}, data); } function showGreeting(message) { $("#greetings").append(""); } $(function() { $("form").on('submit', function(e) { e.preventDefault(); }); $("#connect").click(function() { connect(); }); $("#disconnect").click(function() { disconnect(); }); $("#send").click(function() { sendName(); }); }); " + message + "
At the server-side, we always deny any MESSAGE sent to a message that starts with the broker prefix (i.e. "/topic/" or "/queue/"). The clients could send a message directly to these endpoints and impersonate the system. The destination /user/queue/reply
gets turned into the destination of "/queue/user/reply-"
Websocket SockJS Config
To make use of SockJS in the client code you require to include sockjs.js in html.SockJS provides best available fallback options whenever websocket connection fails or unavailable.Following is the sample code to make use of it while establishing websocket connection from client.
var socket = new SockJS('ws://localhost:8080/greeting'); ws = Stomp.over(socket); //rest of the code as usual as above
we have a .html file to trigger the WebSocket connection over a button click. The text entered in the text box will be sent to server over websocket protocol and the response from the server will be diaplayed in the Greetings div element.
Index.html<!DOCTYPE html> <html> <head> <title>Hello WebSocket</title> <link href="/bootstrap.min.css" rel="stylesheet"> <link href="/main.css" rel="stylesheet"> <script src="/jquery-1.10.2.min.js"></script> <script src="/app.js"></script> <script src="/stomp.js"></script> </head> <body> <noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websocket relies on Javascript being enabled. Please enable Javascript and reload this page!</h2></noscript> <div id="main-content" class="container"> <div class="row"> <div class="col-md-6"> <form class="form-inline"> <div class="form-group"> <label for="connect">WebSocket connection:</label> <button id="connect" class="btn btn-default" type="submit">Connect</button> <button id="disconnect" class="btn btn-default" type="submit" disabled="disabled">Disconnect </button> </div> </form> </div> <div class="col-md-6"> <form class="form-inline"> <div class="form-group"> <label for="name">What is your name?</label> <input type="text" id="name" class="form-control" placeholder="Your name here..."> </div> <button id="send" class="btn btn-default" type="submit">Send</button> </form> </div> </div> <div class="row"> <div class="col-md-12"> <table id="conversation" class="table table-striped"> <thead> <tr> <th>Greetings</th> </tr> </thead> <tbody id="greetings"> </tbody> </table> </div> </div> </div> </body> </html>
Websocket Integration with Spring Boot 2
The above WebSocket configuration is done for Spring Boot version 1 but with the release of Spring Boot 2, the class(AbstractWebSocketMessageBrokerConfigurer.java) that we extended is deprecated. Hence, in this section, we will try to achieve the same result with Spring Boot 2.
With respect to the above Maven configuration that is using Spring Boot 1, first we need to update our pom.xml to use Spring Boot 2. Below is the only required change in our pom.xml.
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.4.RELEASE</version> </parent>
Next, we need to make changes in our WebSocketConfig.java. Instead of extending AbstractWebSocketMessageBrokerConfigurer.java, it will now implement the interface WebSocketMessageBrokerConfigurer.java. The remaining implementation remains the same. Below is the complete configuration.
package com.devglan.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 config) { config.enableSimpleBroker("/topic/", "/queue/"); config.setApplicationDestinationPrefixes("/app"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/greeting").withSockJS(); } }
These are all the changes that we need to make in order to integrate WebSocket with Spring Boot 2 with respect to Spring Boot 1.
Run Websocket Application
1. Run the Application.java as a java application.
2. Open the browser and hit the URL - http:localhost:8080. It will prompt for username and password as below.By default the username is user and password you can check in the console.
3. Now, you can see the index.html after successful authentication.
4. Click on connect button to make the socket connection.
5. Enter the name and post the message and you can see the greeting message as below from websocket server.
Conclusion
In this article we learned about spring boot websocket configuration and implemenation.I hope this article served you that you were looking for. If you have anything that you want to add or share then please share it below in the comment section.