Introducción a HTTP/2

HTTP/2 hará que nuestras aplicaciones sean más rápidas, sencillas y sólidas (una combinación poco frecuente), ya que nos permite deshacer muchas de las soluciones alternativas de HTTP/1.1 que se realizaron antes dentro de 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, ya que permiten la multiplexación completa de solicitudes y respuestas, minimizar la sobrecarga de protocolo a través de una compresión eficiente de los campos de encabezado HTTP y agregar compatibilidad con la priorización de solicitudes y el envío de servidores. Para implementar estos requisitos, existe una gran variedad de otras mejoras de protocolo, como nuevos mecanismos de control de flujo, manejo de errores y actualización, pero estas son las funciones más importantes que todo desarrollador web debe comprender y aprovechar en sus aplicaciones.

HTTP/2 no modifica la semántica de la aplicación de HTTP de ninguna manera. Todos los conceptos principales, como los métodos de HTTP, los códigos de estado, los URIs y los campos de encabezado, permanecen vigentes. En cambio, HTTP/2 modifica la forma en que se formatean (enmarcan) y transportan los datos entre el cliente y el servidor (ambos 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 HTTP/1.2?

Para lograr los objetivos de rendimiento establecidos por el grupo de trabajo HTTP, HTTP/2 presenta una nueva capa de enmarcado binario que no es retrocompatible con los servidores y clientes HTTP/1.x anteriores, por lo que la versión principal del protocolo aumenta a HTTP/2.

Dicho esto, a menos que implementes un servidor web (o un cliente personalizado) con sockets TCP sin procesar, no verás ninguna diferencia: el cliente y el servidor realizan todo el enmarcado nuevo 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 servidor push.

Breve historia de SPDY y HTTP/2

SPDY era un protocolo experimental, desarrollado en Google y anunciado a mediados de 2009, cuyo objetivo principal era tratar de 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).
  • Evitar la necesidad de que los autores del sitio web modifiquen el contenido.
  • Minimizar la complejidad de implementación y evitar cambios en la infraestructura de red.
  • Desarrollar 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, ambos 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 probamos SPDY en condiciones de laboratorio. 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, ya que 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, Facebook) como pequeños, implementaban SPDY dentro de su infraestructura. De hecho, SPDY estaba encaminado para convertirse en un estándar de facto gracias a la creciente adopción de la industria.

Al observar esta tendencia, el HTTP Working Group (HTTP-WG) inició un nuevo esfuerzo para aprovechar las lecciones aprendidas de SPDY, compilarlas 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 mucho debate en el grupo de trabajo, la especificación de SPDY se adoptó como punto de partida para el nuevo protocolo HTTP/2.

Durante los años siguientes SPDY y HTTP/2 continuaron evolucionando en paralelo, y SPDY actuó como una rama experimental que se utilizaba para probar nuevas características y propuestas para el estándar HTTP/2. Lo que se ve bien en teoría puede no funcionar en la práctica y viceversa, y SPDY ofreció una vía 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 publican 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 publican 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 plan de dar de baja SPDY y la extensión NPN para TLS:

Los cambios principales de HTTP/2 en comparación con HTTP/1.1 se enfocan en mejorar el rendimiento. Algunas funciones clave, como multiplexación, compresión de encabezados, priorización y negociación de protocolos, evolucionaron del trabajo realizado en un protocolo abierto, 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, al mismo tiempo, quitar la compatibilidad con la extensión TLS llamada NPN a favor de ALPN en Chrome. Se recomienda encarecidamente a los desarrolladores de servidores que migren a HTTP/2 y ALPN.

Nos complace haber contribuido al proceso de estándares abiertos que condujo a HTTP/2 y esperamos ver una amplia adopción debido al amplio compromiso de la industria sobre 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 obtengan experiencia en el mundo real con el nuevo protocolo a medida que se desarrollaba. Como resultado, el estándar HTTP/2 es uno de los mejores y más probados de inmediato. En el momento en que IESG aprobó HTTP/2, había decenas de implementaciones de clientes y servidores completamente probadas y listas para la producción. De hecho, unas semanas después de que se aprobara el protocolo final, muchos usuarios ya disfrutaban de sus beneficios, ya 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 de forma intencional 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 oficial de IETF; consulta Breve historia de HTTP. Como tal, HTTP/0.9-1.x cumplió exactamente lo que se propuso: HTTP es uno de los protocolos de aplicaciones más adoptados en Internet.

Por desgracia, la simplicidad de la implementación también afectó 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 genera 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 TCP subyacente, y así sucesivamente.

Estas limitaciones no fueron fatales, pero a medida que las aplicaciones web seguían creciendo en cuanto a alcance, complejidad e importancia en nuestras vidas cotidianas, impusieron una carga cada vez mayor tanto para los desarrolladores como para los usuarios de la Web, que es la brecha exacta que se diseñó para HTTP/2:

HTTP/2 permite un uso más eficiente de los recursos de red y una menor percepción de la 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 intercalar los mensajes de solicitud y respuesta en la misma conexión y utiliza una codificación eficiente para los campos de encabezado HTTP. También permite la priorización de solicitudes, lo que permite que las solicitudes más importantes se completen con mayor rapidez, lo que mejora aún más el rendimiento.

El protocolo resultante es más amigable 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 mayor duración, lo que, a su vez, conduce a una mejor utilización de la capacidad de red disponible. Por último, HTTP/2 también permite un procesamiento más eficiente de los mensajes a través del 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 la aplicación de HTTP es la misma y no se realizaron cambios en la funcionalidad ofrecida ni en los conceptos principales, 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 atributos.

Capa de enmarcado 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 se encapsulan y se transfieren los mensajes HTTP entre el cliente y el servidor.

Capa de enmarcado binario HTTP/2

La "capa" se refiere a una opción de diseño para introducir un nuevo 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 verbos, métodos y encabezados, no se ve afectada, pero la forma en que se codifican mientras están 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 se codifica en formato binario.

Como resultado, tanto el cliente como el servidor deben usar el nuevo mecanismo de codificación binaria para comprenderse entre sí: un cliente HTTP/1.x no comprenderá un servidor solo HTTP/2 y viceversa. Afortunadamente, nuestras aplicaciones desconocen 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: Un flujo bidireccional de bytes dentro de una conexión establecida, que puede llevar uno o más mensajes.
  • Mensaje: Es una secuencia completa de marcos que se asignan a un mensaje lógico de solicitud o respuesta.
  • Trama: Es la unidad de comunicación más pequeña en HTTP/2, cada una de las cuales contiene 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 puede resumirse de la siguiente manera:

  • Toda la comunicación se realiza mediante una única conexión de TCP que puede llevar cualquier cantidad de transmisiones bidireccionales.
  • Cada transmisión tiene un identificador único y también información de prioridad opcional que se usa para llevar mensajes bidireccionales.
  • Cada mensaje es un mensaje de HTTP lógico, como una solicitud o respuesta, que consta de uno o más marcos.
  • El marco 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 ensamblarlos mediante el identificador de transmisión incorporado en el encabezado de cada fotograma.

Transmisiones, mensajes y tramas de HTTP/2

En resumen, HTTP/2 divide la comunicación del protocolo HTTP en un intercambio de tramas con codificación binaria, que luego se asignan a mensajes que pertenecen a una transmisión en particular, los cuales se multiplexan en una sola conexión 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 quiere realizar varias solicitudes paralelas para mejorar el rendimiento, se deben usar varias conexiones TCP (consulta Usa varias conexiones TCP). Este comportamiento es una consecuencia directa del modelo de entrega HTTP/1.x, que garantiza que solo se pueda entregar una respuesta a la vez (cola de respuestas) por conexión. Peor aún, esto también genera 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 permite 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 intercalen y los vuelvan a ensamblar en el otro extremo.

multiplexación de solicitud y respuesta HTTP/2 dentro de una conexión compartida

La instantánea captura varias transmisiones en tránsito dentro de la misma conexión. El cliente transmite una trama DATA (transmisión 5) al servidor, mientras que el servidor transmite una secuencia intercalada de tramas 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, introduce 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 varias solicitudes y respuestas en paralelo.
  • Quita las soluciones alternativas de HTTP/1.x innecesarias (consulta Cómo optimizar para HTTP/1.x, como archivos concatenados, objeto de imagen y fragmentación de dominios).
  • Proporcionar tiempos de carga de páginas inferiores mediante la eliminación de la latencia innecesaria y la mejora de la utilización 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 encabezado de línea que se encuentra en HTTP/1.x y elimina la necesidad de múltiples conexiones para habilitar el procesamiento y la entrega paralelos de solicitudes y respuestas. Como resultado, nuestras aplicaciones son más rápidas, sencillas y económicas de implementar.

Priorización de transmisiones

Una vez que un mensaje HTTP se puede dividir en muchos marcos individuales y permitimos que los marcos de varias transmisiones se multiplexen, 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 de número entero 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 que el cliente construya y comunique 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.

Dependencias y pesos de transmisión de HTTP/2

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 a sus dependencias. En otras palabras, “procesa y entrega la respuesta D antes que la respuesta 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 del mismo nivel tiene un peso de 4, para determinar la proporción de recursos que cada una de estas transmisiones debe recibir:

  1. Suma todos los pesos: 4 + 12 = 16
  2. 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 en la imagen de arriba. De izquierda a derecha:

  1. Ni la transmisión A ni la B especifican una dependencia principal 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 las ponderaciones proporcionales, la transmisión B debe recibir un tercio de los recursos asignados a la transmisión A.
  2. La transmisión D depende de la transmisión raíz; C depende de D. Por lo tanto, D debería 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.
  3. La transmisión D debe recibir una asignación completa 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.
  4. 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 igual antes que A y B. A y B deben recibir una asignación proporcional según sus pesos.

Como se muestra en los ejemplos anteriores, la combinación de dependencias y ponderaciones 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 ponderaciones. 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 las dependencias y reasignar las ponderaciones en respuesta a la interacción del usuario y otros indicadores.

Una conexión por origen

Con el nuevo mecanismo de enmarcado binario establecido, 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 principal es la multiplexación arbitraria en un solo canal con buen control de 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 toda la sobrecarga). Para HTTP/1, el 74% de nuestras conexiones activas llevan una sola transacción; las conexiones persistentes no son tan útiles como queramos. Sin embargo, en HTTP/2 ese número desciende al 25%. Es una gran ventaja en la reducción de la sobrecarga. (HTTP/2 está disponible en Firefox, Patrick McManus)

La mayoría de las transferencias HTTP son cortas y poco estables, mientras que TCP está optimizado para transferencias de datos masivas y de larga duración. Cuando se vuelve a usar la misma conexión, HTTP/2 puede hacer un uso más eficiente de cada conexión TCP y reducir de forma significativa la sobrecarga de protocolo general. Además, el uso de menos conexiones reduce la huella de memoria y de procesamiento en toda la ruta de conexión (en otras palabras, cliente, intermediarios y servidores de origen). Esto reduce los costos operativos generales y mejora la utilización 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, tener mucha carga o solo estar dispuesto a asignar una cantidad fija de recursos a una transmisión en particular. Por ejemplo, es posible que el cliente haya solicitado una gran transmisión de video por Internet con prioridad alta, pero el usuario pausó el video y ahora desea pausar o limitar la entrega desde el servidor para evitar la recuperación y el almacenamiento en búfer de datos innecesarios. Como alternativa, un servidor proxy puede tener conexiones ascendentes rápidas y conexiones ascendentes lentas y, de manera similar, desea regular la rapidez con la que el downstream entrega datos para que coincida con la velocidad de subida y controlar su uso de 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 en una sola conexión TCP, el control de flujo de TCP no es lo suficientemente detallado y no proporciona las APIs a nivel de aplicación necesarias para regular la entrega de transmisiones individuales. Para abordar esto, HTTP/2 proporciona un conjunto de componentes básicos simples que permiten al cliente y al servidor implementar su propio control de flujo a nivel de transmisión y 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 cuando el remitente emite un marco DATA y se incrementa mediante un marco WINDOW_UPDATE que envía el receptor.
  • El control de flujo no se puede inhabilitar. Cuando se establece la conexión HTTP/2, el cliente y el servidor intercambian tramas 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 gran tamaño máximo de ventana (2^31-1 bytes) y mantenerlo mediante el envío de un marco WINDOW_UPDATE cada vez que se reciben datos.
  • El control de flujo es de salto a salto, no de extremo a extremo. Es decir, un intermediario puede usarla para controlar el uso de recursos y también 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 componentes básicos simples y aplaza la implementación en el cliente y el servidor, que pueden usarla para implementar estrategias personalizadas con el objetivo de regular el uso y la asignación de recursos, así como implementar capacidades de entrega nuevas que pueden ayudar a mejorar el rendimiento real y 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, coloque la recuperación en espera mediante la reducción a cero de la ventana de control del flujo de transmisión y, luego, la reanude más tarde. 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 prioridad alta continúen, y reanudar la recuperación una vez que se hayan terminado de cargar los 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 manera explícita.

El servidor inicia transmisiones (promesas) nuevas para recursos de envío

¿Por qué necesitaríamos un mecanismo de este tipo en un navegador? Una aplicación web típica consta de decenas de recursos, todos los cuales son descubiertos por el cliente 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; eso es el servidor push.

De hecho, si alguna vez integraste un recurso de CSS, JavaScript o cualquier otro a través de un URI de datos (consulta Inserción de recursos), ya tienes experiencia práctica con el servidor push. Cuando se intercala el recurso en el documento de forma manual, se envía el 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 push pueden ser:

  • Almacenados en caché por el cliente
  • Reutilizados en diferentes páginas
  • Multiplexados junto con otros recursos
  • Priorizada por el servidor
  • Rechazado por el cliente

Introducción a PUSH_PROMISE

Todas las transmisiones del servidor push 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 fundamental: el cliente necesita saber qué recursos desea enviar el servidor a fin de evitar que se creen solicitudes duplicadas para estos recursos. La estrategia más simple para cumplir este requisito es enviar todos los marcos PUSH_PROMISE, que contienen solo los encabezados HTTP del recurso prometido, antes que la respuesta del elemento superior (en otras palabras, los marcos DATA).

Una vez que el cliente recibe un fotograma PUSH_PROMISE, tiene la opción de rechazar la transmisión (a través de un fotograma RST_STREAM) si así lo desea. (Esto puede ocurrir, por ejemplo, porque el recurso ya está en la caché). Esta es una mejora importante con respecto a HTTP/1.x. Por el contrario, el uso de la intercalación de recursos, que es una “optimización” popular para HTTP/1.x, es equivalente a una “inserción forzada”: el cliente no puede inhabilitarla, cancelarla ni procesar el recurso intercalado de forma individual.

Con HTTP/2 el cliente mantiene el control total de cómo se utiliza el servidor push. El cliente puede limitar la cantidad de transmisiones enviadas en simultáneo, ajustar la ventana de control de flujo inicial para controlar cuántos datos se envían cuando la transmisión se abre por primera vez o inhabilitar el servidor push por completo. Estas preferencias se comunican a través de los marcos SETTINGS al comienzo de la conexión HTTP/2 y pueden actualizarse 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 que aplica el navegador es que los recursos enviados deben obedecer la política del mismo origen: el servidor debe estar autorizado para 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 de protocolo). Para reducir esta sobrecarga y mejorar el rendimiento, HTTP/2 comprime los metadatos del encabezado de solicitud y respuesta mediante el formato de compresión HPACK, que usa dos técnicas simples pero poderosas:

  1. Permite que los campos del encabezado transmitido se codifiquen a través de un código Huffman estático, que reduce su tamaño de transferencia individual.
  2. 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 que los valores individuales se compriman cuando se transfieren, y la lista indexada de valores transferidos anteriormente nos permite codificar valores duplicados mediante la transferencia de valores de índice que se pueden usar para buscar y reconstruir los valores y las claves de encabezado completos de manera eficiente.

HPACK: Compresión de encabezado para HTTP/2

Como otra optimización, 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á inicialmente vacía y se actualiza en función de los valores intercambiados en una conexión en particular. Como resultado, el tamaño de cada solicitud se reduce mediante la codificación Huffman estática para valores que no se han visto antes y la sustitución de índices por valores que ya están presentes en las tablas estáticas o dinámicas en 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 transferido 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 tiene 375 Kbps, la compresión del encabezado de solicitud en particular dio lugar a 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). Descubrimos una reducción de entre 45 y 1,142 ms en el tiempo de carga de la página debido a la compresión del encabezado. (Informe de SPDY, Chromium.org)

Sin embargo, en el verano de 2012, se publicó un ataque de seguridad "CRIME" contra los algoritmos de compresión TLS y SPDY, que podía provocar el 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 simple de implementar correctamente y, por supuesto, habilitar una buena compresión de los metadatos del encabezado HTTP.

Para obtener todos los detalles del 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