Compression Streams API: границы браузера
Используйте CompressionStream и DecompressionStream с gzip или deflate, явно задавая backpressure, отмену и лимиты данных.
BotBrowser Team
Нужна структурированная документация по теме Платформа?
Эта статья относится к редакционной библиотеке. Для пошаговой настройки, справки и постоянных обновлений переходите сразу в соответствующий раздел docs.
Compression Streams API сжимает и распаковывает web-поток без загрузки всех данных в один буфер JavaScript. Проблема начинается, когда тело ответа, декодер текста, сигнал отмены и политика Content-Encoding сервера используют одну цепочку. BotBrowser поддерживает воспроизведение разрешённого fixture и запись видимых состояний успеха, отмены и ошибки; он не сертифицирует политику удалённого сервера или криптографическую целостность. Для соседних границ см. измерение производительности и переносимость WebAssembly.
TL;DR
CompressionStreamиDecompressionStreamпреобразуют байты в форматахgzip,deflateилиdeflate-raw, но не проверяют смысл приложения.ReadableStreamограничивает память только при наличии backpressure у потребителя и отмене всех созданных владельцем ветвей.- Декодируйте текст после распаковки, сохраняйте владельца байтов и превращайте неправильный ввод в явную ошибку.
- Поддержка браузера,
Content-Encodingсервера и целостность данных являются разными вопросами.
Содержание
- Выбрать формат и владельца
- Создать ограниченный конвейер байтов
- Обработать текст, отмену и ошибки
- Проверить границы fixture
- Заключение
Выбрать формат и владельца
Спецификация Compression Streams определяет CompressionStream и DecompressionStream как интерфейсы TransformStream. Конструктор получает формат. gzip оборачивает DEFLATE заголовком и трейлером, deflate использует форму zlib, а deflate-raw содержит необёрнутый поток DEFLATE. Производитель и потребитель должны согласовать формат, поскольку наличие двух API не создаёт автоматического согласования.
Справочники MDN для CompressionStream и DecompressionStream описывают форматы и доступность. Проверяйте оба конструктора до выбора нативного пути. Отсутствующий конструктор означает ограничение возможности, а не повреждение данных. Статья Chrome Developers описывает потоковую модель, но не обещает одинаковое поведение каждого webview или серверного адаптера.
Назначьте владельца до соединения потоков. Код, получивший Response.body, владеет reader, AbortController, распаковщиком и декодером до передачи полного результата. Записывайте fixtureId, format, source, consumer и outcome, но не приватный текст и полный URL.
Не смешивайте транспортное и прикладное сжатие. fetch может прозрачно декодировать Content-Encoding: gzip; затем DecompressionStream('gzip') станет двойной распаковкой. Бинарный endpoint может вернуть непрозрачный gzip-member, и тогда распаковка принадлежит приложению. Перед преобразованием проверяйте заголовки и контракт.
Та же граница действует для загрузки. Браузерное преобразование само не задаёт Content-Encoding, а заголовок не доказывает корректность gzip-байтов. Храните заголовок, формат преобразования и ответ сервера отдельно. BotBrowser поддерживает воспроизведение разрешённого fixture и запись видимого состояния, но не может управлять политикой удалённого origin.
Создать ограниченный конвейер байтов
Используйте pipeThrough() и pipeTo(), чтобы backpressure и отмена оставались частью модели потоков:
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 });
Пример записывает chunks, а не собирает бесконечный массив. Потребитель управляет спросом, а владелец может отменить весь pipe. Границы chunks меняются между версиями и не описывают сетевые пакеты. Измеряйте итоговые байты и видимое завершение, а не предполагаемое число вызовов write().
Установите бюджет, например 8 MB сжатых данных и отдельный максимум для распакованного вывода. Лимит защищает от чрезмерного расширения, но не является checksum. Сохраняйте bytesIn, bytesOut, limitBytes и outcome, не сохраняя данные. Успешный codec не подтверждает корректность JSON, архива или схемы.
tee() создаёт ветви с отдельными спросом и отменой. Не дублируйте response только для журналирования сырых байтов. Worker может владеть codec, но должен отправлять странице короткое состояние compression-complete, а не всё тело. После закрытия первого владельца второй 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, если восстановление различается. Abort pipe не удаляет уже отправленные байты и копии другого сервиса.
Распакованные данные не доказывают подлинность. Checksums некоторых форматов обнаруживают ошибки framing, но не являются подписью приложения. Проверяйте отдельную подпись и храните результат рядом с состоянием распаковки.
Проверить границы fixture
Используйте четыре синтетические строки: native gzip compression, native gzip decompression, отменённый pipe и malformed bytes. Повторите их при доступных конструкторах, без конструкторов и с уже потреблённым body:
| Случай | Ожидаемое состояние | Класс для проверки |
|---|---|---|
CompressionStream('gzip') получает байты | compressed с ограниченным bytesOut | unsupported-format или ошибка sink |
DecompressionStream('gzip') получает fixture | decoded и корректный UTF-8 | decompress-error или decode-error |
| Срабатывает сигнал отмены | cancelled, оба владельца закрыты | утёкший reader или частичный результат |
| К распаковщику приходит malformed member | decompress-error | бесконечный retry или пустой успех |
Response.body null или consumed | body-null или body-locked | неправильная гипотеза владельца |
Записывайте browserVersion, implementation, format, bytesIn, bytesOut, limitBytes, outcome и fixtureId. Значения должны быть синтетическими и ограниченными. BotBrowser может записать compressed, decoded, cancelled и decompress-error, но не управляет удалённым Content-Encoding, сохранением байтов proxy или криптографической целостностью.
Проверьте тот же ресурс через сервисный обработчик и через window. Если worker распаковывает и возвращает новый Response, страница не должна распаковывать повторно. Если worker передаёт непрозрачный binary member, преобразование принадлежит странице. Загрузка и скачивание требуют отдельных строк, поскольку владельцы и восстановление различаются.
Честный запасной путь может использовать проверенную библиотеку, server endpoint или путь без compression. В implementation сохраняйте native, fallback-unavailable или server-decoded; не обещайте одинаковые chunks и timing. После изменения браузера, сервера, parser или сервисного обработчика повторите строки и сравните первое изменившееся поле.
Public sources
- Compression Streams specification
- MDN: CompressionStream
- MDN: DecompressionStream
- Chrome Developers: Compression Streams API
- BotBrowser: Automation consistency
Заключение
Объявите формат, назначьте одного владельца каждому потоку и разделите распаковку, декодирование, отмену и целостность. Применяйте backpressure, ограничивайте байты и закрывайте ошибочный reader вместо повтора детерминированной порчи. Повторяйте fixture после изменений браузера, сервера или сервисного обработчика и принимайте решение по первому изменившемуся состоянию.
Эта статья входит в руководство: Поверхности веб-платформы: карта проверки
Похожие статьи
WebTransport: границы источника, потоков и приватности
ПлатформаОбнаружение возможностей WebAssembly и переносимость модулей
ПлатформаWeb Share API: границы разрешений и безопасные запасные варианты
ПлатформаЦелостность API браузера между realm
ПлатформаПоведение WebAssembly на разных браузерных платформах
ПлатформаПриоритеты scheduler.postTask для веб-задач
Переведите BotBrowser из исследований в продакшн
Используйте эти руководства, чтобы понять модель, а затем перейти к кроссплатформенной валидации, изолированным контекстам и масштабируемому браузерному развертыванию.