SSE 流中收到不完整行时应如何解析和缓存?

考查重点:残行保留与完整事件边界。把未到换行的残片留缓冲,下批拼接后逐行解析,空行才完成事件;多data行按协议合并,结束或异常时检查未完整尾段

本题目录

参考答案

核心回答

保留两个层次的缓冲:一层保存尚未遇到行结束符的残行;另一层收集当前事件的各个 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 是此实现选择的严格错误策略;若要完全沿用浏览器标准的替换字符行为,可以使用默认解码策略。

实际客户端还要限制单行长度、单事件大小和未结束缓冲区大小;处理慢时做背压,避免服务端无限发送或恶意输入撑满内存。

对应教程

参考资料

相关面经

2 篇

百度

后端开发工程师
一面
查看原面经 →
假如SSE流式传输只返回了一半的json,要不要执行这个工具?或者说你会采取什么策略来应对这种未知情况?

快手

前端开发工程师
一面
查看原面经 →
sse不完整行这么处理

相关题目

4 题