一、Server-Sent Events规范描述了一个内建的类EventSource,EventSource对象自动建立一个持久的连接,并允许服务器通过这个连接发送消息。
1、与WebSocket类似,其连接是持久的。
2、与WebSocket相比,EventSource是与服务器通信的一种不那么强大的方式。
二、EventSource在所有现代浏览器(除了 IE)中都得到了支持。

优缺点

一、优点 / 我们为什么要使用它?

  • 主要原因:简单。在很多应用中,WebSocket有点大材小用。
  • 低延迟:消息即时到达,不发无用请求
  • 它是一个普通的旧的 HTTP,不是一个新协议。

二、缺点

  • 服务器维护一个长连接会增加开销

    适用场景

    一、我们需要从服务器接收一个数据流:可能是聊天消息或者市场价格等。这正是EventSource所擅长的。它还支持自动重新连接,而在WebSocket中这个功能需要我们手动实现。

获取消息

一、要开始接收消息,我们只需要创建new EventSource(url)即可。
1、浏览器将会连接到url并保持连接打开,等待事件。
2、服务器响应状态码应该为 200,header 为Content-Type: text/event-stream,然后保持此连接并以一种特殊的格式写入消息,就像这样:

  1. data: Message 1
  2. data: Message 2
  3. data: Message 3
  4. data: of two lines
  • data:后为消息文本,冒号后面的空格是可选的。
  • 消息以双换行符\n\n分隔。
  • 要发送一个换行\n,我们可以在要换行的位置立即再发送一个data:(上面的第三条消息)。

二、在实际开发中,复杂的消息通常是用 JSON 编码后发送。换行符在其中编码为\n,因此不需要多行data:消息。

| 【示例】```javascript data: {“user”:”John”,”message”:”First line\n Second line”}

  1. 1、因此,我们可以假设一个data:只保存了一条消息。<br />2、对于每个这样的消息,都会生成message事件:```javascript
  2. let eventSource = new EventSource("/events/subscribe");
  3. eventSource.onmessage = function(event) {
  4. console.log("New message", event.data);
  5. // 对于上面的数据流将打印三次
  6. };
  7. // 或 eventSource.addEventListener('message', ...)

| | —- |

三、服务器可以在event:中设置自定义事件名称。应该使用addEventListener来处理此类事件,而不是使用on

服务器响应格式

一、服务器发送由\n\n分隔的消息。
二、一条消息可能有以下字段:

  • data:—— 消息体(body),一系列多个data被解释为单个消息,各个部分之间由\n分隔。
  • id:—— 更新lastEventId,重连时以Last-Event-ID发送此 id。
  • retry:—— 建议重连的延迟,以 ms 为单位。无法通过 JavaScript 进行设置。
  • event:—— 事件名,必须在data:之前。

二、一条消息可以按任何顺序包含一个或多个字段,但是id:通常排在最后

跨源请求

一、EventSource支持跨源请求,就像fetch任何其他网络方法。我们可以使用任何 URL:

let source = new EventSource("https://another-site.com/events");

二、远程服务器将会获取到Originheader,并且必须以Access-Control-Allow-Origin响应来处理。
要传递凭证(credentials),我们应该设置附加选项withCredentials,就像这样:

let source = new EventSource("https://another-site.com/events", {
  withCredentials: true, // 允许发送跨源凭证
});

重新连接

一、创建之后,new EventSource连接到服务器,如果连接断开 —— 则重新连接。
1、这非常方便,我们不用去关心重新连接的事情。
二、每次重新连接之间有一点小的延迟,默认为几秒钟。
1、服务器可以使用retry:来设置需要的延迟响应时间(以毫秒为单位)。

retry: 15000
data: Hello, I set the reconnection delay to 15 seconds

2、retry: 既可以与某些数据一起出现,也可以作为独立的消息出现。
三、在重新连接之前,浏览器需要等待那么多毫秒。甚至更长,例如,如果浏览器知道(从操作系统)此时没有网络连接,它会等到连接出现,然后重试。
1、如果服务器想要浏览器停止重新连接,那么它应该使用 HTTP 状态码 204 进行响应。
2、如果浏览器想要关闭连接,则应该调用eventSource.close():

let eventSource = new EventSource(...);

eventSource.close();

四、如果响应具有不正确的Content-Type或者其 HTTP 状态码不是 301,307,200 和 204,则不会进行重新连接。在这种情况下,将会发出”error”事件,并且浏览器不会重新连接。
五、当连接最终被关闭时,就无法“重新打开”它。如果我们想要再次连接,只需要创建一个新的EventSource。

EventSource对象的属性

连接状态:readyState

一、EventSource对象有readyState属性,该属性具有下列值之一:

EventSource.CONNECTING = 0; // 连接中或者重连中
EventSource.OPEN = 1;       // 已连接
EventSource.CLOSED = 2;     // 连接已关闭

二、对象创建完成或者连接断开后,它始终是EventSource.CONNECTING(等于0)。
三、我们可以查询该属性以了解EventSource的状态。

消息 id:lastEventId

一、当一个连接由于网络问题而中断时,客户端和服务器都无法确定哪些消息已经收到哪些没有收到。
二、为了正确地恢复连接,每条消息都应该有一个id字段,就像这样:

data: Message 1
id: 1

data: Message 2
id: 2

data: Message 3
data: of two lines
id: 3

三、当收到具有id的消息时,浏览器会:
1、将属性eventSource.lastEventId设置为其值。
2、重新连接后,发送带有id的 header Last-Event-ID,以便服务器可以重新发送后面的消息。
四、id被服务器附加到data消息后,以确保在收到消息后lastEventId会被更新。

EventSource对象的方法

  • close()

关闭连接。

EventSource对象的事件

Event 类型

一、默认情况下EventSource对象生成三个事件:

  • message—— 收到消息,可以用event.data访问。
  • open—— 连接已打开。
  • error—— 无法建立连接,例如,服务器返回 HTTP 500 状态码。

二、服务器可以在事件开始时使用event: …指定另一种类型事件。

event: join
data: Bob

data: Hello

event: leave
data: Bob

三、要处理自定义事件,我们必须使用addEventListener而非onmessage:

eventSource.addEventListener('join', event => {
  alert(`Joined ${event.data}`);
});

eventSource.addEventListener('message', event => {
  alert(`Said: ${event.data}`);
});

eventSource.addEventListener('leave', event => {
  alert(`Left ${event.data}`);
});

完整示例

| 一、效果
1、服务器依次发送1,2,3,最后发送bye并断开连接。
2、然后浏览器会自动重新连接。
二、示例代码
1、index.html```javascript <!DOCTYPE html>

Press the “Start” to begin.

“Stop” to finish

2、server.js```javascript
let http = require('http');
let url = require('url');
let querystring = require('querystring');

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 == 4) {
      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
}

| | —- |