HTTP/2 hará que nuestras aplicaciones sean más veloces, más simples y sólidas (una combinación infrecuente), ya que nos permitirá deshacer muchas de las soluciones alternativas de HTTP/1.1 que se aplicaban anteriormente en nuestras aplicaciones y abordar estas inquietudes dentro de la capa de transporte. Mejor aún, también abre una serie de oportunidades completamente nuevas para optimizar nuestras aplicaciones y mejorar el rendimiento.
Los objetivos principales de HTTP/2 son reducir la latencia mediante la habilitación completa de la multiplexación de solicitudes y respuestas, minimizar la sobrecarga del protocolo a través de una compresión eficiente de los campos del encabezado HTTP y agregar compatibilidad con la priorización de solicitudes y el envío del servidor. Para implementar estos requisitos, existe una gran variedad de otras mejoras del protocolo, como nuevos mecanismos de control de flujo, manejo de errores y actualización, pero estas son las características más importantes que todo desarrollador web debe comprender y aprovechar en sus aplicaciones.
HTTP/2 no modifica la semántica de aplicación de HTTP de ninguna manera. Se mantienen todos los conceptos principales, como los métodos HTTP, los códigos de estado, los URI y los campos de encabezado. En cambio, HTTP/2 modifica la manera en que se formatean (enmarcan) y transportan los datos entre el cliente y el servidor, que administran todo el proceso, y oculta toda la complejidad de nuestras aplicaciones dentro de la nueva capa de enmarcado. Como resultado, todas las aplicaciones existentes se pueden entregar sin modificaciones.
¿Por qué no es HTTP/1.2?
Para lograr los objetivos de rendimiento establecidos por el Grupo de trabajo de HTTP, HTTP/2 introduce una nueva capa de entramado binario que no es retrocompatible con servidores y clientes HTTP/1.x anteriores; por eso, el incremento de la versión principal del protocolo a HTTP/2.
Dicho esto, a menos que implementes un servidor web (o un cliente personalizado) mediante sockets TCP sin procesar, no verás ninguna diferencia: el cliente y el servidor realizan todos los nuevos enmarcados de bajo nivel en tu nombre. Las únicas diferencias observables serán un rendimiento mejorado y la disponibilidad de capacidades nuevas, como la priorización de solicitudes, el control de flujo y el envío de servidores.
Breve historia de SPDY y HTTP/2
SPDY fue un protocolo experimental, desarrollado en Google y anunciado a mediados de 2009, cuyo objetivo principal era intentar reducir la latencia de carga de las páginas web abordando algunas de las limitaciones de rendimiento conocidas de HTTP/1.1. Específicamente, los objetivos descritos del proyecto se establecieron de la siguiente manera:
- Apuntar a una reducción del 50% en el tiempo de carga de la página (PLT).
- Evita la necesidad de que los autores del sitio web realicen cambios en el contenido.
- Minimizar la complejidad de implementación y evitar cambios en la infraestructura de red.
- Desarrolla este protocolo nuevo en asociación con la comunidad de código abierto.
- Reunir datos de rendimiento reales para (in)validar el protocolo experimental.
Poco después del anuncio inicial, Mike Belshe y Roberto Peon, ingenieros de software de Google, compartieron sus primeros resultados, la documentación y el código fuente para la implementación experimental del nuevo protocolo SPDY:
Hasta ahora, solo hemos probado SPDY en laboratorios. Los resultados iniciales son muy alentadores: cuando descargamos los 25 sitios web principales mediante conexiones de red doméstica simuladas, observamos una mejora significativa en el rendimiento (las páginas se cargan hasta un 55% más rápido). (Blog de Chromium)
En 2012, el nuevo protocolo experimental era compatible con Chrome, Firefox y Opera. Además, una cantidad cada vez mayor de sitios, tanto grandes (por ejemplo, Google, Twitter o Facebook) como pequeños, implementaban SPDY en su infraestructura. De hecho, SPDY estaba encaminado para convertirse en un estándar de facto mediante la creciente adopción por parte de la industria.
Al observar esta tendencia, el Grupo de trabajo de HTTP (HTTP-WG) inició un nuevo esfuerzo para tomar las lecciones aprendidas de SPDY, desarrollarlas y mejorarlas, y entregar un estándar "HTTP/2" oficial. Se redactó un nuevo estatuto, se realizó una convocatoria abierta para propuestas de HTTP/2 y, después de un gran debate en el grupo de trabajo, se adoptó la especificación SPDY como punto de partida para el nuevo protocolo HTTP/2.
En los años siguientes SPDY y HTTP/2 continuaron evolucionando en paralelo, con SPDY actuando como una rama experimental que se usaba para probar funciones y propuestas nuevas para el estándar HTTP/2. Lo que se ve bien en la teoría puede que no funcione en la práctica, y viceversa, y SPDY ofreció una ruta para probar y evaluar cada propuesta antes de su inclusión en el estándar HTTP/2. Al final, este proceso duró tres años y dio como resultado más de una docena de borradores intermedios:
- Marzo de 2012: Convocatoria de propuestas para HTTP/2
- Noviembre de 2012: Primer borrador de HTTP/2 (basado en SPDY)
- Agosto de 2014: se publicaron el borrador 17 de HTTP/2 y el borrador 12 de HPACK
- Agosto de 2014: Última llamada del Grupo de trabajo para HTTP/2
- Febrero de 2015: IESG aprobó los borradores de HTTP/2 y HPACK
- Mayo de 2015: Se publicaron RFC 7540 (HTTP/2) y RFC 7541 (HPACK)
A principios de 2015, IESG revisó y aprobó el nuevo estándar HTTP/2 para su publicación. Poco después, el equipo de Google Chrome anunció su programa de dar de baja SPDY y la extensión NPN para TLS:
Los principales cambios de HTTP/2 en comparación con HTTP/1.1 se enfocan en un mejor rendimiento. Algunas características clave, como multiplexación, compresión de encabezado, priorización y negociación de protocolo, evolucionaron del trabajo realizado en un protocolo abierto anterior, pero no estándar llamado SPDY. Chrome es compatible con SPDY desde Chrome 6, pero como la mayoría de los beneficios están presentes en HTTP/2, es hora de despedirse. Planeamos quitar la compatibilidad con SPDY a principios de 2016 y también quitar la compatibilidad con la extensión TLS llamada NPN a favor de ALPN en Chrome al mismo tiempo. Se recomienda a los desarrolladores de servidores que migren a HTTP/2 y ALPN.
Nos complace haber contribuido en el proceso de estándares abiertos que condujo a HTTP/2 y esperamos ver una amplia adopción dada la amplia participación del sector en la estandarización y la implementación. (Blog de Chromium)
La evolución conjunta de SPDY y HTTP/2 permitió que los desarrolladores de servidores, navegadores y sitios obtuvieran experiencia en el mundo real con el protocolo nuevo a medida que se desarrollaba. Como resultado, el estándar HTTP/2 es uno de los mejores estándares y de los más probados desde el principio. En el momento en que IESG aprobó HTTP/2, había decenas de implementaciones de cliente y servidor listas para la producción y probadas de forma exhaustiva. De hecho, unas semanas después de que se aprobara el protocolo final, muchos usuarios ya disfrutaban de los beneficios, dado que varios navegadores populares (y muchos sitios) implementaron la compatibilidad total con HTTP/2.
Objetivos técnicos y de diseño
Las versiones anteriores del protocolo HTTP se diseñaron intencionalmente para simplificar la implementación: HTTP/0.9 era un protocolo de una línea para iniciar la World Wide Web; HTTP/1.0 documentaba las extensiones populares de HTTP/0.9 en un estándar informativo; HTTP/1.1 introdujo un estándar IETF oficial. Consulta Breve historia de HTTP. Como tal, HTTP/0.9-1.x ofreció exactamente lo que se propuso: HTTP es uno de los protocolos de aplicaciones más adoptados en Internet.
Lamentablemente, la simplicidad de implementación también tuvo un costo en el rendimiento de la aplicación: los clientes HTTP/1.x necesitan usar varias conexiones para lograr la simultaneidad y reducir la latencia; HTTP/1.x no comprime los encabezados de solicitud y respuesta, lo que causa un tráfico de red innecesario; HTTP/1.x no permite una priorización eficaz de recursos, lo que genera un uso deficiente de la conexión de TCP subyacente; y así sucesivamente.
Estas limitaciones no eran fatales, pero a medida que las aplicaciones web continuaban creciendo en alcance, complejidad e importancia en nuestras vidas cotidianas, imponían una carga cada vez mayor para los desarrolladores y los usuarios de la Web, que es la brecha exacta para la que HTTP/2 se diseñó para abordar:
HTTP/2 permite un uso más eficiente de los recursos de red y una menor percepción de latencia, ya que introduce la compresión de campos de encabezado y permite varios intercambios simultáneos en la misma conexión. Específicamente, permite la intercalación de mensajes de solicitud y respuesta en la misma conexión y utiliza una programación eficiente para los campos de encabezado HTTP. También permite priorizar las solicitudes, lo que permite que las solicitudes más importantes se completen con mayor rapidez y mejora aún más el rendimiento.
El protocolo resultante es más fácil de usar para la red, ya que se pueden usar menos conexiones TCP en comparación con HTTP/1.x. Esto significa menos competencia con otros flujos y conexiones de larga duración, lo que, a su vez, genera un mejor uso de la capacidad de red disponible. Por último, HTTP/2 también permite un procesamiento más eficiente de los mensajes mediante el enmarcado de mensajes binario. (Protocolo de transferencia de hipertexto versión 2, borrador 17)
Es importante tener en cuenta que HTTP/2 extiende y no reemplaza los estándares HTTP anteriores. La semántica de aplicación de HTTP es la misma y no se realizaron cambios en la funcionalidad ofrecida ni en los conceptos básicos, como los métodos HTTP, los códigos de estado, los URIs y los campos de encabezado. Estos cambios estaban explícitamente fuera del alcance del esfuerzo HTTP/2. Dicho esto, si bien la API de alto nivel sigue siendo la misma, es importante comprender cómo los cambios de bajo nivel abordan las limitaciones de rendimiento de los protocolos anteriores. Hagamos un breve recorrido por la capa de enmarcado binario y sus funciones.
Capa de entramado binario
En el centro de todas las mejoras de rendimiento de HTTP/2, se encuentra la nueva capa de enmarcado binario, que determina cómo los mensajes HTTP se encapsulan y se transfieren entre el cliente y el servidor.
La "capa" se refiere a una opción de diseño para introducir un mecanismo de codificación optimizado entre la interfaz del socket y la API de HTTP superior expuesta a nuestras aplicaciones: la semántica de HTTP, como los verbos, los métodos y los encabezados, no se ve afectada, pero la forma en que se codifican en tránsito es diferente. A diferencia del protocolo HTTP/1.x de texto simple delimitado por saltos de línea, toda la comunicación HTTP/2 se divide en mensajes y marcos más pequeños, cada uno de los cuales está codificado en formato binario.
Como resultado, el cliente y el servidor deben usar el nuevo mecanismo de codificación binaria para comprenderse entre sí: un cliente de HTTP/1.x no entenderá un servidor que solo admite HTTP/2 y viceversa. Afortunadamente, nuestras aplicaciones desconocen satisfactoriamente todos estos cambios, ya que el cliente y el servidor realizan todo el trabajo de enmarcado necesario en nuestro nombre.
Transmisiones, mensajes y marcos
La introducción del nuevo mecanismo de enmarcado binario cambia la forma en que se intercambian los datos entre el cliente y el servidor. Para describir este proceso, familiaricémonos con la terminología de HTTP/2:
- Transmisión: Es un flujo bidireccional de bytes dentro de una conexión establecida, que puede llevar uno o más mensajes.
- Mensaje: Una secuencia completa de marcos que se asignan a un mensaje lógico de solicitud o respuesta.
- Marco: Es la unidad de comunicación más pequeña en HTTP/2, cada una con un encabezado de marco que, como mínimo, identifica la transmisión a la que pertenece el marco.
La relación de estos términos se puede resumir de la siguiente manera:
- Toda la comunicación se realiza a través de una sola conexión de TCP que puede llevar cualquier cantidad de transmisiones bidireccionales.
- Cada transmisión tiene un identificador único, así como información prioritaria opcional que se usa para llevar mensajes bidireccionales.
- Cada mensaje es un mensaje HTTP lógico, como una solicitud o respuesta, que consta de uno o más marcos.
- La trama es la unidad de comunicación más pequeña que lleva un tipo específico de datos, p.ej., encabezados HTTP, carga útil de mensajes, etcétera. Los marcos de diferentes transmisiones se pueden intercalar y, luego, volver a ensamblar a través del identificador de transmisión incorporado en el encabezado de cada marco.
En resumen, HTTP/2 desglosa la comunicación del protocolo HTTP en un intercambio de marcos con codificación binaria, que luego se asignan a mensajes que pertenecen a una transmisión en particular, los cuales se multiplexan dentro de una sola conexión de TCP. Esta es la base que habilita todas las demás funciones y optimizaciones de rendimiento que proporciona el protocolo HTTP/2.
multiplexación de solicitudes y respuestas
Con HTTP/1.x, si el cliente desea realizar varias solicitudes paralelas para mejorar el rendimiento, se deben usar varias conexiones TCP (consulta Usa varias conexiones de TCP). Este comportamiento es una consecuencia directa del modelo de entrega de HTTP/1.x, que garantiza que solo se pueda entregar una respuesta a la vez (cola de respuestas) por conexión. Y, peor aún, esto también da como resultado el bloqueo de cabeza de línea y el uso ineficiente de la conexión TCP subyacente.
La nueva capa de enmarcado binario en HTTP/2 quita estas limitaciones y habilita la multiplexación completa de solicitudes y respuestas, ya que permite que el cliente y el servidor dividan un mensaje HTTP en marcos independientes, los intercalan y, luego, los reensamblan en el otro extremo.
La instantánea captura varias transmisiones en tránsito dentro de la misma conexión. El cliente transmite un marco DATA
(transmisión 5) al servidor, mientras que el servidor transmite una secuencia intercalada de marcos al cliente para las transmisiones 1 y 3. Como resultado, hay tres transmisiones paralelas en tránsito.
La capacidad de dividir un mensaje HTTP en marcos independientes, intercalarlos y, luego, volver a ensamblarlos en el otro extremo es la mejora más importante de HTTP/2. De hecho, presenta un efecto dominó de numerosos beneficios de rendimiento en toda la pila de todas las tecnologías web, lo que nos permite hacer lo siguiente:
- Intercalar múltiples solicitudes en paralelo sin bloquear ninguna.
- Intercalar múltiples respuestas en paralelo sin bloquear ninguna.
- Usa una sola conexión para entregar múltiples solicitudes y respuestas en paralelo.
- Se quitaron las soluciones alternativas innecesarias de HTTP/1.x (consulta Optimización para HTTP/1.x, como archivos concatenados, objetos de imagen y fragmentación de dominios).
- Proporciona tiempos de carga de páginas más bajos mediante la eliminación de la latencia innecesaria y la mejora del uso de la capacidad de red disponible.
- Y mucho más...
La nueva capa de enmarcado binario en HTTP/2 resuelve el problema de bloqueo de línea que se encuentra en HTTP/1.x y elimina la necesidad de varias conexiones para habilitar el procesamiento y la entrega paralelos de solicitudes y respuestas. Como resultado, esto hace que la implementación de nuestras aplicaciones sea más rápida, simple y económica.
Priorización de transmisión
Una vez que un mensaje HTTP se puede dividir en muchos marcos individuales y permitimos que se multiplexen marcos de varias transmisiones, el orden en el que el cliente y el servidor intercalan y entregan los marcos se convierte en una consideración de rendimiento fundamental. Para facilitar esto, el estándar HTTP/2 permite que cada transmisión tenga un peso y una dependencia asociados:
- A cada transmisión se le puede asignar un peso entero de entre 1 y 256.
- Cada transmisión puede recibir una dependencia explícita de otra transmisión.
La combinación de dependencias y pesos de transmisión permite al cliente construir y comunicar un "árbol de priorización" que exprese cómo preferiría recibir las respuestas. A su vez, el servidor puede usar esta información para priorizar el procesamiento de transmisión mediante el control de la asignación de CPU, memoria y otros recursos, y una vez que los datos de respuesta están disponibles, la asignación de ancho de banda para garantizar la entrega óptima de respuestas de alta prioridad al cliente.
Una dependencia de transmisión dentro de HTTP/2 se declara haciendo referencia al identificador único de otra transmisión como su superior. Si se omite el identificador, se dice que la transmisión depende de la "transmisión raíz". Declarar una dependencia de transmisión indica que, si es posible, a la transmisión superior se le deben asignar recursos antes que sus dependencias. Es decir, "Por favor, procesa y entrega la respuesta D antes que la C".
A las transmisiones que comparten la misma transmisión superior (en otras palabras, transmisiones del mismo nivel) se les deben asignar recursos en proporción a su peso. Por ejemplo, si la transmisión A tiene un peso de 12 y la transmisión B de su mismo nivel tiene un peso de 4, entonces, para determinar la proporción de recursos que cada una de estas transmisiones debe recibir, haz lo siguiente:
- Suma todos los pesos:
4 + 12 = 16
- Divide el peso de cada transmisión por el peso total:
A = 12/16, B = 4/16
Por lo tanto, la transmisión A debe recibir tres cuartos y la transmisión B debe recibir un cuarto de los recursos disponibles; la transmisión B debe recibir un tercio de los recursos asignados a la transmisión A. Veamos algunos ejemplos prácticos más en la imagen de arriba. De izquierda a derecha:
- Ni la transmisión A ni la B especifican una dependencia superior y se dice que dependen de la "transmisión raíz" implícita; A tiene un peso de 12 y B tiene un peso de 4. Por lo tanto, según los pesos proporcionales, la transmisión B debe recibir un tercio de los recursos asignados a la transmisión A.
- La transmisión D depende de la transmisión raíz; C depende de D. Por lo tanto, D debe recibir una asignación completa de recursos antes que C. Los pesos son intrascendentes, porque la dependencia de C comunica una preferencia más fuerte.
- La transmisión D debe recibir una asignación total de recursos antes que C; C debe recibir una asignación completa de recursos antes que A y B; la transmisión B debe recibir un tercio de los recursos asignados a la transmisión A.
- La transmisión D debe recibir una asignación completa de recursos antes que E y C; E y C deben recibir una asignación equitativa antes que A y B. A y B deben recibir una asignación proporcional basada en sus pesos.
Como se ilustra en los ejemplos anteriores, la combinación de dependencias y pesos de transmisión proporciona un lenguaje expresivo para la priorización de recursos, que es una función fundamental para mejorar el rendimiento de la navegación cuando tenemos muchos tipos de recursos con diferentes dependencias y pesos. Mejor aún, el protocolo HTTP/2 también permite que el cliente actualice estas preferencias en cualquier momento, lo que habilita más optimizaciones en el navegador. En otras palabras, podemos cambiar dependencias y reasignar pesos en respuesta a la interacción del usuario y otros indicadores.
Una conexión por origen
Con el nuevo mecanismo de enmarcado binario implementado, HTTP/2 ya no necesita varias conexiones TCP para multiplexar transmisiones en paralelo; cada transmisión se divide en muchos marcos, que pueden intercalarse y priorizarse. Como resultado, todas las conexiones HTTP/2 son persistentes y solo se requiere una conexión por origen, lo que ofrece numerosos beneficios de rendimiento.
Tanto para SPDY como para HTTP/2, la función exitosa es la multiplexación arbitraria en un solo canal con buen control de la congestión. Me sorprende lo importante que es y lo bien que funciona. Una gran métrica que disfruto es la fracción de conexiones creadas que llevan solo una transacción HTTP (y, por lo tanto, hacen que esa transacción cargue con toda la sobrecarga). En el caso de HTTP/1, el 74% de nuestras conexiones activas llevan solo una transacción; las conexiones persistentes no son tan útiles como queremos. Sin embargo, en HTTP/2, esa cifra se desploma hasta el 25%. Ese es un gran logro para la reducción de sobrecarga. (HTTP/2 está en vivo en Firefox, Patrick McManus)
La mayoría de las transferencias de HTTP son breves e inestables, mientras que TCP está optimizado para transferencias de datos masivas de larga duración. Mediante la reutilización de la misma conexión, HTTP/2 puede hacer un uso más eficiente de cada conexión TCP y también reducir significativamente la sobrecarga general del protocolo. Además, el uso de menos conexiones reduce la huella de memoria y procesamiento a lo largo de toda la ruta de conexión (es decir, cliente, intermediarios y servidores de origen). Esto reduce los costos operativos generales y mejora el uso y la capacidad de la red. Como resultado, el cambio a HTTP/2 no solo debería reducir la latencia de red, sino también ayudar a mejorar la capacidad de procesamiento y reducir los costos operativos.
Control de flujo
El control de flujo es un mecanismo para evitar que el remitente abrume al receptor con datos que tal vez no quiera o no pueda procesar: el receptor puede estar ocupado, con una carga pesada o solo estar dispuesto a asignar una cantidad fija de recursos para una transmisión en particular. Por ejemplo, es posible que el cliente haya solicitado una transmisión de video grande con prioridad alta, pero el usuario haya pausado el video y el cliente ahora quiera pausar o limitar su entrega desde el servidor para evitar recuperar y almacenar en búfer los datos innecesarios. Como alternativa, un servidor proxy puede tener conexiones descendentes rápidas y conexiones ascendentes lentas y, del mismo modo, desea regular la rapidez con la que el downstream entrega datos para igualar la velocidad del flujo ascendente para controlar el uso de los recursos, y así sucesivamente.
¿Los requisitos anteriores te recuerdan el control de flujo de TCP? Deberían hacerlo, ya que el problema es efectivamente idéntico (consulta Control de flujo). Sin embargo, debido a que las transmisiones HTTP/2 se multiplexan dentro de una sola conexión de TCP, el control de flujo de TCP no es lo suficientemente detallado y no proporciona las API a nivel de aplicación necesarias para regular la entrega de transmisiones individuales. Para solucionarlo, HTTP/2 proporciona un conjunto de componentes básicos simples que permiten al cliente y al servidor implementar sus propios controles de flujo a nivel de transmisión y de conexión:
- El control de flujo es direccional. Cada receptor puede optar por configurar cualquier tamaño de ventana que desee para cada transmisión y toda la conexión.
- El control de flujo se basa en el crédito. Cada receptor anuncia su conexión inicial y la ventana de control de flujo de transmisión (en bytes), que se reduce cada vez que el remitente emite una trama
DATA
y se incrementa a través de una tramaWINDOW_UPDATE
enviada por el receptor. - El control de flujo no se puede inhabilitar. Cuando se establece la conexión HTTP/2, el cliente y el servidor intercambian marcos
SETTINGS
, que establecen los tamaños de la ventana de control de flujo en ambas direcciones. El valor predeterminado de la ventana de control de flujo se establece en 65,535 bytes, pero el receptor puede establecer un tamaño máximo de ventana grande (2^31-1
bytes) y mantenerlo mediante el envío de un marcoWINDOW_UPDATE
cada vez que se recibe algún dato. - El control de flujo es de salto a salto, no de extremo a extremo. Es decir, un intermediario puede usarlo para controlar el uso de recursos y, además, implementar mecanismos de asignación de recursos basados en criterios y heurística propios.
HTTP/2 no especifica ningún algoritmo en particular para implementar el control de flujo. En cambio, proporciona los componentes básicos simples y aplaza la implementación al cliente y al servidor, que pueden usarla para implementar estrategias personalizadas para regular el uso y la asignación de recursos, así como implementar nuevas capacidades de entrega que pueden ayudar a mejorar el rendimiento real y el percibido (consulta Velocidad, rendimiento y percepción humana) de nuestras aplicaciones web.
Por ejemplo, el control de flujo de la capa de la aplicación permite que el navegador recupere solo una parte de un recurso en particular, suspenda la recuperación reduciendo a cero la ventana de control de flujo de transmisión y, luego, reanude la recuperación. En otras palabras, permite que el navegador recupere una vista previa o un primer análisis de una imagen, la muestre y permita que otras recuperaciones de alta prioridad continúen. Luego, la recupere una vez que terminen de cargarse más recursos críticos.
Extracción del servidor
Otra nueva función potente de HTTP/2 es la capacidad del servidor de enviar respuestas múltiples para una sola solicitud del cliente. Es decir, además de la respuesta a la solicitud original, el servidor puede enviar recursos adicionales al cliente (Figura 12-5), sin que el cliente tenga que solicitar cada uno de forma explícita.
¿Por qué necesitaríamos este mecanismo en un navegador? Una aplicación web típica consta de decenas de recursos, los cuales el cliente descubre cuando examina el documento que proporciona el servidor. Como resultado, ¿por qué no eliminar la latencia adicional y permitir que el servidor envíe los recursos asociados con anticipación? El servidor ya sabe qué recursos requerirá el cliente: de eso se trata el servidor push.
De hecho, si alguna vez has integrado un CSS, JavaScript o cualquier otro elemento a través de un URI de datos (consulta Incorporación de recursos), ya tienes experiencia práctica con el servidor push. De hecho, cuando intercalamos manualmente el recurso en el documento, estamos enviando ese recurso al cliente, sin esperar a que este lo solicite. Con HTTP/2 podemos lograr los mismos resultados, pero con beneficios de rendimiento adicionales. Los recursos de envío pueden tener las siguientes características:
- Almacenados en caché por el cliente
- Reutilizados en diferentes páginas
- Multiplexados junto con otros recursos
- Priorizada por el servidor
- Rechazada por el cliente
Introducción a PUSH_PROMISE
Todas las transmisiones de envío del servidor se inician a través de marcos PUSH_PROMISE
, que indican la intención del servidor de enviar los recursos descritos al cliente y deben entregarse antes que los datos de respuesta que solicitan los recursos enviados. Este orden de entrega es crítico: el cliente necesita saber qué recursos desea enviar el servidor a fin de evitar crear solicitudes duplicadas para esos recursos. La estrategia más simple para cumplir con este requisito es enviar todos los marcos PUSH_PROMISE
, que contienen solo los encabezados HTTP del recurso prometido, antes de la respuesta del elemento superior (en otras palabras, los marcos DATA
).
Una vez que el cliente recibe un marco PUSH_PROMISE
, tiene la opción de rechazar la transmisión (a través de un marco RST_STREAM
), si lo desea. (Esto puede ocurrir, por ejemplo, porque el recurso ya está en la caché). Esta es una mejora importante en comparación con HTTP/1.x. Por el contrario, el uso de la incorporación de recursos, que es una "optimización" popular para HTTP/1.x, equivale a un "envío forzado": el cliente no puede inhabilitar, cancelar ni procesar el recurso intercalado de forma individual.
Con HTTP/2, el cliente mantiene el control total de cómo se usa el servidor push. El cliente puede limitar la cantidad de transmisiones enviadas de forma simultánea, ajustar la ventana de control de flujo inicial para controlar cuántos datos se envían cuando se abre la transmisión por primera vez o inhabilitar el envío del servidor por completo. Estas preferencias se comunican a través de los marcos SETTINGS
al comienzo de la conexión HTTP/2 y se pueden actualizar en cualquier momento.
Cada recurso enviado es una transmisión que, a diferencia de un recurso intercalado, permite que el cliente lo multiplexe, priorice y procese de forma individual. La única restricción de seguridad impuesta por el navegador es que los recursos enviados deben obedecer a la política del mismo origen: el servidor debe estar autorizado para entregar el contenido proporcionado.
Compresión de encabezado
Cada transferencia HTTP lleva un conjunto de encabezados que describen el recurso transferido y sus propiedades. En HTTP/1.x, estos metadatos siempre se envían como texto sin formato y agregan entre 500 y 800 bytes de sobrecarga por transferencia y, a veces, kilobytes más si se usan cookies HTTP. (Consulta Mide y controla la sobrecarga del protocolo). Para reducir esta sobrecarga y mejorar el rendimiento, HTTP/2 comprime los metadatos del encabezado de solicitud y respuesta con el formato de compresión HPACK que usa dos técnicas simples pero potentes:
- Permite que se codifiquen los campos del encabezado transmitido mediante un código Huffman estático, que reduce su tamaño de transferencia individual.
- Requiere que tanto el cliente como el servidor mantengan y actualicen una lista indexada de campos de encabezado vistos con anterioridad (en otras palabras, establece un contexto de compresión compartido), que luego se usa como referencia para codificar de manera eficiente los valores transmitidos con anterioridad.
La codificación Huffman permite comprimir los valores individuales cuando se transfieren, y la lista indexada de valores transferidos con anterioridad nos permite codificar valores duplicados mediante la transferencia de valores de índice que se pueden usar para buscar y reconstruir de manera eficiente las claves y los valores completos del encabezado.
Como una optimización adicional, el contexto de compresión de HPACK consta de una tabla estática y dinámica: la tabla estática se define en la especificación y proporciona una lista de campos de encabezado HTTP comunes que es probable que todas las conexiones usen (p. ej., nombres de encabezado válidos). La tabla dinámica está vacía al principio y se actualiza en función de los valores intercambiados dentro de una conexión en particular. Como resultado, el tamaño de cada solicitud se reduce con la programación estática Huffman para valores que no se vieron antes y la sustitución de índices por valores que ya están presentes en las tablas estáticas o dinámicas de cada lado.
Seguridad y rendimiento de HPACK
Las primeras versiones de HTTP/2 y SPDY usaban zlib, con un diccionario personalizado, para comprimir todos los encabezados HTTP. Esto generó una reducción del 85% al 88% en el tamaño de los datos del encabezado transferidos y una mejora significativa en la latencia del tiempo de carga de la página:
En el vínculo DSL de menor ancho de banda, en el que el vínculo de carga solo es de 375 Kbps, en particular, solicitar la compresión del encabezado generó mejoras significativas en el tiempo de carga de la página para ciertos sitios (en otras palabras, aquellos que emitieron una gran cantidad de solicitudes de recursos). Encontramos una reducción de entre 45 y 1,142 ms en el tiempo de carga de la página, simplemente debido a la compresión de encabezados. (Informe de SPDY, chromium.org)
Sin embargo, en el verano de 2012, se publicó un ataque a la seguridad "CRIME" contra los algoritmos de compresión TLS y SPDY, que podía provocar un secuestro de sesión. Como resultado, el algoritmo de compresión zlib se reemplazó por HPACK, que se diseñó específicamente para abordar los problemas de seguridad descubiertos, ser eficiente y fácil de implementar de forma correcta y, por supuesto, habilitar una buena compresión de los metadatos del encabezado HTTP.
Si deseas obtener más detalles sobre el algoritmo de compresión HPACK, consulta IETF HPACK - Header Compression for HTTP/2 (HPACK de IETF: compresión de encabezado para HTTP/2).
Lecturas adicionales
- "HTTP/2": el artículo completo de Ilya Grigorik
- "Setting up HTTP/2" (Configuración de HTTP/2): Surma explica cómo configurar HTTP/2 en diferentes backends.
- “HTTP/2 está aquí, ¡a optimizar! – Presentación de Ilya Grigorik de Velocity 2015
- “Reglas generales para el envío de HTTP/2”: Un análisis de Tom Bergan, Simon Pelchat y Michael Buettner sobre cuándo y cómo usar push.