SSE(Server-sent Events)

3,235 阅读3分钟

介绍

MDN介绍

EventSource 是服务器推送的一个网络事件接口。一个EventSource实例会对HTTP服务开启一个持久化的连接,以text/event-stream 格式发送事件, 会一直保持开启直到被要求关闭。

一旦连接开启,来自服务端传入的消息会以事件的形式分发至你代码中。如果接收消息中有一个事件字段,触发的事件与事件字段的值相同。如果没有事件字段存在,则将触发通用事件。

与 WebSockets,不同的是,服务端推送是单向的。数据信息被单向从服务端到客户端分发. 当不需要以消息形式将数据从客户端发送到服务器时,这使它们成为绝佳的选择。例如,对于处理社交媒体状态更新,新闻提要或将数据传递到客户端存储机制(如IndexedDB或Web存储)之类的,EventSource无疑是一个有效方案。

高程介绍

SSE(Server-sent Events,服务器发送事件),是围绕只读Comet交互退出的API或者模式。SSE API用于创建到服务器的单向连接,服务器通过这个连接可以发送任意数量的数据。服务器响应的MIME类型必须是text/event-steam(Content-type:text/event-steam;),而且是浏览器中的JS API能解析格式输出。SSE支持短轮询、长轮询和HTTP流,而且能在断开连接时自动确定何时重新连接。

其他参数、事件、状态看MDN文档,上面的字数够多的了,上代码:

Node起个服务先,下面介绍使用原生SSE和Vue-SSE库都可以用

Node搭个简易版服务

const Koa = require("koa"); //npm安装
const { PassThrough } = require("stream");
const app = new Koa();

let response = {
  code: 200,
  data: {
    id: 1,
    name: `Jack`
  }
};
app.use(async ctx => {
  const { url } = ctx;
  if (url === "/sse") {
    //!!!必须有
    ctx.set({
      "Content-Type": "text/event-stream",
      "Cache-Control": "no-cache",
      Connection: "keep-alive"
    });
    //!!!必须有
    
    //PassThrough流:一个简单地将输入字节传递到输出的流
    let stream = new PassThrough();
    ctx.status = 200;
    
    //模拟持续服务端传值给客户端
    const interval = setInterval(() => {
      let { id } = response.data;
      if (id % 2 === 0) {
        stream.write("event: customEvents\n");
      }
      stream.write(`id: ${id}\n`);
      stream.write(`data: ${JSON.stringify(response)}\n`);
      stream.write("retry: 1000\n");
      stream.write("\n\n");
      response.data.id++;
    }, 2000);

    ctx.body = stream;
    
    //流关闭,客户端主动断开连接
    stream.on("close", () => {
      console.log("连接断开");
      clearInterval(interval);
    });
  }
});
app.listen(8081, () => {
  console.log("监听端口:8081,地址:127.0.0.1:8081");
});


原生EventSource

<template>
    <div>
        <h1>
            testSSE:
            <button @click="close">关闭</button>
        </h1>
        <h2>默认事件Message返回值:{{message}}</h2>
        <h2>自定义事件customEvents返回值:{{customEvents}}</h2>
    </div>
</template>

<script>
export default {
    data() {
        return {
            message: null, //message返回值
            customEvents: null, //customEvents返回值
            source: null, //SSE实例
        };
    },
    beforeMount() {
        this.init();
    },
    methods: {
        close() {
            //浏览器关闭SSE连接
            this.source.close();
        },
        init() {
            //初始化SSE事件,url为当前同源目录
            this.source = new EventSource('/sse');
            //监听message事件
            this.source.onmessage = (res) => {
                let { data } = JSON.parse(res.data);
                this.message = data;
            };
            //监听自定义customEvents事件
            this.source.addEventListener('customEvents', (res) => {
                let { data } = JSON.parse(res.data);
                this.customEvents = data;
            });
        },
    },
};
</script>

Snipaste_2022-03-16_11-22-12.png

Vue-SSE包

vue-sse Vue版本

event-source-polyfill 兼容浏览器版本

# npm
npm install --save vue-sse@2.5.0

# OR yarn
yarn add vue-sse@2.5.0

#OR pnpm
pnpm add vue-sse@2.5.0
 
import VueSSE from 'vue-sse';

// using defaults
Vue.use(VueSSE);

// OR specify custom defaults (described below)
Vue.use(VueSSE, {
  format: 'json',                 //数据格式
  url: '/my-events-server',       //路径
  withCredentials: true,          //标识为open? // withCredentials should be set after "open" for Safari and Chrome (< 19 ?)
  forcePolyfill:false,            //强制使用原生SSE,使用另一个库event-source-polyfill
  polyfill: true,                 //支持旧版浏览器
  polyfillOptions:null,           //配置参数
});


<template>
    <div>
        <h1>
            testVueSSE:
            <button @click="close">关闭</button>
        </h1>
        <h2>默认事件Message返回值:{{message}}</h2>
        <h2>自定义事件customEvents返回值:{{customEvents}}</h2>
    </div>
</template>

<script>
export default {
    data() {
        return {
            message: null, //message返回值
            customEvents: null, //customEvents返回值
            vueSse: null, //实例
        };
    },
    mounted() {
        //实例化
        this.vueSse = this.$sse.create({ 
                url: '/sse', 
                format: 'json', 
                withCredentials: true 
           });
        this.init();
    },
    sse: {
        //配置后自动添加断开连接事件,源码里面是做了判断,
        //然后加在组件 beforeDestroy 生命周期里
        cleanup: true,
    },
    methods: {
        close() {
            //浏览器关闭SSE连接
            this.vueSse.disconnect();
        },
        init() {
            let vueSse = this.vueSse;
            //监听 message
            vueSse.on('message', this.handleMessage);
            //监听 customEvents
            vueSse.once('customEvents', this.handleCustomEvents);
            
            //里面的 on、once、off 是用了发布订阅模式,
            //源码 once 方法这有点小问题,写文章时改了
            //源码但还没提PR(主要是没提过,不会弄)
            //执行 connect() 返回个Promise,
            vueSse
                .connect()
                .then((sse) => {
                    console.log("We're connected!");
                    setTimeout(() => {
                        //解绑 message
                        vueSse.off('message', this.handleMessage);
                        console.log('Stopped listening to event-less messages!');
                    }, 6000);
                })
                .catch((err) => console.error('Failed make initial connection:', err));
        },
        //message回调
        handleMessage(res) {
            console.info('Message:', res);
            let { data } = res;
            this.message = data;
        },
        //handleCustomEvents回调
        handleCustomEvents(res) {
            let { data } = res;
            console.info('customEvents:', data);
            this.customEvents = data;
        },
    },
};
</script>

ps:第一次写,很多地方没有涉及,有不对的地方麻烦指出,谢谢

参考文章

MDN

搞懂现代Web端即时通讯技术一文就够:WebSocket、socket.io、SSE

使用 Node 和 Koa 的服务器发送事件 (SSE) 流

SSE-HTTP服务端推送详解