Server-Sent Events(SSE) 簡單實現和避坑


服務器向瀏覽器推送信息,除了 WebSocket,還有一種方法:Server-Sent Events(以下簡稱 SSE)。

一、客戶端API(EventSource 對象)

各瀏覽器支持情況: https://caniuse.com/eventsource

 

前端測試

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" type="text/css" href="static/css/home.css">
    <script>
        window.onload = ()=> {
            if (window.EventSource) {
                let source = new EventSource("/mySpringMVC/sse/subscribe");
                let s = '';
                // 
                source.addEventListener('message', function(e) {
                    document.querySelector("p").innerText = e.data;
                })
                source.addEventListener('open',function(e){
                    console.log("connect is open");
                },false);
                source.addEventListener('error',function(e){
                    if(e.readyState == EventSource.CLOSE){
                        console.log("connect is close");
                    }else{
                        console.log(e.readyState);
                    }
                },false);
            } else {
                alert("瀏覽器不支持EventSource");
            }
        }
    </script>
</head>
<body>
    <p></p>
</body>
</html>
View Code

對應后端

package MySpringMVC;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.WebAsyncTask;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;

@Controller
@RequestMapping(value = "/sse")
public class SSEController {
    // 新建一個容器,保存連接,用於輸出返回
    private Map<String, PrintWriter> responseMap = new ConcurrentHashMap<>();


    // 發送數據給客戶端
    private void writeData(String id, String msg, boolean over) throws IOException {
        PrintWriter pw = responseMap.get(id);
        if (pw == null) {
            return;
        }
        pw.println(msg);
        pw.println();
        pw.flush();
        if (over) {
            responseMap.remove(id);
        }
    }

    // 推送
    @RequestMapping(value = "subscribe")
    @ResponseBody
    public WebAsyncTask<Void> subscribe(@RequestParam(defaultValue = "id") String id, HttpServletResponse response) {
        response.setHeader("Content-Type", "text/event-stream");
        response.setCharacterEncoding("utf-8");
        Callable<Void> callable = () -> {
            responseMap.put(id, response.getWriter());
            writeData(id, "data:" + new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()) , false);
            return null;
        };

        // 采用WebAsyncTask 返回 這樣可以處理超時和錯誤 同時也可以指定使用的Excutor名稱
        WebAsyncTask<Void> webAsyncTask = new WebAsyncTask<>(30000, callable);
        // 注意:onCompletion表示完成,不管你是否超時、是否拋出異常,這個函數都會執行的
        webAsyncTask.onCompletion(() -> System.out.println("程序[正常執行]完成的回調"));

        // 這兩個返回的內容,最終都會放進response里面去===========
        webAsyncTask.onTimeout(() -> {
            responseMap.remove(id);
            System.out.println("超時了!!!");
            return null;
        });
        // 備注:這個是Spring5新增的
        webAsyncTask.onError(() -> {
            System.out.println("出現異常!!!");
            return null;
        });
        return webAsyncTask;
    }

    @RequestMapping(value = "subscribe2")
    public void subscribe2(HttpServletResponse response) {
        response.setHeader("Content-Type", "text/event-stream");
        response.setCharacterEncoding("utf-8");
        try {
            PrintWriter pw = response.getWriter();
            pw.println("data:" + new SimpleDateFormat("YYYY-MM-dd hh:mm:ss").format(new Date()));
            pw.println();
            pw.flush();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
View Code

 

注意(避坑):

1、數據內容用data字段表示。

    上面代碼中,事件對象的 "data:"屬性就是服務器端傳回的數據(文本格式),只有這種格式EventSource 的message事件才會觸發。

: this is a test stream\n\n

data: some text\n\n

data: another message\n
data: with two lines \n\n

2、不同事件的內容之間通過僅包含回車符和換行符的空行(“\n\n”)來分隔 (上文代碼用了兩個”pw.println()“)

data: first event
 
data: second event
id: 100
 
event: myevent
data: third event
id: 101
 
: this is a comment
data: fourth event
data: fourth event continue

 

效果(每隔三秒刷新當前時間)

 

 

詳細文檔請看文章:http://www.ruanyifeng.com/blog/2017/05/server-sent_events.html 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM