返回知识中心
平台

Compression Streams API:浏览器边界

使用 CompressionStream 和 DecompressionStream 处理 gzip 或 deflate,并明确 backpressure、取消与数据限制。

BotBrowser Team

文档中心

想直接进入 平台 文档吗?

这篇文章属于知识中心的指南库。若你要步骤化配置、参考说明和持续更新,请直接进入对应 docs 分区。

Compression Streams API 可以在不把全部数据放进一个 JavaScript buffer 的情况下压缩或解压 Web stream。当响应体、文本解码器、取消信号和服务器 Content-Encoding 共用一条管线时,边界就变得重要。BotBrowser 支持复现获授权的 fixture,并记录可见的成功、取消和错误状态;它不能认证远程服务器的压缩策略或密码学完整性。需要延迟分析时参考浏览器性能测量,需要跨 runtime 的二进制能力时参考 WebAssembly 可移植性。

TL;DR

  • CompressionStream 和 DecompressionStream 使用 gzip、deflate 或 deflate-raw 转换字节,但不验证应用语义。
  • ReadableStream 只有在消费者施加 backpressure、所有者取消自己创建的分支时才会限制内存。
  • 解压后再解码文本,保持字节所有权,并把无效输入作为明确错误处理。
  • 浏览器支持、服务器 Content-Encoding 和数据完整性是三个不同问题。

目录

字节经过 gzip、backpressure、解压、文本解码,或进入明确的错误状态。

选择格式与所有者

Compression Streams 规范将 CompressionStream 和 DecompressionStream 定义为 TransformStream 接口。构造函数接收格式。gzip 用头部和尾部包装 DEFLATE,deflate 使用 zlib 包装形式,deflate-raw 则是原始 DEFLATE 流。生产者与消费者必须约定同一格式,API 同时存在并不会自动协商。

MDN CompressionStream 和 MDN DecompressionStream 说明了格式和可用性。选择原生路径前检查两个构造函数。缺少构造函数是能力结果,不是数据损坏的证据。Chrome Developers 文章介绍了流式模型,但不保证每个 webview 或服务器适配器都相同。

连接 stream 前先分配所有权。接收 Response.body 的代码拥有 reader、AbortController、解压器和解码器,直到把完整结果交给应用。记录 fixtureId、format、source、consumer 和 outcome,不要记录私人文本或完整 URL。

不要混淆传输压缩和应用压缩。fetch 可能在交给 JavaScript 前自动解码 Content-Encoding: gzip,此时再使用 DecompressionStream('gzip') 就是重复解压。另一个二进制 endpoint 可能返回不透明的 gzip member,此时由应用负责解压。添加转换前先查看响应头和协议。

上传也有同样的边界。浏览器转换不会自动设置 Content-Encoding,而一个头部也不能证明字节是有效 gzip。把请求头、转换格式和服务器响应作为三个字段记录。BotBrowser 支持复现获授权 fixture 并记录可见状态,但不会替远程 origin 选择策略。

构造有界字节管线

使用 pipeThrough() 和 pipeTo(),让 backpressure 与取消保留在 streams 模型中:

const controller = new AbortController();
const input = new Blob([new TextEncoder().encode('owned fixture')]).stream();
const compressed = input.pipeThrough(new CompressionStream('gzip'));
const output = new WritableStream({
  write(chunk) {
    recordChunk(chunk);
  },
  close() {
    record.outcome = 'compressed';
  },
  abort(reason) {
    record.outcome = 'aborted';
    record.reason = String(reason);
  },
});

await compressed.pipeTo(output, { signal: controller.signal });

示例记录 chunk,而不是无界拼接数组。消费者控制需求,所有者可以取消整条管线。chunk 边界可能随浏览器版本改变,也不代表原始网络包。测量总字节和可见完成,不要假设 write() 调用次数。

设置字节预算,例如压缩数据 8 MB,以及应用对解压输出的单独上限。它可以防止过度膨胀,但不是 checksum。记录 bytesIn、bytesOut、limitBytes 和 outcome,不要保存数据。codec 接受字节不等于 JSON、归档或 schema 有效。

tee() 会产生拥有独立需求与取消的分支。不要只为记录原始字节而复制响应。worker 可以拥有 codec,但应发送 compression-complete 这样的短状态,而不是整个响应体。第一个 reader 关闭 body 后,第二个 reader 无法恢复字节。

处理文本、取消与错误

解压输出的是字节。之后再应用 TextDecoder,编码选择要来自数据契约。转换成功不能证明 UTF-8。{ fatal: true } 可以区分 decode-error 与 decompress-error,替换字符则可能掩盖损坏。

const decoded = compressedResponse.body.pipeThrough(new DecompressionStream('gzip'));
const reader = decoded.getReader();
const decoder = new TextDecoder('utf-8', { fatal: true });
let text = '';
try {
  for (;;) {
    const { value, done } = await reader.read();
    if (done) break;
    text += decoder.decode(value, { stream: true });
  }
  text += decoder.decode();
  record.outcome = 'decoded';
} catch (error) {
  record.outcome = error.name === 'AbortError' ? 'cancelled' : 'decode-error';
} finally {
  reader.releaseLock();
}

大响应应增量消费,不要使用 text +=。body 可能为 null、已经消费或被其他 reader 锁定。重试前区分 body-null、body-locked、unsupported-format、decompress-error 和 decode-error。

取消可能发生在收到字节之前、部分写入之后,或导航删除组件之后。恢复动作不同时要区分 cancelled、sink-error 和 owner-gone。中止管线不会删除已经发送的字节,也不会删除其他服务保留的副本。

解压后的数据不是真实性证明。某些格式的 checksum 只能发现 framing 错误,不是应用签名。需要真实性时单独验证签名,并把结果与解压状态并列保存。

使用 fixture 验证边界

使用四行合成 fixture:原生 gzip 压缩、原生 gzip 解压、被取消的 pipe、错误字节。分别在构造函数存在、构造函数缺失和 body 已消费时运行:

情况预期状态调查类别
CompressionStream('gzip') 接收字节compressed 且 bytesOut 有界unsupported-format 或 sink 错误
DecompressionStream('gzip') 接收 fixturedecoded 且 UTF-8 有效decompress-error 或 decode-error
取消信号触发cancelled 且两个所有者关闭reader 泄漏或覆盖部分结果
错误 member 到达解压器decompress-error无限重试或空成功
Response.body 为 null 或已消费body-null 或 body-locked所有权假设错误

记录 browserVersion、implementation、format、bytesIn、bytesOut、limitBytes、outcome 和 fixtureId。值应合成且有界。BotBrowser 可以记录 compressed、decoded、cancelled 和 decompress-error,但不能控制远程 Content-Encoding、代理是否保留每个字节,也不能认证密码学完整性。

通过 service worker 和 window 各测试一次相同资源。如果 worker 解压并返回新的 Response,页面不应再次解压。如果 worker 传递不透明二进制 member,页面拥有转换。上传和下载虽然使用同一 API,但所有者和恢复动作不同,必须分开记录。

诚实的回退可以使用经过审核的库、服务器 endpoint 或无压缩路径。在 implementation 中标记 native、fallback-unavailable 或 server-decoded,不要承诺相同的 chunking 或 timing。浏览器、服务器、parser 或 service worker 改动后,重复相同行并比较第一个变化字段。

Public sources

实践结论

声明格式,为每个 stream 分配一个所有者,并分别记录解压、解码、取消和完整性。通过消费者施加 backpressure,限制字节预算,reader 出错后直接关闭而不是重试确定性损坏。浏览器、服务器或 service worker 变化后重放有界 fixture,并依据第一个变化状态作出决定。

#Compression Streams API#CompressionStream#DecompressionStream#Web Streams

本文属于指南: Web 平台能力面:一致性复核地图

让 BotBrowser 从研究走向生产

先用这些指南理解模型,再进入跨平台验证、隔离上下文和面向规模化的浏览器部署。