The WebSocket protocol provides new capability for web applications: full-duplex, two-way communication. So in the system where the client and server need to exchange data at high frequency and with low latency, WebSocket is the best solution. That is the reason for us to create a Spring WebSocket Application with JavaSampleApproach.
Related Post: Spring Boot WebSocket with Angular 5 Client | SockJS + STOMP
I. Spring WebSocket Application
1. Flow of messages
We create a Spring WebSocket Application with the below flow of messages:
Detail explanations:
– WebSocket clients connect to the WebSocket endpoint at ‘/jsa-stomp-endpoint’
– Subscriptions to ‘/topic/hi’ pass through the response channel, then are forwarded to the In-memory broker (Simple Broker).
– User objects sent to ‘/jsa/hello’ pass through the request channel then are forwarded to the spring WebController. WebController will handle User
objects by @MessageMapping
and transform to Hello
messages then use @SendTo
returns the messages to ‘/topic/hi’ through the brokerChannel.
... @MessageMapping("/hello") @SendTo("/topic/hi") public Hello greeting(User user) throws Exception { return new Hello(...); } ...
– The Simple Broker broadcasts messages to subscribers through the response channel.
2. Server side
In server side, we use SockJS and STOMP for our application.
What is SockJS?
-> SockJS lets applications use a WebSocket API but falls back to non-WebSocket alternatives when necessary at runtime, without the need to change application code.
Simple Java configuration to enable SockJS and Stomp in Spring application:
@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/topic"); config.setApplicationDestinationPrefixes("/jsa"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/jsa-stomp-endpoint").withSockJS(); } }
*Update: with new Spring WebSocket release, AbstractWebSocketMessageBrokerConfigurer
is deprecated, we will implement the WebSocketMessageBrokerConfigurer
interface instead:
@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { // ... } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { // ... } }
3. Client side
About client side, we uses {sockjs-client, stomp-websocket} libs for development:
<script src="/webjars/sockjs-client/sockjs.min.js"></script> <script src="/webjars/stomp-websocket/stomp.min.js"></script>
– Make a connection:
function connect() { var socket = new SockJS('/jsa-stomp-endpoint'); stompClient = Stomp.over(socket); stompClient.connect({}, function (frame) { setConnected(true); stompClient.subscribe('/topic/hi', function (hello) { ... }); }); }
The connect()
function uses SockJS
and stomp.js
to open a connection to /jsa-stomp-endpoint
, which is where our SockJS
server is waiting for connections.
– Dis-Connection:
function disconnect() { if (stompClient != null) { stompClient.disconnect(); } setConnected(false); }
– Send messages:
function sendName() { stompClient.send("/jsa/hello", {}, JSON.stringify({'name': $("#name").val()})); }
II. Practice
0. Technologies
– Java 8
– Maven 3.6.1
– Spring Tool Suite: Version 3.8.4.RELEASE
– Spring Boot: 1.5.4.RELEASE
– Spring WebSocket
– SockJS
– Stomp
– JQuery
– Bootstrap
1. Server side
1.1 Create SpringBoot project
Using Spring Tool Suite to create a Spring Starter Project, then add dependencies:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>webjars-locator</artifactId> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>sockjs-client</artifactId> <version>1.0.2</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>stomp-websocket</artifactId> <version>2.3.3</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>bootstrap</artifactId> <version>3.3.7</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>3.1.0</version> </dependency>
1.2 Create models
Create 2 message models {User, Hello}
– User:
package com.javasampleapproach.websocket.model; public class User { private String name; public User(){} public void setName(String name){ this.name = name; } public String getName(){ return this.name; } }
– Hello:
package com.javasampleapproach.websocket.model; public class Hello { private String greeting; public Hello(){} public Hello(String greeting){ this.greeting = greeting; } public void setGreeting(String greeting){ this.greeting = greeting; } public String getGreeting(){ return this.greeting; } }
1.3 Configure SockJS and STOMP messaging
package com.javasampleapproach.websocket.config; 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"); config.setApplicationDestinationPrefixes("/jsa"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/jsa-stomp-endpoint").withSockJS(); } }
– @EnableWebSocketMessageBroker
enables WebSocket message handling, backed by a message broker.
– enableSimpleBroker()
enables a simple memory-based message broker.
– registerStompEndpoints()
is for registering the /jsa-stomp-endpoint
endpoint, enabling SockJS fallback options – an alternate transports in case WebSocket is not available.
1.4 Create WebController
package com.javasampleapproach.websocket.controller; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.stereotype.Controller; import com.javasampleapproach.websocket.model.Hello; import com.javasampleapproach.websocket.model.User; @Controller public class WebController { @MessageMapping("/hello") @SendTo("/topic/hi") public Hello greeting(User user) throws Exception { return new Hello("Hi, " + user.getName() + "!"); } }
As above explanation, User objects are sent to /jsa/hello
pass through the request channel then are forwarded to the Spring WebController.
WebController will handle User
objects by @MessageMapping
and transform to Hello
messages then use @SendTo
to return the messages to ‘/topic/hi’ through the brokerChannel.
2. Client side
2.1 Create index.html
<!DOCTYPE html> <html> <head> <title>Spring Boot WebSocket!</title> <link href="/webjars/bootstrap/css/bootstrap.min.css" rel="stylesheet"> <link href="/main.css" rel="stylesheet"> <script src="/webjars/jquery/jquery.min.js"></script> <script src="/webjars/sockjs-client/sockjs.min.js"></script> <script src="/webjars/stomp-websocket/stomp.min.js"></script> <script src="/app.js"></script> </head> <body> <div class="container"> <div class="row"> <div class="col-md-4"> <form class="form-inline"> <div class="form-group"> <label for="connect">Make 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-4"> <form class="form-inline"> <div class="form-group"> <label for="name">User's Name:</label> <input type="text" id="name" class="form-control"> </div> <button id="send" class="btn btn-default" type="submit">Send</button> </form> </div> </div> <div class="row"> <div class="col-md-3"> <table id="conversation" class="table table-striped"> <thead> <tr> <th>Greetings</th> </tr> </thead> <tbody id="hellos"> </tbody> </table> </div> </div> </div> </body> </html>
2.2 Implement WebSocket javascript functions
We implement 3 main functions for connection, disconnection and send messages:
var stompClient = null; function setConnected(connected) { $("#connect").prop("disabled", connected); $("#disconnect").prop("disabled", !connected); if (connected) { $("#conversation").show(); } else { $("#conversation").hide(); } $("#hellos").html(""); } function connect() { var socket = new SockJS('/jsa-stomp-endpoint'); stompClient = Stomp.over(socket); stompClient.connect({}, function (frame) { setConnected(true); console.log('Connected: ' + frame); stompClient.subscribe('/topic/hi', function (hello) { showGreeting(JSON.parse(hello.body).greeting); }); }); } function disconnect() { if (stompClient != null) { stompClient.disconnect(); } setConnected(false); console.log("Disconnected"); } function sendName() { stompClient.send("/jsa/hello", {}, JSON.stringify({'name': $("#name").val()})); } function showGreeting(greeting) { $("#hellos").append(""); } $(function () { $("form").on('submit', function (e) { e.preventDefault(); }); $( "#connect" ).click(function() { connect(); }); $( "#disconnect" ).click(function() { disconnect(); }); $( "#send" ).click(function() { sendName(); }); }); " + greeting + "
3. Run and check results
Build and Run the SpringBoot project with command-lines: mvn clean install
and mvn spring-boot:run
3.1 Make a connection
Make a request at http://localhost:8080/
-> Result:
3.2 Send messages
Press Connet button, and input JavaSampleApproach, then press Send button:
-> Result:
Open a new tab, make a request http://localhost:8080/
, press Connet button, input JSA, then press Send button:
-> Result:
3.3 Dis-connection
Press Disconnect a client, all greetings messages at the client are removed.
-> Result: