ServerSentEvent简单使用

ServerSentEvent简单使用

hash070 27 2023-01-24

简介

SSE规范描述了一个内置的EventSource类,它能让客户端打开一个与服务端的长连接,然后让客户端可以持续接收来自服务端的消息。

The Server-Sent Events specification describes a built-in class EventSource, that keeps connection with the server and allows to receive events from it.

SSE和WebSocket不一样,它不是一个新的协议,因此上手起来超简单。

我们知道一般情况下HTTP请求是这样的:客户端发送HTTP请求->服务端处理并返回请求结果->客户端接收到服务端返回数据

在这个过程中,客户端占主导地位,它可以随时主动向服务端发送请求,但服务端一直处于被动状态,不能主动向客户端发送信息,因为每个HTTP请求响应完毕后便马上断开了。

SSE的原理就是让客户端和服务端打开一个HTTP长连接,这样服务端就可以通过这个长连接主动向客户端发送数据了,过程如下:

客户端发送HTTP请求->服务端响应200,并告知客户端这是一个event-stream接口->客户端与服务端成功建立一个HTTP长连接,服务端可通过该连接持续向客户端发送数据

Demo

NodeJS示例

SSE服务端

通过NodeJS实现服务端,结合使用node-static实现静态网页部署

server.js

let http = require('http');
let url = require('url');
let querystring = require('querystring');
let node_static = require('node-static');
let fileServer = new node_static.Server('.');

function onDigits(req, res) {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream; charset=utf-8',
    'Cache-Control': 'no-cache'
  });

  let i = 0;

  let timer = setInterval(write, 1000);
  write();

  function write() {
    i++;

    if (i === 4000) {
      res.write('event: bye\ndata: bye-bye\n\n');
      clearInterval(timer);
      res.end();
      return;
    }

    res.write('data: ' + i + '\n\n');

  }
}

function accept(req, res) {

  if (req.url === '/digits') {
    onDigits(req, res);
    return;
  }

  fileServer.serve(req, res);
}


if (!module.parent) {
  http.createServer(accept).listen(8080);
} else {
  exports.accept = accept;
}

index.html

<!DOCTYPE html>
<script>
  let eventSource;

  function start() { // when "Start" button pressed
    if (!window.EventSource) {
      // IE or an old browser
      alert("The browser doesn't support EventSource.");
      return;
    }

    eventSource = new EventSource('digits');

    eventSource.onopen = function(e) {
      log("Event: open");
    };

    eventSource.onerror = function(e) {
      log("Event: error");
      if (this.readyState === EventSource.CONNECTING) {
        log(`Reconnecting (readyState=${this.readyState})...`);
      } else {
        log("Error has occured.");
      }
    };

    eventSource.addEventListener('bye', function(e) {
      log("Event: bye, data: " + e.data);
    });

    eventSource.onmessage = function(e) {
      log("Event: message, data: " + e.data);
    };
  }

  function stop() { // when "Stop" button pressed
    eventSource.close();
    log("eventSource.close()");
  }

  function log(msg) {
    logElem.innerHTML += msg + "<br>";
    document.documentElement.scrollTop = 99999999;
  }
</script>

<button onclick="start()">Start</button> Press the "Start" to begin.
<div id="logElem" style="margin: 6px 0"></div>

<button onclick="stop()">Stop</button> "Stop" to finish.

使用Node启动运行后访问本机1080端口,即可看到效果。

运行效果如下:

image-20230124111633233

NodeJS SSE客户端

SSE是浏览器原生支持的,但它不属于Node的原生库,如果想在NodeJS中创建一个SSE客户端,那么就需要安装相关依赖:

下方任选一个

# NPM
npm i eventsource
# yarn
yarn add eventsource
# pnpm
pnpm add eventsource

然后写一个客户端:

client.js

const EventSource = require('eventsource');

let eventSource;

function start() { // when "Start" button pressed
  eventSource = new EventSource('http://localhost:8080/digits');

  eventSource.onopen = function(e) {
    console.log("Event: open");
  };

  eventSource.onerror = function(e) {
    console.log("Event: error");
    if (this.readyState === EventSource.CONNECTING) {
      console.log(`Reconnecting (readyState=${this.readyState})...`);
    } else {
      console.log("Error has occured.");
    }
  };

  eventSource.addEventListener('bye', function(e) {
    console.log("Event: bye, data: " + e.data);
  });

  eventSource.onmessage = function(e) {
    console.log("Event: message, data: " + e.data);
  };
}

function stop() { // when "Stop" button pressed
  eventSource.close();
  console.log("eventSource.close()");
}

start()

效果如下:

image-20230124111827688

参考文章: