读取同源的服务器推送事件流:自动重连并携带最后事件号,事件成批进入统一事件队列,宿主按应用处理速度控制读取而不丢事件。
源码浏览已生成;不能解析的文件以纯文本展示,不表示通过类型检查。
# 网页事件源接口规范 0.1.0
本包让浏览器里的豫言应用**读取同源的服务器推送事件流**(Server-Sent Events,`text/event-stream`)。与浏览器原生 `EventSource` 相比有两处刻意的不同:一、事件不是逐条回调,而是**成批**进入统一事件队列(见[网页事件接口规范](../网页事件/规范.汉语.md)),一次回放几千条历史也只产生少量队列事件;二、宿主按应用的处理速度读取网络流,队列里积压过多时**暂停读取**(由 HTTP 流控把压力传回服务器),因此**不会丢失任何消息**——原生 `EventSource` 与统一队列的丢弃策略都做不到这一点。服务端一侧的事件流回应见[网页事件流](../网页事件流/规范.汉语.md)。应用依赖本包后,全部函数都必须实现。
## 连接行为
宿主按 WHATWG SSE 规范解析响应,并复现 `EventSource` 的重连语义:
- 请求头带 `Accept: text/event-stream` 与 `Cache-Control: no-cache`,`credentials: 'same-origin'`;已知最后事件号时带 `Last-Event-ID`(第一次连接用选项的 `起始事件号`,之后用流里最近收到的 `id:`)。
- 只有 **HTTP 200** 且 `Content-Type` 为 `text/event-stream`(忽略参数与大小写)才算连接成功。其他状态码(含 204)与内容类型不对,都是**致命错误**:投递 `错误`(`已关闭`)后不再重连、不再有事件。
- 流正常结束或网络中断时,投递 `错误`(`重连中`),等待重连间隔后自动重连,成功后再投递 `打开`;重连间隔初始为选项 `重连毫秒`(默认 3000),服务器发来 `retry:` 字段时改用它(限制在 100 至 60000 毫秒内)。
- 事件按 SSE 规范切分:`data:` 多行以换行连接;`event:` 给出事件名(默认 `message`);`id:` 更新“最后事件号”(含 NUL 的忽略),**每个事件带发出时的最后事件号**;空 `data` 不产生事件;注释行(`:` 开头)忽略;行结束符可为 `\n`、`\r`、`\r\n`;流首的 BOM 忽略;无效 UTF-8 字节按浏览器规则替换为 U+FFFD。
- 单个事件的数据超过 1 MiB 视为致命错误(`已关闭`,原因“事件过大”)。
## 函数
### `打开事件源 : 字符串(网址) → 字符串(选项 JSON) → 整数`
`网址` 规则同网页请求包(`/` 开头的站内路径或同源绝对网址,至多 8192 码元,无控制字符与反斜杠)。`选项` 是 JSON 对象,可为空文字(视同 `{}`);字段:
| 字段 | 类型与默认 | 规则 |
| --- | --- | --- |
| `起始事件号` | 文字,默认无 | 至多 256 个 UTF-16 码元、不含换行与 NUL;第一次连接带为 `Last-Event-ID`,用于从游标续读(例如上次读到的编号) |
| `重连毫秒` | 整数,默认 3000 | 100 至 60000;重连间隔的初值 |
未知字段、类型不对、网址非同源、同时已有 8 个事件源时抛事故。返回**事件源号**(正整数,实例内单调递增,不复用);连接在后台建立。
### `关闭事件源 : 整数 → 爻`
停止事件源并移除它尚未被取走的事件。事件源仍存在(含重连等待中),或有未取走的事件被移除时返回阳;事件源号未知,或它已因致命错误自行关闭且事件已取尽时返回阴。**关闭之后保证不再收到该事件源号的事件。**
## 事件
类型 `事件流`,`订阅号` = 事件源号,`名称` 与 `详情` 如下(其余字段依网页事件规范):
| 名称 | 详情 | 含义 |
| --- | --- | --- |
| `打开` | `{"重连":爻}` | 连接成功;`重连` 在第一次连接后为阳 |
| `消息` | `{"批":[{"事件号":"12","事件名":"message","数据":"…"},…]}` | 一批事件,1 至 256 项,按到达顺序;一批的数据总量至多约 1 MiB(单个事件更大时它自己成一批) |
| `错误` | `{"状态":"重连中"或"已关闭","原因":文字,"HTTP状态":整数(可省)}` | 连接中断;`重连中` 后宿主会自动重连(成功则有新的 `打开`),`已关闭` 后该事件源终止 |
`原因` 有:`服务端关闭`(流正常结束)、`网络中断`(连接失败、读流出错,含被重定向到别的来源——`mode: same-origin` 视为网络错误,按重连间隔重试)、`HTTP 状态 N`(附 `HTTP状态`)、`内容类型不是 text/event-stream`、`事件过大`。同一事件源的事件保持先后:`打开` → 若干 `消息` → `错误` → `打开` → …。应用处理 `消息` 批时逐项取 `批[i]`,并自己按 `事件号`(例如业务里的递增编号)去重——重连可能重放少量已见过的事件。
**背压**:宿主对每个事件源,只要队列里该事件源未取走的事件多于 8 个,就暂停读取网络流,待应用取走后再继续;暂停期间服务器一侧的写入会按 HTTP 流控受阻(服务端可用[网页事件流](../网页事件流/规范.汉语.md)包的背压感知机制配合)。事件类型 `事件流` 的队列上限默认 1024,背压保证积压远低于此,不会触发丢弃。应用长时间不取事件时,连接保持暂停,而不是丢消息。
## 限额与失败分类
| 项 | 限额 |
| --- | --- |
| 同时存在的事件源 | 8 |
| 单个事件数据 | 1 MiB |
| 一批事件 | 256 项且约 1 MiB |
| 未取走事件(每事件源) | 约 8 批(背压阈值) |
| 网址 | 8192 个 UTF-16 码元 |
违规调用(网址不合规或非同源、选项无效、事件源已满、宿主已关闭)抛豫言事故;连接与流的运行时失败通过 `错误` 事件报告,不抛事故。
## 一致性样例
1. 服务器依次发出 `id:1 data:a`、`id:2 event:x data:b`:应用先得到 `打开`,再得到一个 `消息` 事件,`批` 为 `[{"事件号":"1","事件名":"message","数据":"a"},{"事件号":"2","事件名":"x","数据":"b"}]`(同一到达窗口内合并为一批也是合法的)。
2. 服务器发送 `data:` 两行 `第一`、`第二`:一个事件,数据 `第一\n第二`。
3. 服务器 3 秒内关闭连接:应用得到 `错误`(`重连中`,`服务端关闭`),随后自动重连,服务器收到 `Last-Event-ID: 2`,应用得到新的 `打开`(`重连` 为阳)。
4. 服务器返回 404:应用得到 `错误`(`已关闭`,`HTTP 状态 404`),不再重连;返回 `text/html`:`已关闭`,原因为内容类型。
5. 服务器一次回放 5000 条事件而应用暂不取事件:队列里的 `事件流` 事件不超过约 8 批,连接被暂停;应用取走后继续接收,最终 5000 条无一丢失、无一重复。
6. `关闭事件源` 之后,即使网络里还有数据,也不会再有该号的事件;同时打开第 9 个事件源抛事故。
## 边界
本包不规定:跨来源事件流(CORS)、自定义请求头与 POST 请求的事件流、`fetch` 流的通用读取、WebSocket、事件流的服务端写入(见网页事件流包)、断线期间的排队与离线缓存。
# 网页事件源之约 0.1.0
此包令浏览器中豫言之客**读同源之服务器推事流**(Server-Sent Events,text/event-stream)。较浏览器原生之 EventSource,有二处刻意之异:其一,事不逐条回调,而**成批**入统一事列(见[网页事件之约](../网页事件/规范.文言.md)),一回放数千条旧事,亦仅生少数列事;其二,宿主依客处事之缓急而读网流,列中积压过多则**止读**(以 HTTP 流控传压于服务器),故**不失一消息**——原生 EventSource 与统一事列之弃法皆不能也。服务端之事件流应,见[网页事件流](../网页事件流/规范.文言.md)。客依此包,诸术皆当具。
## 联之行
宿主依 WHATWG SSE 之规解其应,并复 EventSource 之重连:
- 请求头带 `Accept: text/event-stream` 与 `Cache-Control: no-cache`,`credentials: 'same-origin'`;已知末事件号则带 `Last-Event-ID`(初连用选项之“起始事件号”,其后用流中最近所收之 `id:`)。
- 惟 **HTTP 二百** 且 Content-Type 为 text/event-stream(略参数与大小写)方为连成。他状态(含二百零四)与类型不合,皆**致命之错**:投“错误”(“已关闭”)后不复重连,亦不复有事。
- 流正终或网中断,则投“错误”(“重连中”),候重连之间而自重连,成则再投“打开”;重连之间初为选项“重连毫秒”(默认三千),服务器发 `retry:` 则从之(限一百至六万毫秒)。
- 事依 SSE 之规而切:`data:` 多行以换行连之;`event:` 定事名(默认 message);`id:` 更新“末事件号”(含 NUL 者略之),**每事带其发时之末事件号**;空 data 不生事;注释行(“:”首)略之;行终可为 `\n`、`\r`、`\r\n`;流首之 BOM 略之;无效 UTF-8 字节依浏览器之规易为 U+FFFD。
- 单事之数逾一 MiB 者为致命之错(“已关闭”,因“事件过大”)。
## 诸术
### 打开事件源:字符串(网址)、字符串(选项),返整数
网址之律同网页请求包(“/”首之站内径,或同源绝对网址;至多八千一百九十二码元;无控制字符与反斜杠)。选项乃癸象对象,可为空文(视同 `{}`);字段:
| 字段 | 类型与默认 | 律 |
| --- | --- | --- |
| 起始事件号 | 文,默认无 | 至多二百五十六 UTF-16 码元,无换行与 NUL;初连以为 Last-Event-ID,用于自游标续读(如前所读至之号) |
| 重连毫秒 | 整数,默认三千 | 一百至六万;重连间之初值 |
未知字段、类型不合、网址非同源、同时已有八个事件源,皆发事故。返**事件源号**(正整数,实例内递增,不复用);联于后台立。
### 关闭事件源:整数,返爻
止事件源并去其未取之事。事件源仍在(含候重连中),或有未取之事被去,则返阳;号未知,或已因致命之错自闭而事已取尽,则返阴。**既闭,保证不复收此号之事。**
## 事
类曰“事件流”,订阅号即事件源号,名与详情如下(余字段依网页事件之约):
| 名 | 详情 | 义 |
| --- | --- | --- |
| 打开 | `{"重连":爻}` | 联成;“重连”于首连之后为阳 |
| 消息 | `{"批":[{"事件号":"12","事件名":"message","数据":"…"},…]}` | 一批之事,一至二百五十六项,依到达之序;一批之数据总量至多约一 MiB(单事更大则自成一批) |
| 错误 | `{"状态":"重连中"或"已关闭","原因":文,"HTTP状态":整数(可省)}` | 联断;“重连中”后宿主自重连(成则有新“打开”),“已关闭”后此事件源终 |
“原因”有:服务端关闭(流正终)、网络中断(连不通、读流出错,含重定向至他来源——`mode: same-origin` 视作网络之错,依重连之间而复试)、`HTTP 状态 N`(附 HTTP状态)、内容类型不是 text/event-stream、事件过大。同一事件源之事保先后:打开 → 若干消息 → 错误 → 打开 → …。客处消息批,逐取 批[i],并自以事件号(如业务中递增之号)去重——重连或重放少许已见之事。
**背压**:宿主于每事件源,列中此源未取之事多于八,则止读网流,待客取后复续;止时服务器一侧之写按 HTTP 流控受阻(服务端可用[网页事件流](../网页事件流/规范.文言.md)包之背压感知以相配)。事类“事件流”之列限默认一千零二十四,背压保积压远低于此,不触发弃。客久不取事,则联持止而非失消息。
## 限额与败之类
| 项 | 限 |
| --- | --- |
| 同时之事件源 | 八 |
| 单事之数据 | 一 MiB |
| 一批之事 | 二百五十六项且约一 MiB |
| 未取之事(每事件源) | 约八批(背压之阈) |
| 网址 | 八千一百九十二 UTF-16 码元 |
违约之调用(网址不合或非同源、选项无效、事件源已满、宿主已闭)发豫言事故;联与流之运行时败,以“错误”事报之,不发事故。
## 一致性之例
1. 服务器依次发 `id:1 data:a`、`id:2 event:x data:b`:客先得“打开”,再得一“消息”事,“批”为 `[{"事件号":"1","事件名":"message","数据":"a"},{"事件号":"2","事件名":"x","数据":"b"}]`(同一到达之窗并为一批亦合法)。
2. 服务器发 data 二行“第一”“第二”:一事,数据“第一\n第二”。
3. 服务器三秒内闭联:客得“错误”(“重连中”,“服务端关闭”),随后自重连,服务器收 `Last-Event-ID: 2`,客得新“打开”(“重连”为阳)。
4. 服务器返四百零四:客得“错误”(“已关闭”,“HTTP 状态 404”),不复重连;返 text/html:“已关闭”,因内容类型。
5. 服务器一回放五千事而客暂不取:列中“事件流”事不逾约八批,联被止;客取后续收,终五千条无失无重。
6. 关闭事件源之后,虽网中犹有数据,亦不复有此号之事;同时开第九个事件源发事故。
## 界
此包不定:跨来源事件流(CORS)、自定请求头与 POST 之事件流、fetch 流之通用读取、WebSocket、事件流之服务端写入(见网页事件流包)、断线期间之排队与离线缓存。