网关层流式解析html必须跳过dom构建,否则50mb文件将触发v8 oom;应使用htmlparser2仅监听opentag/closetag事件、禁用xmlmode、避免ontext拼接字符串,并注入chunk-meta注释保留上下文,同时合理设置writablestream的highwatermark防内存堆积。

网关层流式解析 HTML 文档结构,必须跳过 DOM 构建,否则 50MB 文件直接触发 V8 OOM;死锁不是代码写错了,而是 chunk 边界切在半截标签里、或 text 回调里拼字符串导致内存卡死。
htmlparser2 的 opentag/closetag 事件怎么用才不爆栈
遇到 RangeError: Maximum call stack size exceeded,基本是用了递归下降解析器(比如 jsdom 或 DOMParser)去处理千级嵌套 <div>。换成 <code>htmlparser2.Parser + 仅监听 opentag 和 closetag,就能把内存压到 10MB 以内。
- 禁用
xmlMode: true—— HTML 文档必须设xmlMode: false,否则<img>这类自闭合标签会解析失败 - 不要在
ontext回调里做buffer += text—— 改用Uint8Array流式暂存,按需截断 - 若需识别语义块(如
<h2></h2>下所有段落),在opentag中记录栈深度,在closetag时弹出,避免深递归
Transfer-Encoding: chunked 响应为啥浏览器不流式渲染
光有 Transfer-Encoding: chunked 不够,浏览器是否真能边收边画,取决于首 chunk 是否包含合法、完整、可提前渲染的 HTML 结构。
- 第一个 chunk 必须含
开头,否则解析器延迟启动 - 不能出现半截标签,比如只发了
<div cl>,解析器会卡在“等待闭合”状态<li>chunk 之间不能插空行、BOM、调试日志——这些字节会污染 HTML 字符流</li> <li> <code>Content-Length和Transfer-Encoding: chunked不能共存,违反 HTTP/1.1 规范,部分客户端直接降级为全量接收 - 必须在每个分块前注入注释型上下文锚点,例如
<!-- chunk-start:section-17 id="api-auth" depth=3 --> - 禁止用
innerHTML拼接再 parse,那会重建 DOM 树并丢失原始位置信息 - 下游服务收到分块后,要靠注释里的
id和depth重建局部语义树,而非直接插入document.body - 对 10MB/s 吞吐、下游处理耗时约 50ms 的典型网关链路,建议
highWaterMark: 65536(64KB) - 若下游是低带宽设备或高延迟服务,需动态调低,配合
stream.on('drain', () => {...})控制写入节奏 - 绝对不要设
highWaterMark: 0—— 这会让流立刻暂停,等 drain 才恢复,极易造成上游积压
分块后怎么保留锚点和上下文不丢
网关把 30MB HTML 切成 200 个 <section></section>,如果只转发纯片段,下游服务根本还原不了 #api-auth 这种锚点跳转——因为 ID 被切没了,父容器层级也断了。
WritableStream 的 highWaterMark 设多少才不内存堆积
流式解析不是“开了就完事”,WritableStream 的 highWaterMark 配得太小,频繁触发 drain 降低吞吐;配太大,又容易在慢下游场景下内存堆积到 OOM。
真正难的不是切块,而是让每一块都带着“我在原文哪一层、紧挨着哪个 ID、属于哪个语义节”的元信息;丢掉这个,下游拿到的只是碎片,不是结构。











