How does spring integrate Java EE native websocket to push real-time data from the back-end to the front-end
In our daily work, we often encounter some scenes that need to obtain the back-end data in real time, and then constantly refresh the front-end display data.
Generally, we have two methods: one way is that the front-end polls and calls the corresponding API to obtain the latest data, and then constantly update the data change display of the front-end page; another way is that the back-end actively pushes data to the front-end, and the front-end monitors the back-end data changes in real time, and then displays them to the page.
The disadvantage of the first method compared with the second method is that the front-end will generate many unnecessary requests if there is no data change at the back-end. Therefore, we generally choose the second method, that is, using websocket to meet our requirements.
What is websocket?
Websocket is a protocol for full duplex communication over a single TCP connection. Websocket communication protocol was defined as standard RFC 6455 by IETF in 2011 and supplemented by RFC 7936. Websocket API is also set as the standard by W3C.
Websocket makes the data exchange between the client and the server easier, and allows the server to actively push data to the client. In the websocket API, the browser and server only need to complete one-time-handshake, and then they can directly create a persistent connection and conduct two-way data transmission.
How to implement websocket?
1. pom.xml
<!-- websocket dependency -->
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>7.0</version>
</dependency>
2. Code of websocket service implementation based on annotation
package com.util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* WebSocket server
*/
@Component
@Slf4j
@ServerEndpoint("/websocket/{uid}")
public class WebSocketServer {
/**
* Current login user ID
*/
private long uid;
/**
* the session between the server and the client
* the server sends messages to the client through this session
*/
private Session session;
/**
* Static variable, used to record the total number of current online connections
*/
private static int onlineCount = 0;
/**
* Static variable, used to save all client connection session objects
* Copyonwritearrayset is a thread safe set
*/
private static CopyOnWriteArraySet<WebSocketServer> webSocketServers = new CopyOnWriteArraySet<>();
public WebSocketServer(){
System.out.println("WebSocketServer...");
}
/**
* Listen for connection establishment events
* Execute this method when creating a connection session
*
* @param session
* @param uid
* @throws IOException
*/
@OnOpen
public void onOpen(Session session, @PathParam("uid") long uid) throws IOException {
this.uid = uid;
this.session = session;
webSocketServers.add(this);
addOnlineCount();
log.info("Connection created successfully! Current login user ID:{}", this.uid);
// Response client information
sendMessage("The client and server are connected successfully! Number of people currently online: " + getOnlineCount());
}
/**
* Listen to the message event sent by the client and receive the message requested by the client
*
* @param session
* @param message
* @throws IOException
*/
@OnMessage
public void onMessage(Session session, String message) throws IOException {
log.info("Received request from client with user ID {}, message content is: {}", this.uid, message);
// Response client information
sendMessage("Hello, the server has successfully received the message you sent: " + message);
// Mass messaging
/*for (WebSocketServer webSocketServer : webSocketServers) {
webSocketServer.sendMessage(message);
}*/
}
/**
* Listen for exception or error events
* Execute this method when an exception or error occurs
*
* @param session
* @param error
* @throws IOException
*/
@OnError
public void onError(Session session, Throwable error) throws IOException {
log.error("Wrong report! error message: {}", error.getMessage());
sendMessage("Sorry, there is an error on the server!");
}
/**
* Listen for connection closing events
* Execute this method when the connection session is closed
*/
@OnClose
public void onClose() {
webSocketServers.remove(this);
subtractOnlineCount();
log.info("WebSocket session was closed! Number of people currently online: {}", getOnlineCount());
}
/**
* The server actively pushes messages to the client
*
* @param message
* @throws IOException
*/
private void sendMessage(String message) throws IOException {
if (Objects.isNull(this.session) || !this.session.isOpen()) {
throw new NullPointerException("WebSocket session does not exist or failed to create a session!");
}
// Send server message
this.session.getBasicRemote().sendText(message);
}
/**
* If the number of online logins is added, add one
*/
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
/**
* If the number of online logins decreases, minus one
*/
public static synchronized void subtractOnlineCount() {
WebSocketServer.onlineCount--;
}
/**
* Get the total number of currently created connections, that is, the total number of currently logged in online
*
* @return
*/
public static synchronized int getOnlineCount() {
return WebSocketServer.onlineCount;
}
/**
* Gets all sessions of the current login connection
*
* @return
*/
public static CopyOnWriteArraySet<WebSocketServer> getWebSocketServers() {
return WebSocketServer.webSocketServers;
}
/**
* Send a single message
*
* @param message
* @param userId
* @throws IOException
*/
public static void sendMessageToUser(String message, long userId) {
// All current online connection sessions
for (WebSocketServer webSocketServer : WebSocketServer.getWebSocketServers()) {
// Send a single message
if (webSocketServer.uid == userId) {
try {
webSocketServer.sendMessage(message);
} catch (IOException e) {
log.error("Failed to send message, exception information: {}", e.getMessage());
}
return;
}
}
}
/**
* Mass messaging
*
* @param message
*/
public static void sendMessageToAllUsers(String message) {
WebSocketServer.getWebSocketServers().forEach(webSocketServer -> {
try {
webSocketServer.sendMessage(message);
} catch (IOException e) {
log.error("Failed to send message, exception information: {}", e.getMessage());
}
});
}
}
3. Code of controller which used to actively send messages from the server to the client
package com.controller;
import com.hx88.util.WebSocketServer;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
/**
* WebSocket controller
*/
@Controller
@RequestMapping("/websocket")
public class WebSocketController {
@GetMapping("/{userId}")
public String page(@PathVariable("userId") long userId, HttpServletRequest request) {
request.setAttribute("userId", userId);
return "websocket";
}
@ResponseBody
@PostMapping("/push/{userId}")
public String push(@PathVariable("userId") long userId, @RequestParam String message) {
WebSocketServer.sendMessageToUser(message, userId);
return "SUCCESS";
}
@ResponseBody
@PostMapping("/pushAll")
public String pushAll(@RequestParam String message) {
WebSocketServer.sendMessageToAllUsers(message);
return "SUCCESS";
}
}
4. Code of the front-end page enables the front-end to send messages to the back-end and receive messages pushed by the back-end in real time
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>WebSocket test page</title>
</head>
<body>
<input id="message" placeholder="Please enter the message to send"/>
<button id="send">Send</button>
<h2>Received message pushed from back-end: </h2>
<p id="listener"></p>
</body>
<script type="text/javascript" th:inline="javascript">
const userId = 1001; // 1001 is just an exmaple value
// Instantiate the websocket object, specify the server address and port to be connected, and establish the connection
const socket = new WebSocket("ws://localhost:8080/websocket/" + userId);
// Establish connection event
socket.onopen = function () {
console.log("WebSocket connect successfully!")
};
// Send message
document.getElementById("send").onclick = function () {
socket.send(document.getElementById("message").value);
};
// Receive message event
socket.onmessage = function (message) {
console.log("Received message pushed from back-end: " + message);
let p = document.createElement("p");
p.innerText = message.data;
document.getElementById("listener").appendChild(p);
}
// Close event
socket.onclose = function () {
console.log("WebSocket was closed!");
}
// Error reporting event
socket.onerror = function () {
console.log("WebSocket encouter error!");
}
</script>
</html>