Retour au Centre de connaissances
Plateforme

Compression Streams API : limites du navigateur

Utilisez CompressionStream et DecompressionStream avec gzip ou deflate en gardant le backpressure, l’annulation et les limites de données explicites.

BotBrowser Team

Documentation

Vous voulez la documentation structurée pour Plateforme ?

Cet article fait partie de la bibliothèque éditoriale. Pour les étapes de configuration, la référence et les mises à jour continues, passez directement à la section docs.

La Compression Streams API compresse ou décompresse un flux web sans charger toutes les données dans un seul buffer JavaScript. La frontière devient importante lorsque le corps d’une réponse, le décodage de texte, un signal d’annulation et la politique Content-Encoding du serveur partagent le même pipeline. BotBrowser permet de rejouer un fixture autorisé et d’enregistrer les états visibles de réussite, d’annulation et d’erreur ; il ne certifie pas la politique de compression d’un serveur distant ni son intégrité cryptographique. Consultez aussi la mesure des performances et la portabilité WebAssembly pour les autres limites de runtime.

TL;DR

  • CompressionStream et DecompressionStream transforment des octets avec gzip, deflate ou deflate-raw; ils ne valident pas le sens applicatif.
  • Un pipeline ReadableStream ne borne la mémoire que si le consommateur applique le backpressure et si le propriétaire annule toutes ses branches.
  • Décodez le texte après la décompression, gardez la propriété des octets et traitez l’entrée invalide comme une erreur explicite.
  • Support du navigateur, Content-Encoding du serveur et intégrité des données sont des questions distinctes.

Contents

Des octets passent par gzip, le backpressure, la décompression, le décodage de texte ou un état d’erreur explicite.

Choisir le format et le propriétaire

La spécification Compression Streams définit CompressionStream et DecompressionStream comme des interfaces TransformStream. Le constructeur reçoit un format. gzip enveloppe DEFLATE avec un en-tête et un trailer, deflate utilise la forme zlib et deflate-raw contient le flux DEFLATE brut. Le producteur et le consommateur doivent donc partager le même contrat.

Les références MDN de CompressionStream et DecompressionStream décrivent les formats et la disponibilité. Détectez les deux constructeurs avant d’emprunter le chemin natif. Un constructeur absent est un résultat de capacité, pas une preuve de corruption. L’article de Chrome Developers présente un modèle de streaming, pas une promesse pour chaque webview ou adaptateur serveur.

Attribuez la propriété avant de connecter les flux. Le code qui reçoit Response.body possède le reader, l’AbortController, le décompresseur et le décodeur jusqu’à la remise d’un résultat complet. Conservez fixtureId, format, source, consumer et outcome, sans enregistrer le texte privé ni l’URL complète.

Ne confondez pas compression de transport et compression applicative. fetch peut décoder automatiquement une réponse Content-Encoding: gzip; appliquer ensuite DecompressionStream('gzip') serait une double décompression. Un endpoint peut aussi remettre un membre gzip binaire opaque, auquel cas l’application possède la décompression. Examinez les en-têtes et le contrat avant d’ajouter une transformation.

La même limite existe pour les uploads. Une transformation navigateur ne définit pas automatiquement Content-Encoding, et un en-tête ne prouve pas que les octets sont un gzip valide. Gardez enregistrements séparés pour l’en-tête, le format et la réponse. BotBrowser peut rejouer le fixture autorisé et enregistrer son état visible, mais ne choisit pas la politique de l’origine distante.

Construire un pipeline d’octets borné

Utilisez pipeThrough() et pipeTo() afin que le backpressure et l’annulation restent dans le modèle des flux:

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 });

L’exemple enregistre les chunks au lieu de les concaténer sans limite. Le consommateur contrôle la demande et le propriétaire peut annuler tout le pipeline. Les limites de chunk peuvent changer entre versions et ne décrivent pas les paquets réseau. Mesurez les octets totaux et la fin visible, pas un nombre supposé de write().

Ajoutez un budget, par exemple 8 MB compressés et une limite applicative sur la sortie décompressée. Ce garde-fou protège contre une expansion excessive, mais n’est pas un checksum. Enregistrez bytesIn, bytesOut, limitBytes et outcome sans conserver les données. Un codec qui accepte les octets ne valide pas pour autant JSON, archive ou schéma.

Avec plusieurs consommateurs, tee() crée des branches dont la demande et l’annulation sont séparées. Ne dupliquez pas une réponse uniquement pour journaliser ses octets. Un worker peut posséder le codec, mais il doit envoyer un état court comme compression-complete, pas tout le corps. Un second reader ne peut pas récupérer un body déjà consommé.

Gérer texte, annulation et erreurs

La décompression produit des octets. Appliquez TextDecoder ensuite et choisissez l’encodage selon le contrat. UTF-8 ne découle pas d’une transformation réussie. { fatal: true } distingue decode-error de decompress-error, tandis que les caractères de remplacement peuvent masquer une corruption.

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();
}

Pour une grande réponse, consommez par fragments plutôt que d’utiliser text +=. Un body peut être nul, déjà consommé ou verrouillé par un autre reader. Distinguez body-null, body-locked, unsupported-format, decompress-error et decode-error avant de recommencer.

L’annulation peut arriver avant les octets, après une écriture partielle ou parce qu’une navigation a supprimé le composant. Gardez cancelled, sink-error et owner-gone séparés lorsque l’action de reprise diffère. Abandonner le pipeline n’efface pas les octets déjà envoyés ni une copie conservée par un autre service.

Des données décompressées ne prouvent pas leur authenticité. Les checksums de certains formats détectent des erreurs de framing, mais ne sont pas une signature applicative. Vérifiez une signature séparée et gardez son résultat à côté de l’état de décompression.

Vérifier les limites avec un fixture

Utilisez quatre lignes synthétiques: compression gzip native, décompression gzip native, pipeline annulé et octets malformés. Répétez avec les constructeurs disponibles, absents, puis avec un body déjà consommé:

CasÉtat attenduClasse à inspecter
CompressionStream('gzip') reçoit des octetscompressed avec bytesOut bornéunsupported-format ou erreur du sink
DecompressionStream('gzip') reçoit le fixturedecoded et UTF-8 validedecompress-error ou decode-error
Le signal d’annulation se déclenchecancelled et fermeture des deux propriétairesreader fui ou résultat partiel
Un membre malformé arrive au décompresseurdecompress-errorboucle de retry ou succès vide
Response.body est nul ou consommébody-null ou body-lockedhypothèse de propriété erronée

Capturez browserVersion, implementation, format, bytesIn, bytesOut, limitBytes, outcome et fixtureId. Gardez les valeurs synthétiques et bornées. BotBrowser peut rejouer le fixture et enregistrer compressed, decoded, cancelled et decompress-error; il ne contrôle pas Content-Encoding distant, la conservation par un proxy ni l’intégrité cryptographique.

Testez aussi la même ressource via un service worker et une window. Si le worker décompresse et renvoie une nouvelle Response, la page ne doit pas décompresser une seconde fois. Si le worker transmet un membre binaire opaque, la page possède la transformation. L’envoi et le téléchargement ont des propriétaires et récupérations différents, même s’ils utilisent la même API.

Un repli honnête peut employer une bibliothèque contrôlée, un endpoint serveur ou une voie non compressée. Étiquetez native, fallback-unavailable ou server-decoded dans implementation; ne promettez pas un chunking ou un timing identique. Après un changement de navigateur, de serveur, de parser ou de service worker, rejouez les mêmes lignes et comparez le premier champ différent. BotBrowser prend en charge la relecture du fixture autorisé, mais ne peut pas contrôler la politique de compression de l’origine distante; il enregistre seulement les états visibles.

Public sources

Conclusion pratique

Déclarez le format, attribuez un seul propriétaire à chaque flux et séparez décompression, décodage, annulation et intégrité. Appliquez le backpressure, imposez un budget d’octets et abandonnez un reader en erreur au lieu de relancer une corruption déterministe. Rejouez le fixture après chaque changement de navigateur, serveur ou service worker et décidez à partir du premier état différent.

#Compression Streams API#CompressionStream#DecompressionStream#Flux Web

Cet article fait partie du guide : Surfaces de la plateforme web : carte de revue

Faites passer BotBrowser de la recherche à la production

Utilisez ces guides pour comprendre le modèle, puis passez à la validation multi-plateforme, aux contextes isolés et au déploiement navigateur prêt pour l'échelle.