springboot integrates sse

Link: SpringBoot implements SSE server-sent events
Link: SpringBoot implements SSE server-sent events
Link: Springboot integrates SSE to implement message push
Link: springboot SseEmitter message push
Link: Use Sse (Server-sent Events) Web real-time communication technology in springboot-server Send event SseEmitter
link: Detailed explanation of Web real-time message push

Link: SSE (Server-Send Events) in action

Link: Server side event (SSE) implements message push function

what is sse

Link: Copyright belongs to the original link
Server-Sent Events (Server-Sent Events), referred to as SSE. This is a one-way message push from server to client (browser). SSE is based on the HTTP protocol. We know that the HTTP protocol in the general sense cannot enable the server to actively push messages to the client, but SSE is an exception. It changes a way of thinking.

SSE opens a one-way channel between the server and the client. What the server responds to is no longer a one-time data packet but a text/event-stream type of data flow information, which is streamed from the server to the server when there is a data change. client. The overall implementation idea is a bit similar to online video playback. The video stream will be continuously pushed to the browser. You can also understand that it takes a long time for the client to complete a download (the network is not smooth).

SSE and WebSocket have similar functions, both can establish communication between the server and the browser, and push messages from the server to the client, but there are still some differences:

  • SSE is based on the HTTP protocol, and they do not require a special protocol or server implementation to work; WebSocket requires a separate server to handle the protocol.
  • SSE one-way communication, only one-way communication from the server to the client;
    WebSocket full-duplex communication, that is, both parties to the communication can send and receive information at the same time.
  • SSE is easy to implement and has low development costs, without the need to introduce other components;
    WebSocket data transmission needs to be analyzed twice, and the development threshold is higher.
  • SSE supports disconnection and reconnection by default; WebSocket needs to implement it by itself.
  • SSE can only transmit text messages, and binary data needs to be encoded before transmission; WebSocket supports the transmission of binary data by default.

Note: SSE does not support IE browser, and it does a good job of compatibility with other major browsers.

How to choose between SSE and WebSocket?

Link: Copyright belongs to the original link
SSE seems to have been unknown to everyone, partly because of the emergence of WebSocket, which provides a richer protocol to perform two-way, full-duplex communication. For gaming, instant messaging, and scenarios that require bi-directional near real-time updates, having bi-directional channels is more attractive. However, in some cases, there is no need to send data from the client. And you just need some updates for server operations. For example: in-site messages, number of unread messages, status updates, stock quotes, monitoring quantity and other scenarios, SEE has more advantages in terms of ease of implementation and cost. Additionally, SSE has several features that WebSocket lacks by design, such as: automatic reconnection, event IDs, and the ability to send arbitrary events.

sse specification

In the definition of html5, server-side sse generally needs to follow the following requirements

  • request header
开启长连接 + 流方式传递
Content-Type: text/event-stream;charset=UTF-8
Cache-Control: no-cache
Connection: keep-alive
  • Data Format

The message sent by the server is composed of message, and its format is as follows:

field:value\n\n

Among them, field has five possibilities

  • Empty: It starts with:, which means a comment, which can be understood as the heartbeat sent from the server to the client to ensure that the connection is not interrupted
  • data: data. After subscribing, the server sends messages to the client as soon as they are available. Events are text messages encoded in UTF-8. Events are separated by two newlines\n\n. Each event consists of one or more name:value fields, separated by a single newline \n.
  • event: event, default value
  • id: The id field indicates the data identifier, which is equivalent to the serial number of each piece of data. The server MAY send a unique event identifier (id field). If the connection is interrupted, the client will automatically reconnect and send the last received event ID with header Last-Event-ID.
  • retry: reconnection time, in the retry field, the server can send a timeout in milliseconds, after which the client should automatically reconnect if the connection is lost. If this field is not specified, the standard should be 3000 milliseconds.

rear end

import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * sse服务
 * 客户端关闭时,只能等待超时 服务端断开连接
 */
@Controller
@RequestMapping(path = "sse")
public class SseRest {
    
    

    private final static Map<String, SseEmitter> sseCache = new ConcurrentHashMap<>();

    /**
     * 连接sse服务
     * @param id
     * @return
     * @throws IOException
     */
    @GetMapping(path = "subscribe", produces = {
    
    MediaType.TEXT_EVENT_STREAM_VALUE})
    public SseEmitter push(String id) throws IOException {
    
    
        // 超时时间设置为5分钟,用于演示客户端自动重连
        SseEmitter sseEmitter = new SseEmitter(5_60_000L);
        // 设置前端的重试时间为1s
        // send(): 发送数据,如果传入的是一个非SseEventBuilder对象,那么传递参数会被封装到 data 中
        sseEmitter.send(SseEmitter.event().reconnectTime(1000).data("连接成功"));
        sseCache.put(id, sseEmitter);
        System.out.println("add " + id);
        sseEmitter.send("你好", MediaType.APPLICATION_JSON);
        SseEmitter.SseEventBuilder data = SseEmitter.event().name("finish").id("6666").data("哈哈");
        sseEmitter.send(data);
        // onTimeout(): 超时回调触发
        sseEmitter.onTimeout(() -> {
    
    
            System.out.println(id + "超时");
            sseCache.remove(id);
        });
        // onCompletion(): 结束之后的回调触发
        sseEmitter.onCompletion(() -> System.out.println("完成!!!"));
        return sseEmitter;
    }

    /**
     * http://127.0.0.1:8080/sse/push?id=7777&content=%E4%BD%A0%E5%93%88aaaaaa
     * @param id
     * @param content
     * @return
     * @throws IOException
     */
    @ResponseBody
    @GetMapping(path = "push")
    public String push(String id, String content) throws IOException {
    
    
        SseEmitter sseEmitter = sseCache.get(id);
        if (sseEmitter != null) {
    
    
            sseEmitter.send(content);
        }
        return "over";
    }

    @ResponseBody
    @GetMapping(path = "over")
    public String over(String id) {
    
    
        SseEmitter sseEmitter = sseCache.get(id);
        if (sseEmitter != null) {
    
    
            // complete(): 表示执行完毕,会断开连接
            sseEmitter.complete();
            sseCache.remove(id);
        }
        return "over";
    }
}

front end

There are readyState properties that represent the state of the connection:
EventSource.CONNECTING = 0 - the connection has not been established, or is closed and the client is reconnecting
EventSource.OPEN = 1 - the client has an open connection and processes events as they are received on
the EventSource. CLOSED = 2 - the connection is not open, and the client did not try to reconnect, either with a fatal error, or the close() method was called

要处理连接的建立,它应该订阅 onopen 事件处理程序。

eventSource.onopen = function () {
    
    
	console.log('connection is established');
 };
为了处理连接状态的一些异常或致命错误,它应该订阅 onerrror 事件处理程序。

eventSource.onerror = function (event) {
    
    
	console.log('connection state: ' + eventSource.readyState + ', error: ' + event);
 };
客户端接收消息并处理他们,可以使用onmessage方法

eventSource.onmessage = function (event) {
    
    
	console.log('id: ' + event.lastEventId + ', data: ' + event.data); 
};
<!doctype html>
<html lang="en">
<head>
    <title>Sse测试文档</title>
</head>
<body>
<div>sse测试</div>
<div id="result"></div>
</body>
</html>
<script>
    let source = null;
    let userId = 7777
    if (window.EventSource) {
      
      
        // 建立连接
        source = new EventSource('http://localhost:8080/sse/subscribe?id='+userId);
        console.log("连接用户=" + userId);
        /**
         * 连接一旦建立,就会触发open事件
         * 另一种写法:source.onopen = function (event) {}
         */
        source.addEventListener('open', function (e) {
      
      
            console.log("建立连接。。。");
        }, false);
        /**
         * 客户端收到服务器发来的数据
         * 另一种写法:source.onmessage = function (event) {}
         */
        source.addEventListener('message', function (e) {
      
      
            console.log(e.data);
        });

        source.addEventListener('finish', function (e) {
      
      
            console.log(e.id);
            console.log(e.data);
        });

    } else {
      
      
        console.log("你的浏览器不支持SSE");
    }
</script>

The above implementation uses several methods of SseEmitter, explained as follows:
send(): Send data, if a non-SseEventBuilder object is passed in, then the passed parameters will be encapsulated in data
complete(): Indicates that the execution is completed and will be interrupted Open connection
onTimeout(): Timeout callback triggers
onCompletion(): Callback triggers after the end

In actual business development, it is recommended to use SseEmitter. SseEmitter has already packaged these for us.

Notice

If nginx configures proxy_buffering off
and does not configure proxy_buffering off, the interface will return directly after the request is sent, and the long connection cannot be maintained.
Refer to the online description: The parameter proxy_buffering is used to control whether to open the buffer of the backend response content. If this setting is off, the two commands proxy_buffers and proxy_busy_buffers_size will be invalid.

Problem Description
I'm using the eventSource API and added addEventListener() to the eventsouce. The event source is activated until only 45 seconds. I want to keep the connection alive until the server sends a response back to the client.

Now, I am getting the following exception because the server is not responding until 45 seconds.

EXCEPTION: No activity within 450000 milliseconds. Reconnecting

Exception: No activity for 450000 milliseconds. Reconnecting.

The solution is to send data periodically, which works fine even with null bytes and can keep the connection alive.

If a connection cannot be established and you want to retry the connection, you can use e.g. setTimeout set to 45 seconds.

Once a connection is established, use clearTimeout to stop attempts.

Guess you like

Origin blog.csdn.net/qq_41604890/article/details/127852300