Spring Boot (2) Websockets. Subscriptions of only one member
1. Introduction
WebSockets are useful for informing a user of the progress of a heavy process. There are a lot of blogs and videos about this question.
Spring boot uses the STOMP protocol
2. Creating the project
Use Spring boot initializer for creating the project, use these dependencies:
- Spring boot dev tools
- Lombok
- Spring Web
- Thymeleaf
- Websocket
And use gradle format
Then import it from Eclipse as File > Import > Gradle > Existing Gradle Project
The final project structure after adding all the needed classes, html and js file is:
3. Add the needed dependencies to the build.gradle file
Add javascript dependencies to the build.gradle
implementation 'org.webjars:webjars-locator-core' implementation 'org.webjars:sockjs-client:1.5.1' implementation 'org.webjars:stomp-websocket:2.3.4' implementation 'org.webjars:bootstrap:3.3.7' implementation 'org.webjars:jquery:3.6.4'
Add also the Gson dependencies, and the build.xml file will be
plugins { id 'java' id 'org.springframework.boot' version '3.0.5' id 'io.spring.dependency-management' version '1.1.0' } group = 'websocket1' version = '0.0.1-SNAPSHOT' sourceCompatibility = '17' configurations { compileOnly { extendsFrom annotationProcessor } } repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-websocket' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' implementation 'com.google.code.gson:gson:2.10.1' implementation 'org.webjars:webjars-locator-core' implementation 'org.webjars:sockjs-client:1.5.1' implementation 'org.webjars:stomp-websocket:2.3.4' implementation 'org.webjars:bootstrap:3.3.7' implementation 'org.webjars:jquery:3.6.4' } tasks.named('test') { useJUnitPlatform() }
4. The configuration class
In the simple configuration class, you should notice:
- The endpoint "/hello" in this class must match the @MessageMapping("/hello") from the controller class and the new SockJS('/hello') of the javascript
- The enableSimpleBorker param "/queue" can be multiple (for instance registry.enableSimpleBroker("/queue", "/error") and must match as prefix to the subscription of the javascript code stompClient.subscribe('/queue/reply/'+randNum, function(message) {
- If you want to send messages only to one user, in the javascript subscribe, a unique suffix can be added so that it is unique for this client, in this case a random number randNum
Note also the annotations and the interface implementations
package websocket.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 WebSocketConfiguration implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/hello").withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableSimpleBroker("/queue"); registry.setApplicationDestinationPrefixes("/app"); } }
5. The Controller class
In this class in order to address only one client, the destination of the messages must contain a unique identifier that matches the "subscribe" method of the javascript.
Note the @MessageMapping("/hello") that must match the endpoint "/hello" of the previous configuration class and the new SockJS('/hello') of the javascript
Note the @Autowire annotation for the SimpleMessageSendingOperations class that is used for sending messages to the "subscribe" javascript channel "/queue/reply/"+idSession of the client. In this example, 2 messages are returned on the receipt of one message from the client.
package websocket.controllers; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.simp.SimpMessageSendingOperations; import org.springframework.stereotype.Controller; @Controller public class WebSocketController { @Autowired private SimpMessageSendingOperations messagingTemplate; @MessageMapping("/hello") /* //Si que va @SendToUser("/queue/reply") public String send(String username) { return "Hello........, " + username; } */ public void send(Message message) { String username=message.getName(); String text=message.getText(); String idSession=message.getIdSession(); this.messagingTemplate.convertAndSend("/queue/reply/"+idSession, text + " Hello...++....., " + username+ " " + idSession); this.messagingTemplate.convertAndSend("/queue/reply/"+idSession, text + " Hello...+++....., " + username); } }
6. The model to pass
Here is a simple model. Node the idSession field that matches the randNumber in the javascript file
package websocket.controllers; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @NoArgsConstructor @AllArgsConstructor @Getter @Setter public class Message { private String name; // name of the user private String text; // message to pass private String idSession; // Random number indicating the session }
7. The simple Html file (index.html) of the client
It could be used thymeleaf, bootstrap, css and other improvements but for sake of simplicity it is only displayed how to reference bootstrap, stomp, jquery .... in this file. The most remarkable is:
- Including js dependencies indicated in the build.gradle (sockjs-client, stomp-websocket, jquery)
- A referenced to the js file app.js
- 2 textboxes (one for the name of the client and another for the message)
- 1 textarea for collecting the messages form the server
<!DOCTYPE html> <html> <head> <title>Hello WebSocket</title> <script src="/webjars/sockjs-client/sockjs.min.js"></script> <script src="/webjars/jquery/jquery.min.js"></script> <script src="/webjars/stomp-websocket/stomp.min.js"></script> <script src="/app.js"></script> </head> <body> <div id="main-content"> <div> <form> <div> <label>What is your name?</label> <input type="text" name="name" id="name" placeholder="Your name here..."> </div> <div> <label>What is your message?</label> <input type="text" name="text" id="text" placeholder="Your message here..."> </div> <button id="send" type="submit">Send</button> </form> </div> </div> <div> <label>Message from server: </label><span id="message"></span> <textarea id="mytextarea" rows="4" cols="30">1234</textarea> </div> </body> </html>
8. The js file (app.js)
We could have created a more useful javascript file indicating disconnection and more facilities but for sake of simplicity here is the file.
Note the "stombClient.subscribe""/queue/reply/"+idSession that SimpleMessageSendingOperations class in the Controller that is used for sending messages, and that enableSimpleBorker param "/queue" of the Configuration class is the prefix of the subscribe
var randNum = (Math.floor(Math.random() * 1000000000)).toString(); $(document).ready(function() { connect(); }); function connect() { var socket = new SockJS('/hello'); stompClient = Stomp.over(socket); stompClient.connect({}, function() { console.log('Web Socket is connected'); //stompClient.subscribe('/user/queue/reply', function(message) { //stompClient.subscribe('/queue/reply', function(message) { stompClient.subscribe('/queue/reply/'+randNum, function(message) { $("#message").text(message.body); document.getElementById("mytextarea").value +="\n"+message.body; alert(message.body); }); }); } $(function() { $("form").on('submit', function(e) { e.preventDefault(); }); $("#send").click(function() { //stompClient.send("/app/hello", {}, $("#name").val()+'#$#'+randNum); const formElement = document.querySelector("form"); const formData = new FormData(formElement); var objFormData=Object.fromEntries(formData); objFormData.idSession = randNum; alert(JSON.stringify(objFormData)); stompClient.send("/app/hello", {}, JSON.stringify(objFormData)); }); });
9. Running the applications
The main class that has been generated by the Spring Boot Initializer is:
package websocket; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Websocket1Application { public static void main(String[] args) { SpringApplication.run(Websocket1Application.class, args); } }
right-clicking on in and selecting Run As > Java Application and opening a browser at
http://localhost:8080
And this message is only sent to a client (unless the random number is duplicated!)
Comentarios
Publicar un comentario