服务器推送事件

1,535 阅读3分钟

概述

服务器推送事件(Server-Sent Events,简称SSE),是浏览器向服务器发送一个HTTP请求后,服务器不断单向地向浏览器推送“信息”(message),本质上,这种通信是以流信息的方式完成的一次用时很长的下载。

特点

  • SSE使用HTTP协议,现有的服务器软件都支持
  • SSE属于轻量级,使用简单
  • SSE默认支持断线重连
  • SSE一般只用来传送文本,二进制数据需要编码后传送
  • SSE支持自定义发送的消息类型

用法

客户端
  • EventSource 对象
    SSE的客户端API部署在EventSource对象上,使用SSE时,浏览器首先生成一个EventSource实例,向服务器发起连接

    let source;
    
    if ('EventSource' in window) {
        source = new EventSource('/api/stream');
    }
    

    EventSource可指定两个参数,urlwithCredentials
    url: EventSource实例的url属性返回连接的网址(请求地址)
    withCredentials: EventSource实例的withCredentials属性返回一个布尔值,表示当前实例是否开启CORS的withCredentials,默认值是false

  • readyState 属性
    表明连接的当前状态,可取以下值:
    0:相当于常量EventSource.CONNECTING,表示连接还未建立,或者断线正在重连
    1:相当于常量EventSource.OPEN,表示连接已经建立,可以接受数据
    2:相当于常量EventSource.CLOSED,表示连接已断,且不会重连

  • 方法

    open:连接一旦建立,就会触发open事件,可在onopen属性定义回调函数

    source.onopen = function(event) {
        console.log('onopen event= ', event);
    };
    
    // 另一种写法
    source.addEventListener('open', function(event) {
        console.log('onopen event= ', event);
    }, false);
    

    error:如果发生通信发生错误或连接中断触发error事件

    source.onerror = function(event) {
        console.log('onerror event= ', event);
    };
    
    // 另一种写法
    source.addEventListener('error', function(event) {
        console.log('onerror event= ', event);
    }, false);
    

    message:客户端收到服务器发送回来的数据就会触发message事件

    source.onmessage = function(event) {
        console.log('onmessage event= ', event);
    };
    
    // 另一种写法
    source.addEventListener('message', function(event) {
        console.log('onmessage event= ', event);
    }, false);
    

    自定义事件:默认情况下,服务器发送回来的数据总是触发message事件,我们可以自定义SSE事件,这种情况下,发送回来的数据不会触发message事件

    source.addEventListener('connecttime', function(event) {
        console.log('自定义事件:', event);
    }, false);
    

    close:用于关闭SSE连接

    source.close();
    
  • 代码

    const _this = this;
    // 创建EventSource实例对象
    const source = new EventSource('/api/stream');
    
    // 连接一旦建立,调用该方法
    source.onopen = function(event) {
        console.log('onopen= ', event);
        _this.isVisible = true;
    };
    
    // 错误
    source.onerror = function(event) {
        console.log('onerror= ', event);
    };
    
    // 默认事件
    source.onmessage = function(event) {
        console.log('onmessage= ', event.data);
    };
    
    // 监听自定义progress方法
    source.addEventListener('progress', function(event) {
        console.log('progress= ', event);
        _this.percentage = ~~event.data;
        if (_this.percentage >= 100) {
            // 关闭连接
            source.close();
        }
    }, false);
    
服务端
  • 数据格式

    服务端向浏览器发送的SSE数据,必须是UTF-8编码的文本,具有如下的HTTP头信息:

    Content-Type: text/event-stream
    Cache-Control: no-cache
    Connection: keep-alive
    

    Content-Type必需指定为text/event-stream

  • 字段

    每一次发送的信息,由若干个message组成,每个message之间用\n\n分隔。每个message内部由若干行组成,每一行的格式如下:

    [field]: value\n
    

    data:数据内容。

    data: message\n\n
    

    如果数据很长,可以分成多行,最后一行用\n\n分隔,中间每行用\n

    data: begin message\n
    data: continue message\n\n
    

    JSON数据:

        data: {\n
        data: data: [],\n
        data: message: '操作成功',\n
        data: status: 0\n
        data: }\n\n
    

    event:表示自定义的事件类型,默认是message事件。浏览器用addEventListener()监听。

    event: connecttime\n
    data: ${new Date()}\n\n
    

    id:数据标识符,相当于每一条数据的编号。浏览器用lastEventId属性读取这个值。一旦连接断线,浏览器会发送一个HTTP头,里面包含一个特殊的Last-Event-ID头信息,用来帮助服务端重建连接。因此,这个头信息可以被视为一种同步机制。

    retry: 指定浏览器重新发起连接的时间间隔。

    retry: 10000\n
    

    导致浏览器重新发起连接:
    ① 时间间隔到期
    ② 由于网络错误等原因,导致连接出错

  • 代码

    const http = require('http');
    
    http.createServer(function(req, res) {
        let fileName = '.' + req.url;
        if (fileName === './stream') {
            res.writeHead(200, {
                "Content-Type": "text/event-stream", // 必需
                "Cache-Control": "no-cache",
                "Connection": "keep-alive",
                "Access-Control-Allow-Origin": "*"
            });
    
            // 指定浏览器重新发起连接的时间间隔:10s
            res.write('retry: 10000\n');
            // 自定义事件:connecttime
            res.write(`event: connecttime\n`);
            // 自定义事件返回数据,以\n\n结束
            res.write(`data: ${new Date()}\n\n`);
    
            let id = 0;
            let timer = setInterval(() => {
                // 默认事件标识符
                res.write(`id: ${id++}\n`);
                // 默认事件返回数据
                res.write(`data: ${new Date()}\n\n`);
            }, 1000);
    
            req.connection.addListener('close', function() {
                clearInterval(timer);
            }, false);
    
        }
    }).listen(3000, '127.0.0.1');
    

浏览器支持情况

caniuse—EventSource

参考资料

Server-sent events
EventSource