SSE 流中收到不完整行时应如何解析和缓存?
未标记
考查重点:残行保留与完整事件边界。把未到换行的残片留缓冲,下批拼接后逐行解析,空行才完成事件;多data行按协议合并,结束或异常时检查未完整尾段
本题目录
参考答案
本答案基于相关资料,由 AI 辅助编写,供学习与面试准备参考。如有疏漏,欢迎反馈指正。
核心回答
保留两个层次的缓冲:一层保存尚未遇到行结束符的残行;另一层收集当前事件的各个 data 行。下次读取时先拼回残行,只有遇到空行才派发事件。网络读取块不是事件边界,不能对每个读取块直接 split("\n\n") 后独立解析。
字节流先经过带状态的 TextDecoder,防止中文的 UTF-8 编码被切断。行解析同时支持 LF、CRLF 和 CR;如果读取块最后是 CR,应等待下一块判断是否紧跟 LF。
一个可运行的行与事件解析器
下面接收已经流式解码的字符串。示例处理 data/event/id 与注释;自动重连、retry 调度、鉴权和资源上限应由外层客户端负责。
function createSSEParser(onEvent) {
let rest = "",
data = [],
type = "",
lastId = "";
function line(text) {
if (text === "") {
if (data.length) onEvent({ type: type || "message", id: lastId, data: data.join("\n") });
data = [];
type = "";
return;
}
if (text.startsWith(":")) return;
const colon = text.indexOf(":");
const field = colon < 0 ? text : text.slice(0, colon);
let value = colon < 0 ? "" : text.slice(colon + 1);
if (value.startsWith(" ")) value = value.slice(1);
if (field === "data") data.push(value);
else if (field === "event") type = value;
else if (field === "id" && !value.includes("\0")) lastId = value;
// 此示例不实现 retry 调度;未知字段按协议忽略。
}
function drain(final) {
let start = 0;
for (let i = 0; i < rest.length; i++) {
if (rest[i] !== "\r" && rest[i] !== "\n") continue;
if (rest[i] === "\r" && i + 1 === rest.length && !final) break;
line(rest.slice(start, i));
if (rest[i] === "\r" && rest[i + 1] === "\n") i++;
start = i + 1;
}
rest = rest.slice(start);
}
return {
push(text) {
rest += text;
drain(false);
},
finish() {
drain(true);
rest = "";
data = [];
type = "";
}
};
}
push() 可以任意分块调用;空行派发后只清理当前事件的文本和类型,最后事件 ID 保留。只有 id、没有 data 的事件不会产生普通消息,但会更新恢复标识。id 中的空字符按协议忽略。
字节解码与结束行为
const decoder = new TextDecoder("utf-8", { fatal: true });
// 每次读取 Uint8Array:
parser.push(decoder.decode(bytes, { stream: true }));
// 真正读到 EOF:
parser.push(decoder.decode());
parser.finish();
EOF 不会代替事件结束的空行,未完成的事件丢弃或由外层报告截断,不能当成完整结果执行。fatal: true 是此实现选择的严格错误策略;若要完全沿用浏览器标准的替换字符行为,可以使用默认解码策略。
实际客户端还要限制单行长度、单事件大小和未结束缓冲区大小;处理慢时做背压,避免服务端无限发送或恶意输入撑满内存。
对应教程
- 电商问数第15章:API接口基础与FastAPI入门:回看流式响应与
SSE消息格式。 - 电商问数第17章:前后端联调与日志追踪:回看
progress、result、error的前后端约定。
参考资料
- 国内:阿里云:传统 HTTP 模式与 SSE:采用文本事件包裹业务内容的格式,说明先恢复事件边界再处理
JSON。 - 国内:阿里云百炼:流式输出:采用客户端
ReadableStream与流式解码的处理要求,区分读取块与文本内容。 - 国内:CloudWeGo:Hertz SSE:对照事件
ID/Type/Data及读写职责,确定示例解析器输出的事件结构。 - 国内:阿里云点金:Agent 事件数据模型:借鉴心跳与普通业务事件分开的方式,说明非业务行不能直接追加到结果文本。
- 国外:WHATWG:Server-sent events:逐项核对换行、空行、多行data、空data、id空字符与
EOF丢弃未终止事件的规则。 - 国外:Microsoft:Fetch Event Source:采用
Fetch客户端需要专门事件解析器的设计,限定示例不包含重连、鉴权和调度功能。
