1. 9.2 服务器发送事件
      1. 9.2.1 简介
      2. 9.2.2 EventSource 接口
      3. 9.2.3 处理模型
      4. 9.2.4 `Last-Event-ID` 标头
      5. 9.2.5 解析事件流
      6. 9.2.6 解释事件流
      7. 9.2.7 作者注意事项
      8. 9.2.8 无连接推送和其他功能
      9. 9.2.9 垃圾回收
      10. 9.2.10 实现建议

9.2 服务器发送事件

服务器发送事件

所有当前引擎均支持。

Firefox6+Safari5+Chrome6+
Opera11+Edge79+
Edge (旧版)?Internet Explorer
Firefox Android45+Safari iOS5+Chrome Android?WebView Android?Samsung Internet?Opera Android11+

9.2.1 简介

本节为非规范性内容。

为了使服务器能够通过 HTTP 或专用服务器推送协议将数据推送到网页,本规范引入了 EventSource 接口。

使用此 API 包括创建 EventSource 对象和注册事件监听器。

var source = new EventSource('updates.cgi');
source.onmessage = function (event) {
  alert(event.data);
};

在服务器端,脚本(在本例中为“updates.cgi”)以以下形式发送消息,并使用 text/event-stream MIME 类型

data: This is the first message.

data: This is the second message, it
data: has two lines.

data: This is the third message.

作者可以使用不同的事件类型来分隔事件。以下是一个具有两种事件类型“add”和“remove”的流

event: add
data: 73857293

event: remove
data: 2153

event: add
data: 113411

处理此类流的脚本如下所示(其中 addHandlerremoveHandler 是接受一个参数(事件)的函数)

var source = new EventSource('updates.cgi');
source.addEventListener('add', addHandler, false);
source.addEventListener('remove', removeHandler, false);

默认事件类型为“message”。

事件流始终以 UTF-8 解码。无法指定其他字符编码。


事件流请求可以使用 HTTP 301 和 307 重定向,就像普通 HTTP 请求一样。如果连接关闭,客户端将重新连接;可以使用 HTTP 204 无内容响应代码告诉客户端停止重新连接。

使用此 API 而不是使用 XMLHttpRequestiframe 模拟它,允许用户代理在用户代理实现者和网络运营商能够提前协调的情况下,更好地利用网络资源。除了其他好处之外,这可以显著节省便携式设备的电池寿命。在以下关于 无连接推送 的部分中将进一步讨论此问题。

9.2.2 EventSource 接口

EventSource

所有当前引擎均支持。

Firefox6+Safari5+Chrome6+
Opera11+Edge79+
Edge (旧版)?Internet Explorer
Firefox Android45+Safari iOS5+Chrome Android?WebView Android?Samsung Internet?Opera Android11+
[Exposed=(Window,Worker)]
interface EventSource : EventTarget {
  constructor(USVString url, optional EventSourceInit eventSourceInitDict = {});

  readonly attribute USVString url;
  readonly attribute boolean withCredentials;

  // ready state
  const unsigned short CONNECTING = 0;
  const unsigned short OPEN = 1;
  const unsigned short CLOSED = 2;
  readonly attribute unsigned short readyState;

  // networking
  attribute EventHandler onopen;
  attribute EventHandler onmessage;
  attribute EventHandler onerror;
  undefined close();
};

dictionary EventSourceInit {
  boolean withCredentials = false;
};

每个 EventSource 对象都与以下内容相关联

除了 URL 之外,这些目前没有在 EventSource 对象上公开。

source = new EventSource( url [, { withCredentials: true } ])

EventSource/EventSource

所有当前引擎均支持。

Firefox6+Safari5+Chrome6+
Opera11+Edge79+
Edge (旧版)?Internet Explorer
Firefox Android45+Safari iOS5+Chrome Android?WebView Android?Samsung Internet?Opera Android12+

创建一个新的 EventSource 对象。

url 是一个字符串,用于提供事件流的 URL

withCredentials 设置为 true 将将连接请求到 url凭据模式 设置为“include”。

source.close()

EventSource/close

所有当前引擎均支持。

Firefox6+Safari5+Chrome6+
Opera12+Edge79+
Edge (旧版)?Internet Explorer
Firefox Android45+Safari iOS5+Chrome Android?WebView Android?Samsung Internet?Opera Android12+

中止为该 EventSource 对象启动的任何 fetch 算法实例,并将 readyState 属性设置为 CLOSED

source.url

EventSource/url

所有当前引擎均支持。

Firefox6+Safari6+Chrome18+
Opera12+Edge79+
Edge (旧版)?Internet Explorer
Firefox Android45+Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android12+

返回 提供事件流的 URL

source.withCredentials

EventSource/withCredentials

所有当前引擎均支持。

Firefox6+Safari7+Chrome26+
Opera12+Edge79+
Edge (旧版)?Internet Explorer
Firefox Android45+Safari iOS?Chrome Android?WebView Android?Samsung Internet?Opera Android12+

如果连接请求到 提供事件流的 URL凭据模式 设置为“include”,则返回 true,否则返回 false。

source.readyState

EventSource/readyState

所有当前引擎均支持。

Firefox6+Safari5+Chrome6+
Opera12+Edge79+
Edge (旧版)?Internet Explorer
Firefox Android45+Safari iOS5+Chrome Android?WebView Android?Samsung Internet?Opera Android12+

返回此 EventSource 对象连接的状态。它可以具有以下描述的值。

当调用 EventSource(url, eventSourceInitDict) 构造函数时,必须运行以下步骤

  1. ev 成为一个新的 EventSource 对象。

  2. settings 成为 ev相关设置对象

  3. urlRecord 成为根据 url 相对于 settings 编码解析 URL 的结果。

  4. 如果 urlRecord 为失败,则抛出一个 "SyntaxError" DOMException

  5. evURL 设置为 urlRecord

  6. corsAttributeState匿名

  7. 如果 eventSourceInitDictwithCredentials 成员的值为 true,则将 corsAttributeState 设置为 使用凭据 并将 evwithCredentials 属性设置为 true。

  8. request 成为根据 urlRecord、空字符串和 corsAttributeState 创建潜在 CORS 请求 的结果。

  9. request客户端 设置为 settings

  10. 用户代理可以 设置 (`Accept`, `text/event-stream`) 在 request标头列表 中。

  11. request缓存模式 设置为“no-store”。

  12. request发起者类型 设置为“other”。

  13. ev请求 设置为 request

  14. 让给定 响应 resprocessEventSourceEndOfBody 为以下步骤:如果 res 不是 网络错误,则 重新建立连接

  15. Fetch request,其中 processResponseEndOfBody 设置为 processEventSourceEndOfBodyprocessResponse 设置为以下步骤,给定 响应 res

    1. 如果 res中止的网络错误,则 使连接失败

    2. 否则,如果 res网络错误,则 重新建立连接,除非用户代理知道这将是徒劳的,在这种情况下,用户代理可以 使连接失败

    3. 否则,如果res状态不是200,或者res的`内容类型`不是`text/event-stream`,则断开连接

    4. 否则,宣布连接解释res主体逐行。

  16. 返回ev


The url 属性的 getter 必须返回此EventSource 对象的url序列化

The withCredentials 属性必须返回它最后初始化的值。当对象被创建时,它必须被初始化为 false。

The readyState 属性表示连接的状态。它可以具有以下值

CONNECTING (数值 0)
连接尚未建立,或者它已关闭,并且用户代理正在重新连接。
OPEN (数值 1)
用户代理有一个开放的连接,并且正在分发事件,因为它正在接收它们。
CLOSED (数值 2)
连接未打开,用户代理未尝试重新连接。要么存在致命错误,要么close()方法被调用。

当对象被创建时,它的readyState必须设置为CONNECTING (0)。下面给出的处理连接的规则定义了该值何时更改。

The close() 方法必须中止为此EventSource 对象启动的fetch 算法的任何实例,并且必须将readyState 属性设置为CLOSED

以下是所有实现EventSource 接口的对象必须支持的事件处理程序(以及它们的对应事件处理程序事件类型),作为事件处理程序 IDL 属性

事件处理程序 事件处理程序事件类型
onopen

EventSource/open_event

所有当前引擎均支持。

Firefox6+Safari5+Chrome6+
Opera12+Edge79+
Edge (旧版)?Internet Explorer
Firefox Android45+Safari iOS5+Chrome Android?WebView Android?Samsung Internet?Opera Android12+
open
onmessage

EventSource/message_event

所有当前引擎均支持。

Firefox6+Safari5+Chrome6+
Opera12+Edge79+
Edge (旧版)?Internet Explorer
Firefox Android45+Safari iOS5+Chrome Android?WebView Android?Samsung Internet?Opera Android12+
message
onerror

EventSource/error_event

所有当前引擎均支持。

Firefox6+Safari5+Chrome6+
Opera12+Edge79+
Edge (旧版)?Internet Explorer
Firefox Android45+Safari iOS5+Chrome Android?WebView Android?Samsung Internet?Opera Android12+
error

9.2.3 处理模型

当用户代理要宣布连接时,用户代理必须排队一个任务,如果readyState属性设置为除CLOSED之外的值,则将readyState属性设置为OPEN,并在EventSource 对象上触发一个名为open的事件。

当用户代理要重新建立连接时,用户代理必须运行以下步骤。这些步骤是并行运行的,而不是作为任务的一部分。(它排队的任务当然像普通任务一样运行,而不是自己并行运行。)

  1. 排队一个任务来运行以下步骤

    1. 如果readyState属性设置为CLOSED,则中止该任务。

    2. readyState属性设置为CONNECTING

    3. EventSource 对象上触发一个名为error的事件。

  2. 等待等于事件源的重新连接时间的延迟。

  3. 可选地,再等待一段时间。特别地,如果之前的尝试失败,那么用户代理可能会引入指数退避延迟以避免过度加载可能已经过载的服务器。或者,如果操作系统已报告没有网络连接,用户代理可能会等待操作系统宣布网络连接已恢复,然后再重试。

  4. 等待上述任务运行,如果它尚未运行。

  5. 排队一个任务来运行以下步骤

    1. 如果EventSource 对象的readyState属性没有设置为CONNECTING,则返回。

    2. request成为EventSource 对象的请求

    3. 如果EventSource 对象的最后一个事件 ID 字符串不是空字符串,则

      1. lastEventIDValue成为EventSource 对象的最后一个事件 ID 字符串以 UTF-8 编码

      2. request标头列表设置 (`Last-Event-ID`, lastEventIDValue)。

    4. 像本节前面描述的那样,获取 request 并处理以这种方式获得的响应(如果有)。

当用户代理要断开连接时,用户代理必须排队一个任务,如果readyState属性设置为除CLOSED之外的值,则将readyState属性设置为CLOSED,并在EventSource 对象上触发一个名为error的事件。**一旦用户代理断开了连接,它就不会尝试重新连接。**


任何由EventSource 对象排队任务任务源远程事件任务源

9.2.4 The `Last-Event-ID` header

The Last-Event-ID` HTTP 请求标头在用户代理要重新建立连接时,向服务器报告EventSource 对象的最后一个事件 ID 字符串

参见whatwg/html 问题 #7363 以更好地定义值空间。它本质上是任何 UTF-8 编码的字符串,不包含 U+0000 NULL、U+000A LF 或 U+000D CR。

9.2.5 解析事件流

此事件流格式的MIME 类型text/event-stream

事件流格式如以下 ABNF 的 stream 产生式所描述,其字符集为 Unicode。 [ABNF]

stream        = [ bom ] *event
event         = *( comment / field ) end-of-line
comment       = colon *any-char end-of-line
field         = 1*name-char [ colon [ space ] *any-char ] end-of-line
end-of-line   = ( cr lf / cr / lf )

; characters
lf            = %x000A ; U+000A LINE FEED (LF)
cr            = %x000D ; U+000D CARRIAGE RETURN (CR)
space         = %x0020 ; U+0020 SPACE
colon         = %x003A ; U+003A COLON (:)
bom           = %xFEFF ; U+FEFF BYTE ORDER MARK
name-char     = %x0000-0009 / %x000B-000C / %x000E-0039 / %x003B-10FFFF
                ; a scalar value other than U+000A LINE FEED (LF), U+000D CARRIAGE RETURN (CR), or U+003A COLON (:)
any-char      = %x0000-0009 / %x000B-000C / %x000E-10FFFF
                ; a scalar value other than U+000A LINE FEED (LF) or U+000D CARRIAGE RETURN (CR)

此格式的事件流必须始终以 UTF-8 编码。 [ENCODING]

行必须用 U+000D 回车符 U+000A 换行符 (CRLF) 字符对、单个 U+000A 换行符 (LF) 字符或单个 U+000D 回车符 (CR) 字符隔开。

由于为此类资源建立到远程服务器的连接预计是长期的,因此 UA 应确保使用适当的缓冲。特别是,虽然行缓冲(行定义为以单个 U+000A 换行符 (LF) 字符结尾)是安全的,但块缓冲或行缓冲(具有不同的预期行结尾)会导致事件分发延迟。

9.2.6 解释事件流

流必须使用UTF-8 解码算法解码。

The UTF-8 解码 算法将删除任何前导 UTF-8 字节顺序标记 (BOM)(如果有)。

然后,必须通过逐行读取流来解析流,其中 U+000D 回车符 U+000A 换行符 (CRLF) 字符对、单个 U+000A 换行符 (LF) 字符(前面没有 U+000D 回车符 (CR) 字符)和单个 U+000D 回车符 (CR) 字符(后面没有 U+000A 换行符 (LF) 字符)是行可以结束的方式。

解析流时,必须为其关联一个 data 缓冲区、一个 事件类型 缓冲区和一个 最后一个事件 ID 缓冲区。它们必须初始化为空字符串。

必须按接收顺序处理行,如下所示

如果该行为空(空行)

分发事件,如下定义。

如果该行以 U+003A 冒号字符 (:) 开头

忽略该行。

如果该行包含 U+003A 冒号字符 (:)

收集行中第一个 U+003A 冒号字符 (:) 之前的字符,并将该字符串设为 field

收集行中第一个 U+003A 冒号字符 (:) 之后的字符,并将该字符串设为 value。如果 value 以 U+0020 空格字符开头,则将其从 value 中删除。

处理字段 使用以下步骤,使用 field 作为字段名称和 value 作为字段值。

否则,字符串不为空,但它不包含 U+003A COLON 字符 (:)

处理字段 使用以下步骤,使用整行作为字段名称,并使用空字符串作为字段值。

到达文件末尾后,必须丢弃任何挂起的 data。 (如果文件在事件中间结束,在最后一行空白行之前,则不会分派不完整的事件。)


给定字段名称和字段值,处理字段 的步骤取决于字段名称,如下面的列表所示。字段名称必须按字面意义比较,不执行大小写折叠。

如果字段名称为 "event"

事件类型 缓冲区设置为字段值。

如果字段名称为 "data"

将字段值附加到 data 缓冲区,然后将单个 U+000A LINE FEED (LF) 字符附加到 data 缓冲区。

如果字段名称为 "id"

如果字段值不包含 U+0000 NULL,则将 最后一次事件 ID 缓冲区设置为字段值。否则,忽略该字段。

如果字段名称为 "retry"

如果字段值仅包含 ASCII 数字,则将字段值解释为十进制整数,并将事件流的 重新连接时间 设置为该整数。否则,忽略该字段。

否则

忽略该字段。

当用户代理需要分派事件 时,用户代理必须使用适合用户代理的步骤处理 data 缓冲区、事件类型 缓冲区和 最后一次事件 ID 缓冲区。

对于 Web 浏览器,分派事件 的适当步骤如下

  1. 将事件源的 最后一次事件 ID 字符串 设置为 最后一次事件 ID 缓冲区的值。缓冲区不会重置,因此事件源的 最后一次事件 ID 字符串 仍然设置为该值,直到服务器下一次设置它。

  2. 如果 data 缓冲区为空字符串,则将 data 缓冲区和 事件类型 缓冲区设置为空字符串并返回。

  3. 如果 data 缓冲区的最后一个字符是 U+000A LINE FEED (LF) 字符,则从 data 缓冲区中删除最后一个字符。

  4. event 是使用 MessageEventEventSource 对象的 相关领域创建事件 的结果。

  5. eventtype 属性初始化为 "message",其 data 属性初始化为 data,其 origin 属性初始化为事件流的最终 URL 的 序列化(即重定向后的 URL),以及其 lastEventId 属性初始化为事件源的 最后一次事件 ID 字符串

  6. 如果 事件类型 缓冲区的值不是空字符串,则将新创建的事件的 type 更改为等于 事件类型 缓冲区的值。

  7. data 缓冲区和 事件类型 缓冲区设置为空字符串。

  8. 排队一个任务,如果 readyState 属性设置为除 CLOSED 之外的任何值,则在 EventSource 对象上分派 新创建的事件。

如果事件没有 "id" 字段,但较早的事件确实设置了事件源的 最后一次事件 ID 字符串,则事件的 lastEventId 字段将设置为最后一次看到的 "id" 字段的值。

对于其他用户代理,分派事件 的适当步骤取决于实现,但至少它们必须在返回之前将 data事件类型 缓冲区设置为空字符串。

以下事件流,在后面跟着一个空白行

data: YHOO
data: +2
data: 10

...将导致在 EventSource 对象上分派具有 MessageEvent 接口的 message 事件。事件的 data 属性将包含字符串 "YHOO\n+2\n10"(其中 "\n" 代表换行符)。

这可以按如下方式使用

var stocks = new EventSource("https://stocks.example.com/ticker.php");
stocks.onmessage = function (event) {
  var data = event.data.split('\n');
  updateStocks(data[0], data[1], data[2]);
};

...其中 updateStocks() 是一个定义为

function updateStocks(symbol, delta, value) { ... }

...或类似的函数。

以下流包含四个块。第一个块只有一个注释,不会触发任何内容。第二个块分别有两个名为 "data" 和 "id" 的字段;将为该块触发一个事件,数据为 "first event",然后将最后一次事件 ID 设置为 "1",以便如果连接在该块和下一个块之间断开,服务器将发送一个 `Last-Event-ID` 标头,其值为 `1`。第三个块触发一个事件,数据为 "second event",并且也有一个 "id" 字段,这次没有值,这会将最后一次事件 ID 重置为空字符串(意味着在尝试重新连接的情况下,现在不会发送 `Last-Event-ID` 标头)。最后,最后一个块只触发一个事件,数据为 " third event"(带有单个前导空格字符)。请注意,最后一个仍然必须以空白行结尾,流的结束不足以触发最后一个事件的分派。

: test stream

data: first event
id: 1

data:second event
id

data:  third event

以下流触发两个事件

data

data
data

data:

第一个块触发事件,数据设置为空字符串,如果最后一个块后面跟着一个空白行,则最后一个块也会触发事件。中间块触发一个事件,数据设置为一个换行符。最后一个块被丢弃,因为它后面没有空白行。

以下流触发两个相同的事件

data:test

data: test

这是因为如果冒号后面有空格,则会忽略该空格。

9.2.7 作者注意事项

众所周知,遗留代理服务器在某些情况下会在大约 15 秒的超时后断开 HTTP 连接。为了防止此类代理服务器,作者可以每 15 秒左右包含一条注释行(以 ':' 字符开头的行)。

希望将事件源连接相互关联或与先前提供的特定文档关联的作者可能会发现依赖 IP 地址不起作用,因为单个客户端可以具有多个 IP 地址(由于具有多个代理服务器),并且单个 IP 地址可以具有多个客户端(由于共享一个代理服务器)。最好在提供文档时在文档中包含一个唯一的标识符,然后在建立连接时将该标识符作为 URL 的一部分传递。

作者还应注意,HTTP 分块可能会对该协议的可靠性产生意想不到的负面影响,尤其是当分块由不知道时间要求的不同层执行时。如果这是一个问题,可以禁用用于提供事件流的分块。

支持 HTTP 每个服务器连接限制的客户端在从一个站点打开多个页面时可能会遇到问题,如果每个页面都有一个指向相同域的 EventSource。作者可以使用相对复杂的机制来避免这种情况,即为每个连接使用唯一的域名,或者允许用户在每个页面基础上启用或禁用 EventSource 功能,或者使用 共享 worker 共享单个 EventSource 对象。

9.2.8 无连接推送和其他功能

在受控环境中运行的用户代理,例如绑定到特定运营商的移动手机上的浏览器,可能会将连接管理卸载到网络上的代理。在这种情况下,为了符合标准,用户代理被认为包括手机软件和网络代理。

例如,移动设备上的浏览器在建立连接后,可能会检测到它处于支持网络上,并请求网络上的代理服务器接管连接管理。这种情况的时间线可能如下

  1. 浏览器连接到远程 HTTP 服务器并请求作者在 EventSource 构造函数中指定的资源。
  2. 服务器偶尔发送消息。
  3. 在两条消息之间,浏览器检测到除了保持 TCP 连接存活所需的网络活动外,它处于空闲状态,并决定切换到睡眠模式以节省电力。
  4. 浏览器断开与服务器的连接。
  5. 浏览器联系网络上的服务,并请求该服务(“推送代理”)保持连接。
  6. “推送代理”服务联系远程 HTTP 服务器并请求作者在 EventSource 构造函数中指定的资源(可能包括 `Last-Event-ID` HTTP 标头等)。
  7. 浏览器允许移动设备进入睡眠状态。
  8. 服务器发送另一条消息。
  9. “推送代理”服务使用诸如 OMA 推送之类的技术将事件传达给移动设备,该设备仅唤醒足够的时间来处理事件,然后返回睡眠状态。

这可以减少总数据使用量,从而节省大量的电能。

除了按照本规范以及上述描述以更分布式的方式实现现有 API 和 text/event-stream 线路格式外,其他适用规范 定义的事件帧格式也可能会得到支持。本规范未定义如何解析或处理这些格式。

9.2.9 垃圾回收

EventSource 对象的 readyStateCONNECTING,并且该对象为 openmessageerror 事件注册了一个或多个事件监听器时,必须存在一个从 WindowWorkerGlobalScope 对象到 EventSource 对象本身的强引用,其中 EventSource 对象的构造函数是从该对象中调用的。

EventSource 对象的 readyStateOPEN,并且该对象为 messageerror 事件注册了一个或多个事件监听器时,必须存在一个从 WindowWorkerGlobalScope 对象到 EventSource 对象本身的强引用,其中 EventSource 对象的构造函数是从该对象中调用的。

当存在一个由 EventSource 对象在 远程事件任务源 上排队的任务时,必须存在一个从 WindowWorkerGlobalScope 对象到该 EventSource 对象的强引用,其中 EventSource 对象的构造函数是从该对象中调用的。

如果用户代理要强制关闭 EventSource 对象(当 Document 对象永久消失时发生),则用户代理必须中止为该 EventSource 对象启动的任何 fetch 算法实例,并且必须将 readyState 属性设置为 CLOSED

如果 EventSource 对象在连接仍然打开的情况下被垃圾回收,则用户代理必须中止该 EventSource 对象打开的任何 fetch 算法实例。

9.2.10 实现建议

本节为非规范性内容。

强烈建议用户代理在其开发控制台中提供有关 EventSource 对象及其相关网络连接的详细诊断信息,以帮助作者调试使用此 API 的代码。

例如,用户代理可以有一个面板显示页面创建的所有 EventSource 对象,每个对象都列出构造函数的参数,是否存在网络错误,连接的 CORS 状态以及客户端发送的标题和从服务器接收到的标题以导致该状态,收到的消息以及消息是如何解析的,等等。

尤其鼓励实现每当触发 error 事件时向其开发控制台报告详细的信息,因为事件本身几乎没有信息可用。