zoukankan      html  css  js  c++  java
  • 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
    
    
    
    data: some text
    
    
    
    data: another message
    
    data: with two lines 
    
    

    2、不同事件的内容之间通过仅包含回车符和换行符的空行(“ ”)来分隔 (上文代码用了两个”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 

  • 相关阅读:
    HTML(三)
    HTML(二)
    HTML(一)
    Python-数据库索引浅谈
    Django-ORM之聚合和分组查询、F和Q查询、事务
    [LeetCode][Python]String to Integer (atoi)
    [LeetCode][Python]Reverse Integer
    [LeetCode][Python]ZigZag Conversion
    [LeetCode][Python]Longest Palindromic Substring
    [LeetCode][Python]Median of Two Sorted Arrays
  • 原文地址:https://www.cnblogs.com/hzb462606/p/15422600.html
Copyright © 2011-2022 走看看