Re learn the server push technology of Springboot series
- Technical description of mainstream server push
- Server push event SSE
- Two way real-time communication websocket
- Instant messaging IM
- Goeasy
- Detailed message system
- Novice introduction: detailed explanation of the principle of the most complete Web instant messaging technology in history
Technical description of mainstream server push
Demand and background
A few years ago, all requests were initiated by the browser, and the browser itself did not have the ability to accept requests. Therefore, some special requirements are implemented by ajax polling. For example:
- The share price display page obtains the share price update in real time
- Live text broadcast of the event to update the situation in real time
- Start a task through the page. The front end wants to know the real-time running status of the task background
The usual approach is to frequently establish an http connection to the server at small intervals, ask for updates to the task status, and then refresh the page display status. But the consequence of doing so is to waste a lot of traffic, causing great pressure on the server.
Common technologies of server push
After html5 has been widely promoted, we can use the server to actively push data and the browser to receive data to solve the above problems. Let's introduce two server-side data push technologies
Full duplex communication: WebSocket
Full duplex, full duplex is two-way communication. If the http protocol is a call between "walkie talkies" (you say one thing and I say one thing, there is a return), then our websocket is a mobile phone (you can send and receive information at any time, that is, full duplex).

In essence, it is an additional tcp connection. The handshake uses http protocol when establishing and closing, and other data transmission does not use http protocol. It is more complex. It is more suitable for complex two-way real-time data communication. Customer service and chat rooms on web pages are generally developed using WebSocket protocol.
Server active push: SSE (Server Send Event)
The new html5 standard is used to push data from the server to the browser in real time. It is directly established on the current http connection. In essence, it is to maintain a long http connection and lightweight protocol. The client sends a request to the server. The server keeps the request connected until a new message is ready and returns the message to the client. This connection will be maintained unless it is actively closed.
- Establish connection
- Server - > browser (connection hold)
- Close connection
A major feature of SSE is to reuse one connection to receive messages sent by the server (also known as event s), so as to avoid continuous polling requests to establish a connection, resulting in a shortage of service resources.
Comparison between websocket and SSE

However, IE and Edge browsers do not support SSE, so there are few current application scenarios for SSE. Although websocket is not compatible with many older browsers, it is generally better than SSE. In addition, some open-source JS front-end products, such as SockJS and Socket.IO, provide a better programming experience of websocket front-end JS on the browser side, which is better than that of the browser Compatibility.

Server push event SSE
Simulate network payment scenario
We should all have used the payment system, such as scanning code to pay after buying a product on Taobao. Let's see how to realize this process in combination with SSE.

- The user scan code is paid to the payment system (Alipay).
- After the payment is completed, inform the merchant system (Taobao seller system) that I have initiated the payment (establish SSE connection)
- The payment system (Alipay) tells the merchant system (Taobao seller system) that the user really paid for it.
- The merchant system (Taobao seller system) sends a message to the user: you have paid successfully, jump to the payment success page. (through SSE connection, the server informs the user of the client browser)
Note: in the operation of returning the final payment result, the event push from the server to the client can be realized by SSE
Application scenario
Starting from the characteristics of sse, we can roughly judge its application scenario. It is mostly available for case s that need to poll to obtain the latest data from the server
For example, display the real-time number of people online on the current website, display the current real-time exchange rate in French currency, promote the real-time transaction volume of e-commerce, and so on
sse specification
In the definition of html5, the server sse generally needs to comply with the following requirements
Request header
Enable long connection + stream transfer
Content-Type: text/event-stream;charset=UTF-8 Cache-Control: no-cache Connection: keep-alive
data format
The message sent by the server consists of message, and its format is as follows:
field:value\n\n
There are five possibilities for field
empty: Namely:The beginning indicates a comment, which can be understood as the heartbeat sent by the server to the client to ensure that the connection is not interrupted data: data event: Event, default id: For data identifier id Field, equivalent to the number of each piece of data retry: Reconnection time
Simulation Implementation
If you can't understand the following code, look back at this figure

We write code to simulate the implementation of the four steps 2, 3 and 4 in the above sequence diagram.
Browser front end implementation
For the data sent by the server to the browser, the browser needs to use the EventSource object in JavaScript for processing. EventSource uses the standard event listener method, and only needs to add the corresponding event processing method on the object. EventSource provides three standard events

In addition to using standard event handling methods, you can also use the addEventListener method to listen to events.
var es = new EventSource('Event source name') ; //Establish a connection with the event source //Standard event handling methods include onopen and onerror es.onmessage = function(e) { }; //You can listen to custom event names es.addEventListener('Custom event name', function(e) { });
ssetest.html (user payment page of merchant system)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>SSE</title> </head> <body> <div id = "message"> </div> <script> if (window.EventSource) { //Determine whether the browser supports SSE //Step 2: take the initiative to establish a long connection, indicating that the user has initiated payment let source = new EventSource( 'http://localhost/dhy/orderpay?payid=1'); let innerHTML = ''; //Listen for events sent from the server: open source.onopen = function(e) { console.log("Connection establishment") innerHTML += "onopen: Ready to start receiving server data" + "<br/>"; //Payment results document.getElementById("message").innerHTML = innerHTML; }; //Listen for events sent from the server: message source.onmessage = function(e) { console.log("The message sent by the server is: "+e) innerHTML += "onmessage:" + e.data + "<br/>"; //Payment results document.getElementById("message").innerHTML = innerHTML; }; //Customize the finish event and actively close the EventSource source.addEventListener('finish', function(e) { console.log("Events sent by the server: "+e) source.close(); innerHTML += "After receiving the payment result, notify the server to close EventSource" + "<br/>"; document.getElementById("message").innerHTML = innerHTML; }, false); //Listen for events from the server: error source.onerror = function(e) { console.log("Exception from server: "+e) if (e.readyState === EventSource.CLOSED) { innerHTML += "sse Connection closed" + "<br/>"; } else { console.log(e); } }; } else { console.log("Your browser does not support SSE"); } </script> </body> </html>
Server implementation
Controller code (merchant system server code)
@RestController public class SSEControler { //After creation, save SseEmitter to ConcurrentHashMap according to the order id //Normally, it should be stored in the database to generate database orders. Here we just simulate it public static final ConcurrentHashMap<Long, SseEmitter> sseEmitters = new ConcurrentHashMap<>(); //Step 2: accept the user to establish a long connection, indicating that the user has paid, and the order can be generated after payment (unconfirmed status) @GetMapping("/orderpay") public SseEmitter orderpay(@RequestParam Long payid) throws IOException { System.out.println("=======orderpay Method execution========"); //Set the default timeout of 3 seconds. After the timeout, the server actively closes the connection. //Timeout refers to the time interval when the server does not send data to the client SseEmitter emitter = new SseEmitter(3 * 1000L); sseEmitters.put(payid,emitter); emitter.onTimeout(() -> sseEmitters.remove(payid)); emitter.send(SseEmitter.event().reconnectTime(1000).data("Connection succeeded")); //Triggered by the callback interface after execution emitter.onCompletion(() -> System.out.println("Done!!!")); return emitter; } //Step 3: accept the payment result notification from the payment system, indicating that the user has paid successfully @GetMapping("/payback") public void payback (@RequestParam Long payid){ System.out.println("=======payback Method execution========"); //Take out the SSE connection SseEmitter emitter = sseEmitters.get(payid); try { //Step 4: the server informs the browser that the user has paid successfully emitter.send("User payment succeeded"); //The front-end message event is triggered. //Trigger the finish event customized by the front end emitter.send(SseEmitter.event().name("finish").id("6666").data("ha-ha")); } catch (IOException e) { emitter.completeWithError(e); //Departure front-end onerror event } } }
Introduction to SseEmitter api
- send(): send data. If a non SseEventBuilder object is passed in, the passed parameters will be encapsulated in data
- complete(): indicates that the connection will be disconnected after execution
- onTimeout(): triggered by timeout callback
- onCompletion(): callback trigger after completion
Access test
Simulation test step 2
User access http://localhost:8888/ssetest.html Page. The js code of ssetest.html page will be executed automatically,
let source = new EventSource( 'https://localhost:8888/orderpay?payid=1');
So as to simulate the user to inform the "merchant system" after the browser initiates payment: the user has initiated payment.

Simulation test step 3
Simulation of payment system (Alipay) with PostMan and interface to merchant system https://localhost:8888/payback?payid=1 Send a request, simulate the "payment system" to request from the merchant system developed by us, and inform the user that the payment is successful.

Simulation test step 4
The merchant system informs the user's browser that you have successfully paid (server data push). Automatically print out the information of "payment success" in the browser.

Because it is the first time to receive the data push from the server, the first line of text onopen in the figure is printed
Because the send message from the server is received, the second line of text onmessage in the figure is printed
The server triggered a custom finish event after data send, so the third line of text in the figure was printed
Global processing of connection timeout exception
@ExceptionHandler(AsyncRequestTimeoutException.class) @ResponseBody public String handleAsyncRequestTimeoutException(AsyncRequestTimeoutException e) { return SseEmitter.event().data("timeout!!").build().stream() .map(d -> d.getData().toString()) .collect(Collectors.joining()); }
SSE technical recommended reference articles
[SringBoot WEB series] detailed explanation of events sent by SSE server
[SpringBoot WEB series] detailed explanation of events sent by SSE server
SSE Technology Details: a new HTML5 server push event technology
Two way real-time communication websocket
Integrate websocket
<!-- introduce websocket rely on --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
Enable websocket function
@Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
Compatible with HTTPS protocol
- The ws protocol of WebSocket is implemented based on HTTP protocol
- The wss protocol of WebSocket is implemented based on HTTPS protocol
Once the https protocol is used in your project, your websocket must use the WSS protocol. How to make the Spring Boot project support WSS protocol?
Refer to my previous article Configure HTTPS for Web container , add the following code on the basis of Tomcat customizer configuration in that section to support wss protocol.
@Bean public TomcatContextCustomizer tomcatContextCustomizer() { return new TomcatContextCustomizer() { @Override public void customize(Context context) { context.addServletContainerInitializer(new WsSci(), null); } }; }
Fundamentals of WebSocket programming
Connection establishment
The front-end js sends a wss connection establishment request to the back-end
If the http protocol is used, it can be changed to ws
socket = new WebSocket("wss://localhost:8888/ws/asset");
The SpringBoot server WebSocket service receiving class is defined as follows:
@Component @Slf4j @ServerEndpoint(value = "/ws/asset") public class WebSocketServer {
Full duplex data interaction
Both front-end and back-end
- Listen to the onopen event and handle the connection establishment event
- Listen to the onmessage event and process the message data sent by the other party
- The onclose event listens and handles connection closure
- Listen to the onerror event and handle exceptions during interaction

Data transmission
Data exchange between browser and server

Front end JS
socket.send(message);
The back-end Java sends a message to a javax.websocket.Session user.
/** * Send a message. Practice shows that the session changes every time the browser refreshes. * @param session session * @param message news */ private static void sendMessage(Session session, String message) throws IOException{ session.getBasicRemote().sendText(String.format("%s (From Server,Session ID=%s)",message,session.getId())); }
One user sends mass messages to other users

The server sends messages to all online javax.websocket.Session users.
/** * Mass messaging * @param message news */ public static void broadCastInfo(String message) throws IOException { for (Session session : SessionSet) { if(session.isOpen()){ sendMessage(session, message); } } }
Implementing chat software with websocket
WebSocketServer is the core code of this section, websocket server code
- @ServerEndpoint(value = "/ ws/asset") indicates the interface service address of websocket
- @OnOpen annotated method, which is called when the connection is established successfully
- @OnClose annotated method, which is the method called for connection closure
- The way to annotate @OnMessage is the way to call after receiving the client message.
- @The method annotated with OnError is the method called when an exception occurs
@Component @Slf4j @ServerEndpoint(value = "/ws/asset") public class WebSocketServer { //Used to count the number of connected clients private static final AtomicInteger OnlineCount = new AtomicInteger(0); // The thread safe Set of the concurrent package is used to store the Session object corresponding to each client. private static CopyOnWriteArraySet<Session> SessionSet = new CopyOnWriteArraySet<>(); /** * Method successfully called for connection establishment */ @OnOpen public void onOpen(Session session) throws IOException { SessionSet.add(session); int cnt = OnlineCount.incrementAndGet(); // Online number plus 1 log.info("There are connections joined. The current number of connections is:{}", cnt); } /** * Method of calling after receiving client message * @param message Messages sent by the client */ @OnMessage public void onMessage(String message, Session session) throws IOException { log.info("Message from client:{}",message); sendMessage(session, "Echo Message content:"+message); // broadCastInfo(message); Mass messaging } /** * Method called for connection closure */ @OnClose public void onClose(Session session) { SessionSet.remove(session); int cnt = OnlineCount.decrementAndGet(); log.info("There are connections closed. The current number of connections is:{}", cnt); } /** * An error occurred */ @OnError public void onError(Session session, Throwable error) { log.error("An error occurred:{},Session ID: {}",error.getMessage(),session.getId()); } /** * Send a message. Practice shows that the session changes every time the browser refreshes. * @param session session * @param message news */ private static void sendMessage(Session session, String message) throws IOException { session.getBasicRemote().sendText(String.format("%s (From Server,Session ID=%s)",message,session.getId())); } /** * Mass messaging * @param message news */ public static void broadCastInfo(String message) throws IOException { for (Session session : SessionSet) { if(session.isOpen()){ sendMessage(session, message); } } } }
Client code, do several experiments, naturally understand the meaning of the code. Don't look at the code first, look at the effect of the experiment through the browser first, so as to better understand the function of the code. public/wstest.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>websocket test</title> <style type="text/css"> h3,h4{ text-align:center; } </style> </head> <body> <h3>Please enter the message to be sent to the server:</h3><br/> <label for="text">Enter sending information</label><input id="text" type="text" /> <button onclick="sendToServer()">Send server message</button> <button onclick="closeWebSocket()">Close connection</button> <br> information: <span id="message"> </span> <script type="text/javascript"> var socket; if (typeof (WebSocket) == "undefined") { console.log("Sorry: your browser does not support WebSocket"); } else { socket = new WebSocket("wss://localhost:8888/ws/asset"); //Connection open event socket.onopen = function() { console.log("Socket Opened"); }; //Message received event socket.onmessage = function(msg) { document.getElementById('message').innerHTML += msg.data + '<br/>'; }; //Connection close event socket.onclose = function() { console.log("Socket Closed"); }; //An error event occurred socket.onerror = function() { alert("Socket An error has occurred"); }; //Close the connection when the window closes window.unload=function() { socket.close(); }; } //Close connection function closeWebSocket(){ socket.close(); } //Send message to server function sendToServer(){ var message = document.getElementById('text').value; socket.send(message); } </script> </body> </html>
test


Once the connection is closed, the message will not take effect
Refreshing the browser will cause the current long connection to close