Presentamos HTTP/2 Server Push con NGINX 1.13.9

NGINX | 20 de febrero de 2018

La compatibilidad con la inserción de servidor HTTP/2 también está incluida en NGINX Plus R15.

Nos complace anunciar que NGINX 1.13.9, publicado el 20 de febrero de 2018, incluye compatibilidad con la inserción de servidor HTTP/2. Para los usuarios de NGINX Plus, la compatibilidad con la inserción de servidor HTTP/2 se incluirá en la próxima versión NGINX Plus R15, cuyo lanzamiento está previsto para abril de 2018.

La inserción del servidor, que se define en la especificación HTTP/2, permite que un servidor envíe recursos de forma anticipada a un cliente remoto, previendo que este pueda solicitarlos en breve. De este modo, es posible reducir en uno o más el número de RTT (tiempo de ida y vuelta, es decir, el tiempo necesario para una solicitud y su respuesta) durante la carga de una página, lo que permite ofrecer una respuesta más rápida al usuario.

La inserción del servidor puede utilizarse para proporcionar de antemano a un cliente hojas de estilo, imágenes y otros recursos que necesitará para renderizar una página web. Procure insertar únicamente los recursos necesarios; no inserte recursos que probablemente el cliente ya tenga almacenados en caché.

En esta entrada del blog, describo:

Configuración de Server Push de HTTP/2

Para enviar recursos junto con la carga de una página, utilice la directiva «http2_push» de la siguiente manera:

server {    # Ensure that HTTP/2 is enabled for the server        
    listen 443 ssl http2;

    ssl_certificate ssl/certificate.pem;
    ssl_certificate_key ssl/key.pem;

    root /var/www/html;

    # whenever a client requests demo.html, also push
    # /style.css, /image1.jpg and /image2.jpg
    location = /demo.html {
        http2_push /style.css; http2_push /image1.jpg; http2_push /image2.jpg;
    }
}

Verificación de Server Push de HTTP/2

Puede comprobar fácilmente que el envío automático del servidor está activo mediante cualquiera de estos dos métodos:

  • Las herramientas para desarrolladores de tu navegador web
  • Un cliente HTTP/2 de línea de comandos como nghttp

Verificación con las herramientas para desarrolladores (Google Chrome)

A continuación se explica cómo utilizar las herramientas para desarrolladores del navegador web para comprobar que la inserción del servidor está activa, tomando Google Chrome como ejemplo. En la figura, la columna «Iniciador» de la pestaña «Red» de las herramientas para desarrolladores de Chrome indica que se enviaron varios recursos al cliente como parte de una solicitud de /demo.html.

La columna Iniciador indica que se utilizó la inserción del servidor para enviar recursos

Verificación con un cliente de línea de comandos (nghttp)

Además de las herramientas del navegador web, puede utilizar el cliente de línea de comandos nghttp del proyecto nghttp2.org para comprobar que la inserción del servidor está activa. Puede descargar el cliente de línea de comandos nghttp desde GitHub o instalar el paquete correspondiente del sistema operativo, si está disponible. Para Ubuntu, utilice el paquete nghttp2-client.

En la salida, el asterisco (*) indica los recursos enviados mediante push por el servidor.

$ nghttp -ans https://example.com/demo.htmlid  responseEnd requestStart  process code size request path
 13    +84.25ms       +136us  84.11ms  200  492 /demo.html
  2    +84.33ms *   +84.09ms    246us  200  266 /style.css
  4   +261.94ms *   +84.12ms 177.83ms  200  40K /image2.jpg
  6   +685.95ms *   +84.12ms 601.82ms  200 173K /image1.jpg

Envío automático de recursos a los clientes

En muchas situaciones, resulta poco práctico —o incluso imposible— enumerar en el archivo de configuración de NGINX los recursos que se desean enviar. Por este motivo, NGINX también permite interceptar las cabeceras de precarga Link preload headers, para luego enviar los recursos identificados en ellas. Para habilitar la precarga, incluya la directiva http2_push_preload en la configuración:

server {    # Ensure that HTTP/2 is enabled for the server        
    listen 443 ssl http2;

    ssl_certificate ssl/certificate.pem;
    ssl_certificate_key ssl/key.pem;

    root /var/www/html;

    # Intercept Link header and initiate requested Pushes
    location = /myapp {
        proxy_pass http://upstream;
        http2_push_preload on;
    }
}

Por ejemplo, cuando NGINX funciona como proxy (para HTTP, FastCGI u otros tipos de tráfico), el servidor de origen puede añadir una cabecera Link como esta a su respuesta:

Link: </style.css>; as=style; rel=preload

NGINX intercepta esta cabecera e inicia el envío mediante push del servidor de /style.css. La ruta de la cabecera Link debe ser absoluta; no se admiten rutas relativas como ./style.css. La ruta puede incluir opcionalmente una cadena de consulta.

Para enviar varios objetos mediante push, puede proporcionar varias cabeceras Link o, mejor aún, incluir todos los objetos en una lista separada por comas:

Link: </style.css>; as=style; rel=preload, </favicon.ico>; as=image; rel=preload

Si no quieres que NGINX envíe un recurso precargado, añade el parámetro nopush a la cabecera:

# Resource is not pushedLink: </nginx.png>; as=image; rel=preload; nopush

Cuando http2_push_preload está habilitado, también puede iniciar la inserción del servidor de precarga configurando la cabecera de respuesta en su configuración de NGINX:

add_header Link "</style.css>; as=style; rel=preload";

Envío selectivo de recursos a los clientes

La especificación HTTP/2 no aborda el reto de determinar si se deben enviar recursos mediante push. Evidentemente, lo mejor es enviar recursos a los clientes mediante push únicamente si se sabe que probablemente los necesitarán y que es poco probable que ya los tengan almacenados en caché.

Un enfoque posible consiste en enviar recursos a los clientes únicamente en su primera visita al sitio. Por ejemplo, puedes comprobar si existe una cookie de sesión y establecer condicionalmente la cabecera Link, de modo que los recursos solo se precarguen si la cookie de sesión no está presente.

Suponiendo que los clientes se comporten correctamente e incluyan la cookie en las solicitudes posteriores, con la siguiente configuración NGINX envía los recursos a los clientes una sola vez por sesión del navegador:

server {
    listen 443 ssl http2 default_server;

    ssl_certificate ssl/certificate.pem;
    ssl_certificate_key ssl/key.pem;

    root /var/www/html;
    http2_push_preload on;

    location = /demo.html {
        add_header Set-Cookie "session=1";
        add_header Link $resources;
    }
}

map $http_cookie $resources {
    "~*session=1" "";
    default "</style.css>; as=style; rel=preload, </image1.jpg>; as=image; rel=preload, </image2.jpg>; as=image; rel=preload";
}

Medición del efecto del envío push del servidor HTTP/2

Para medir el efecto de la inserción del servidor, creamos una página de prueba sencilla, /demo.html, que hace referencia a una hoja de estilos independiente, /style.css. A su vez, la hoja de estilos hace referencia a dos imágenes. Probamos los tiempos de carga de la página con tres configuraciones diferentes:

  • Solicitudes GET secuenciales (sin optimización): el navegador cargó los recursos cuando detectó que eran necesarios
  • Indicaciones de precarga – Las indicaciones de precarga (Link) se incluyeron en la primera respuesta para indicar al navegador que cargara las dependencias
  • Envío desde el servidor (solo HTTP/2) – Las dependencias se enviaban de forma anticipada al navegador

Se probaron tres configuraciones para medir el impacto de HTTP/2 con Server Push

Realizamos varias pruebas de cada configuración utilizando HTTP, HTTPS o HTTP/2. Las dos primeras configuraciones se aplican a los tres protocolos, mientras que la inserción del servidor solo se aplica a HTTP/2.

El comportamiento se midió utilizando las herramientas para desarrolladores de Chrome. Se evaluó el comportamiento más habitual de cada configuración y se calcularon los valores medios; además, los tiempos se correlacionaron con el RTT del enlace (medido mediante «ping») para ilustrar el efecto mecánico de cada método.

Los resultados de las pruebas muestran que cada configuración requirió numerosos viajes de ida y vuelta

Algunas observaciones básicas

  • DOM cargado es el tiempo necesario para iniciar una nueva conexión y recuperar la página demo.html. Hoja de estilos es el tiempo necesario para recuperar el recurso CSS.
  • Establecer una conexión mediante HTTP requiere 1 RTT; con HTTPS y HTTP/2, requiere 2 RTT.
  • La carga útil de los recursos HTML y CSS es menor que el tamaño de la unidad máxima de transmisión (MTU), por lo que una operación GET se completa en aproximadamente 1 RTT.

Interpretación de los resultados: indicaciones de precarga

  • Las indicaciones de precarga tienen un efecto mínimo en el recurso CSS porque se hace referencia a él directamente en el recurso HTML y este se entrega rápidamente. El navegador inicia la solicitud del CSS en cuanto se entrega la página HTML.
  • Las indicaciones de precarga permiten iniciar rápidamente la descarga de los recursos declarados en el recurso CSS (en este caso, las dos imágenes). Al utilizar indicaciones de precarga, las descargas pueden comenzar 1 RTT antes.
  • El ahorro neto que ofrecen las indicaciones de precarga puede ser de 1 RTT o más. Al descargar recursos en paralelo, el navegador debe abrir una o varias conexiones adicionales. El rendimiento depende de si las solicitudes lentas (respuestas de mayor tamaño) se programan primero o se retrasan mientras se abren nuevas conexiones. Este orden impredecible de las solicitudes explica la mejora de 1 RTT en HTTP y HTTP/2, y de 2 RTT en HTTPS.

Interpretación de los resultados: inserción del servidor

  • La inserción del servidor mejoró el tiempo de las indicaciones de precarga en 1 RTT adicional. Las «respuestas» de inserción se iniciaron al mismo tiempo que la respuesta a la primera solicitud, mientras que las respuestas de las indicaciones de precarga sufrieron un retraso de 1 RTT: 0,5 RTT por la respuesta a la primera solicitud más 0,5 RTT por la solicitud de precarga GET.

Notas de la prueba

  • Se realizaron varias pruebas para cada configuración. Cada prueba comenzó con la caché del navegador vacía y sin conexiones persistentes establecidas con el servidor NGINX. Las directivas de NGINX keepalive_timeout y http2_idle_timeout se utilizaron para cerrar rápidamente las conexiones persistentes.
  • Actualmente no parece posible enviar recursos de fuentes a Chrome mediante push, quizá debido a una complicación conocida. Chrome solicita explícitamente un recurso de fuente aunque ya se haya enviado mediante push.
  • Se procuró borrar explícitamente las cachés de los navegadores antes de cada prueba, y todo el contenido se sirvió con cabeceras de control de caché caducadas.
  • Chrome almacena en caché los recursos precargados. Estos recursos almacenados en caché no siempre se omiten al desactivar el almacenamiento en caché ni se eliminan de forma sistemática al ejecutar explícitamente la opción «borrar la caché del navegador». (En Chrome, una forma de desactivar el almacenamiento en caché es marcar la casilla Desactivar caché de la pestaña Red de las Herramientas para desarrolladores. Para borrar la caché del navegador, con las Herramientas para desarrolladores abiertas, puede hacer clic con el botón derecho en el botón de recarga del navegador y seleccionar Vaciar la caché y volver a cargar de manera forzada).
  • Algunos intentos de precargar contenido hicieron que Chrome no pudiera revalidar las copias almacenadas en caché y, a continuación, descargara el recurso de la forma habitual. Estos intentos no se contabilizaron en las mediciones.
  • Chrome añade un retraso innecesario de 2 RTT a todas las conexiones SSL nuevas que utilizan certificados autofirmados aceptados previamente. La prueba se realizó con certificados de Let’s Encrypt firmados por una autoridad de certificación para evitar este retraso de 2 RTT.
  • Los retrasos de DNS se solucionaron editando el archivo local /etc/hosts.
  • Estas sencillas pruebas no pretendían medir el efecto del envío activo del servidor cuando el cliente ya tenía una copia del recurso en caché. En las pruebas, se vaciaron todas las cachés antes de cada prueba y la mayoría de los envíos fueron demasiado rápidos como para cancelarlos.

Conclusión

Esta prueba se simplificó deliberadamente para destacar el funcionamiento de las indicaciones de precarga y el envío activo del servidor. En situaciones sencillas, el envío activo del servidor ofrece una mejora de 1 RTT con respecto a las indicaciones de precarga, y una mejora aún mayor en comparación con las solicitudes GET secuenciales y sin optimizar, y con la detección de recursos dependientes.

Los casos de uso más realistas presentan muchas más variables: múltiples recursos dependientes, múltiples fuentes e incluso la posibilidad de desperdiciar ancho de banda al enviar recursos que ya están almacenados en caché o que no se necesitan de inmediato. Las diferencias entre navegadores también afectan al rendimiento. Sin duda, sus resultados diferirán de los de esta sencilla prueba.

Por ejemplo, el equipo de Chrome ha publicado una serie de recomendaciones detalladas sobre cuándo implementar el envío activo del servidor y ha realizado mediciones en sitios más complejos para comparar los efectos de no aplicar ninguna optimización, usar indicaciones de precarga y emplear el envío activo del servidor a través de HTTP/2. Su informe «Reglas prácticas para el envío activo mediante HTTP/2» es una lectura recomendable para cualquiera que esté pensando en implementar el envío activo del servidor mediante HTTP/2 en producción.

La conclusión pragmática es que, si puede identificar de antemano qué recursos se necesitan, resulta realmente beneficioso que los servidores de origen envíen una indicación de precarga. La ventaja adicional de enviar estos recursos mediante push es pequeña, pero cuantificable, aunque puede provocar un desperdicio de ancho de banda y retrasos en los recursos necesarios. Debe probar y supervisar cuidadosamente cualquier configuración de server push.

Apéndice: ¿Cómo funciona el envío push de HTTP/2?

La información que figura a continuación se basa en parte en la investigación expuesta en la detallada entrada de blog de Jake Archibald «HTTP/2 push es más difícil de lo que pensaba».

El envío push del servidor HTTP/2 se utiliza normalmente para enviar recursos dependientes de forma anticipada cuando el cliente solicita un recurso. Por ejemplo, si un cliente solicita una página web, el servidor puede enviarle de forma proactiva las hojas de estilo, las fuentes y las imágenes asociadas.

Cuando un cliente establece una conexión HTTP/2, el servidor puede optar por iniciar una o varias respuestas push a través de dicha conexión. Estas respuestas envían recursos que el cliente no ha solicitado explícitamente.

El cliente puede rechazar un envío push (enviando una trama RST_STREAM) o aceptarlo. El cliente almacena el contenido enviado en una «caché push» local asociada a la conexión HTTP/2.

Más adelante, cuando el cliente solicita un recurso mediante una conexión HTTP/2 establecida, comprueba si la caché de inserción de la conexión contiene una respuesta completada o en curso para dicha solicitud. Utiliza el recurso almacenado en caché en lugar de realizar una nueva solicitud HTTP/2 para obtenerlo.

Cualquier recurso enviado mediante push permanece en la caché de push de cada conexión hasta que (a) se utiliza o (b) se cierra la conexión HTTP/2:

  1. Si se utiliza el recurso, el cliente obtiene una copia y se elimina la entrada de la caché de inserción. Si el recurso se puede almacenar en caché, el cliente puede guardar su copia en la caché HTTP de la página.
  2. Si la conexión HTTP/2 se cierra por cualquier motivo, se elimina su caché de inserción local.

Esto tiene varias implicaciones:

  • El contenido de la caché de páginas HTTP del navegador se utiliza con preferencia al contenido de la caché push, aunque el contenido enviado mediante push sea más reciente.
  • Las conexiones HTTP/2 se pueden compartir entre distintas cargas de página. Un recurso enviado mediante push como resultado de la carga de una página puede utilizarse cuando se solicita durante la carga de otra página.
  • Las solicitudes con credenciales utilizan conexiones HTTP/2 distintas de las solicitudes sin credenciales; por ejemplo, es posible que un recurso enviado mediante una solicitud de origen cruzado (con credenciales) no se encuentre si el navegador realiza una solicitud sin credenciales para obtenerlo.

Puedes consultar una lista mucho más detallada de problemas en el artículo de Jake Archibald «HTTP/2 push es más complicado de lo que pensaba».

La inserción del servidor HTTP/2 es una funcionalidad interesante. Asegúrate de probar exhaustivamente la configuración de inserción del servidor HTTP/2 y prepárate para recurrir a las indicaciones de precarga cuando estas ofrezcan un comportamiento más predecible y que tenga en cuenta la caché.


Compartir

Sobre el autor

Owen Garrett
Owen GarrettDirector sénior de Gestión de Producto

Más artículos de Owen Garrett

Artículos relacionados del blog

Únete a F5 NGINX en AppWorld 2024
NGINX | 04/01/2024

Únete a F5 NGINX en AppWorld 2024

Descubre las numerosas razones para asistir al evento insignia de F5, que incluirá un emocionante anuncio sobre la cartera de productos NGINX.

Rotación de certificados SSL/TLS sin reinicios en NGINX Open Source
NGINX | 26/09/2023

Rotación de certificados SSL/TLS sin reinicios en NGINX Open Source

La función de diccionario compartido permite configurar NGINX sin necesidad de reiniciarlo al renovar los certificados SSL/TLS.

Exposición de APIs en Kubernetes
NGINX | 10/03/2022

Exposición de APIs en Kubernetes

La unidad 2 de Microservices March 2022 se centra en cómo exponer tus API en Kubernetes. Aquí tienes enlaces a los recursos que necesitas: la retransmisión en directo del 14 de marzo, artículos de blog y otros recursos de preparación, y el laboratorio práctico «Protege las API de Kubernetes mediante la limitación de solicitudes».

NGINX Sprint 2.0: visión clara, código renovado y nuevos compromisos con el código abierto
NGINX | 23/08/2021

NGINX Sprint 2.0: visión clara, código renovado y nuevos compromisos con el código abierto

Hoy, en NGINX Sprint 2.0, anunciamos cómo respaldaremos nuestra visión del futuro de las aplicaciones modernas con una mayor presencia en la comunidad de código abierto, incluida la participación en el proyecto comunitario Kubernetes, nuevos proyectos de software de código abierto y la arquitectura de referencia para aplicaciones modernas.

Presentamos NGINX Plus R22
NGINX | 09/06/2020

Presentamos NGINX Plus R22

NGINX Plus R22 incorpora nuevas funcionalidades, como la compatibilidad con OCSP para verificar la revocación de certificados en TLS mutuo, el uso de varios proveedores de identidad OIDC en una configuración, el seguimiento en tiempo real de la limitación de solicitudes y conexiones en el panel de NGINX Plus y mejoras en NGINX JavaScript.

Presentamos NGINX Plus R17
NGINX | 11/12/2018

Presentamos NGINX Plus R17

NGINX Plus R17 incorpora compatibilidad con la limitación de velocidad en dos fases y con TLS 1.3, la versión más reciente del protocolo Transport Layer Security. La configuración de OpenID Connect es más sencilla y NGINX ModSecurity WAF es el doble de rápido que antes. También se ha actualizado el módulo JavaScript de NGINX.