Planteamiento y contexto
Un navegador debe comprimir con gzip subidas grandes y descomprimir descargas con bajo uso de memoria, cancelación y protección frente a entradas hostiles. Diseña la canalización de streams y explica formatos, contrapresión, errores y límites de seguridad.
La Compression Streams API proporciona CompressionStream y DecompressionStream para fragmentos binarios en una canalización de Web Streams. El estándar define brotli, deflate, deflate-raw y gzip; suministra transformaciones, no límites de tamaño, autenticación ni verificaciones de integridad de negocio.
Qué evalúa el entrevistador
Cubre la composición de Readable/Writable/TransformStream, contrapresión y colas, vaciado (flush), límites de formato, errores de descompresión, propagación de cancelación, presupuestos de memoria, bombas de descompresión y canales laterales basados en la longitud, mejora progresiva y negociación con el servidor.
Marco de respuesta de 30 segundos
“Conectaría un stream de Blob o el cuerpo de una respuesta a CompressionStream mediante pipeThrough para preservar la contrapresión, y propagaría la cancelación con un AbortSignal. En la descarga, limitaría el tamaño comprimido, los bytes descomprimidos y el tiempo de procesamiento antes de alimentar un parser con DecompressionStream; abortando ante errores de formato o integridad. Si el navegador carece de la capacidad, se recurre a la compresión en el servidor. La API no es un escáner de seguridad ni un verificador de integridad.”
Análisis detallado paso a paso
Paso 1: Elegir los streams de entrada y salida
Las subidas pueden partir de Blob.stream() y las descargas de Response.body. Un CompressionStream produce un ReadableStream para el cuerpo de un fetch, un sumidero de archivos o un parser; no cargues primero todo el archivo en un ArrayBuffer.
Paso 2: Conectar una transformación
Una canalización de subida mínima es:
async function uploadGzip(blob, signal) {
const compressed = blob.stream().pipeThrough(
new CompressionStream("gzip"),
{ signal },
);
return fetch("/upload", {
method: "POST",
body: compressed,
signal,
headers: { "Content-Encoding": "gzip" },
});
}El protocolo real debe definir la longitud de la solicitud, la semántica de reintentos y si el servidor acepta cuerpos de solicitud en streaming.
Paso 3: Comprender la contrapresión y el vaciado (flush)
La Streams API ajusta las lecturas ascendentes a partir de la cola descendente y la velocidad de escritura. No agregues cada fragmento a un arreglo sin límites; un sumidero lento debe pausar la canalización de forma natural. Cerrar el lado de escritura debe vaciar (flush) el bloque comprimido final y la suma de verificación.
Paso 4: Negociar formatos
El constructor acepta una cadena de formato compatible y lanza un error si no es compatible. El cliente y el servidor deben acordar Content-Encoding o un campo de negocio. deflate, deflate-raw y gzip tienen envoltorios diferentes; no envíes Brotli sin negociación previa.
Paso 5: Acotar la descompresión
La entrada comprimida puede expandirse a una salida descomunal. Cuenta los bytes de salida, las entradas de archivos, el tiempo de procesamiento y las tareas concurrentes; cancela o aborta una vez superado el presupuesto. El contenido descomprimido aún necesita comprobaciones de MIME, rutas y seguridad de contenido. Una transformación exitosa no equivale a confianza.
Paso 6: Propagar errores y cancelación
Los fallos de formato o de suma de verificación colocan a la transformación en un estado de error. Captura los errores de pipeTo, fetch y lectores, y propaga la cancelación del usuario a la solicitud, lectores, escritores y a la transformación. Libera Blobs temporales, bloqueos y el estado de progreso en la UI para que ninguna lectura quede pendiente.
Paso 7: Proteger la privacidad y la integridad
La longitud comprimida puede revelar relaciones entre secretos y texto controlado por un atacante. No coloques ambos en el mismo contexto de compresión. Utiliza firmas o hashes independientes para archivos importantes; la compresión codifica bytes, pero no autentica el origen ni previene manipulaciones.
Paso 8: Proveer compatibilidad y alternativas (fallback)
Verifica los constructores y formatos al inicio, y ejecuta tareas pesadas en un Worker para proteger el hilo principal. Si no es compatible, delega la compresión al servidor o sube el archivo sin comprimir con la misma semántica de cancelación, tamaño y errores; no valides únicamente en navegadores modernos.
Compensaciones y límites de diseño
CPU del cliente frente a ahorro de red
La compresión ahorra bytes pero consume CPU, batería y tiempo. En dispositivos móviles, decide según el tipo de archivo, la calidad de la red y la batería; por lo general, los formatos ya comprimidos no deben recomprimirse.
Streaming frente a simplicidad de reintentos
El streaming mantiene un uso de memoria bajo pero requiere un servidor que entienda fragmentos y reintentos idempotentes. Para subidas reanudables, asocia los fragmentos comprimidos al protocolo multipart; no reintentes un ReadableStream ya consumido como si fuera reproducible.
Descompresión en el navegador frente al servidor
La descompresión en el navegador ahorra CPU en el servidor, pero traslada los presupuestos de recursos y seguridad al dispositivo. Para datos sensibles o de altísima expansión, descomprime en el servidor y devuelve un resultado acotado mientras el cliente consume el estado y los fragmentos.
Simulacros de fallos y plan de evolución
El sumidero se vuelve lento
Limita la velocidad de subida e inspecciona las curvas de colas y memoria. Confirma que no se retengan todos los fragmentos; si las colas siguen creciendo, reduce la concurrencia y permite que la canalización se pause.
Un stream gzip se trunca
Trunca la entrada comprimida y verifica que falle durante el vaciado o la validación de la suma de verificación, que la UI pase a un estado reintentable y que se liberen lectores, solicitudes y objetos temporales.
La salida supera su presupuesto
Usa una muestra de alta expansión y verifica que alcanzar el límite de bytes aborte la operación de inmediato en lugar de materializar o persistir la salida completa.
Errores comunes y preguntas de seguimiento
Error 1: Asumir que la API previene bombas de descompresión
Pregunta de seguimiento: ¿Qué falta? Presupuestos a nivel de aplicación para bytes, tiempo, entradas y concurrencia; la API solo realiza conversión de formato.
Error 2: Tratar deflate como gzip
Pregunta de seguimiento: ¿Por qué no? Los envoltorios difieren, por lo que se requiere negociación de protocolo y formatos coincidentes.
Error 3: Reintentar un stream consumido
Pregunta de seguimiento: ¿Qué es lo correcto? Reconstruir la canalización a partir de una fuente reproducible de fragmentos o un Blob y coordinar un identificador de subida idempotente con el servidor.
Preguntas de seguimiento extendidas y respuestas modelo
¿Por qué es importante el vaciado (flush)?
La transformación debe emitir los datos de cierre (trailer) de compresión y las sumas de verificación cuando termina la entrada. Sin cerrar el lado de escritura, el consumidor puede recibir un stream incompleto.
¿Cómo reducir el riesgo de canal lateral por longitud?
Separa los secretos del texto controlado por atacantes, evita un contexto de compresión compartido y utiliza relleno (padding) de protocolo o un diseño que no exponga la relación.
¿Cuándo debe mantenerse la compresión en el servidor?
Usa procesamiento del lado del servidor para navegadores antiguos, dispositivos con poca batería, archivos gigantescos o datos confidenciales. Aun así, limita la salida descomprimida y expón un progreso observable al cliente.