Einführung in HTTP/2 Server Push mit NGINX 1.13.9

NGINX | 20. Februar 2018

Unterstützung für HTTP/2 Server Push ist auch in NGINX Plus R15 enthalten.

Wir freuen uns, bekannt geben zu können, dass NGINX 1.13.9, das am 20. Februar 2018 veröffentlicht wurde, HTTP/2 Server Push unterstützt. Für Benutzer von NGINX Plus wird die Unterstützung für HTTP/2 Server Push in der kommenden Version NGINX Plus R15 enthalten sein, deren Veröffentlichung für April 2018 geplant ist.

Server-Push, der in der HTTP/2-Spezifikation definiert ist, ermöglicht es einem Server, Ressourcen proaktiv an einen entfernten Client zu übertragen, wenn davon auszugehen ist, dass der Client diese Ressourcen in Kürze anfordern wird. Dadurch lässt sich die Anzahl der RTTs (Round Trip Time – die für eine Anfrage und Antwort benötigte Zeit) beim Laden einer Seite potenziell um eine oder mehrere RTTs reduzieren, sodass der Benutzer schneller eine Antwort erhält.

Server-Push kann verwendet werden, um einem Client vorab Stylesheets, Bilder und andere Ressourcen bereitzustellen, die er zum Rendern einer Webseite benötigt. Achten Sie darauf, nur erforderliche Ressourcen zu übertragen. Übertragen Sie keine Ressourcen, die der Client wahrscheinlich bereits im Cache gespeichert hat.

In diesem Blogbeitrag beschreibe ich:

HTTP/2 Server Push konfigurieren

Um Ressourcen zusammen mit dem Laden einer Seite zu übertragen, verwenden Sie die Direktive „http2_push“ wie folgt:

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

HTTP/2-Server-Push überprüfen

Sie können ganz einfach mit einer von zwei Methoden überprüfen, ob Server Push aktiv ist:

  • Die Entwicklertools in Ihrem Webbrowser
  • Ein HTTP/2-Client für die Befehlszeile wie etwa nghttp

Überprüfung mit den Entwicklertools (Google Chrome)

Im Folgenden wird am Beispiel von Google Chrome erläutert, wie Sie mit den Entwicklertools Ihres Webbrowsers überprüfen können, ob Server Push aktiv ist. In der Abbildung zeigt die Spalte „Initiator“ auf der Registerkarte „Network“ der Chrome-Entwicklertools, dass im Rahmen einer Anfrage für „/demo.html“ mehrere Ressourcen an den Client übertragen wurden.

Die Initiator-Spalte zeigt an, dass Ressourcen per Server-Push gesendet wurden

Überprüfung mit einem Befehlszeilen-Client (nghttp)

Zusätzlich zu den Webbrowser-Tools können Sie den Befehlszeilen-Client nghttp aus dem Projekt nghttp2.org verwenden, um zu überprüfen, ob Server Push aktiviert ist. Sie können den Befehlszeilen-Client nghttp von GitHub herunterladen oder, sofern verfügbar, das entsprechende Betriebssystempaket installieren. Verwenden Sie für Ubuntu das Paket nghttp2-client.

In der Ausgabe kennzeichnet das Sternchen (*) Ressourcen, die vom Server per Push übertragen wurden.

$ 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

Ressourcen automatisch an Clients übertragen

In vielen Situationen ist es umständlich – oder sogar unmöglich –, die Ressourcen, die per Push übertragen werden sollen, in der NGINX-Konfigurationsdatei aufzulisten. Daher unterstützt NGINX auch das Abfangen von Link-Preload-Headern, um anschließend die in diesen Headern angegebenen Ressourcen per Push zu übertragen. Um Preload zu aktivieren, fügen Sie die Direktive http2_push_preload in die Konfiguration ein:

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

Wenn NGINX beispielsweise als Proxy fungiert (für HTTP, FastCGI oder andere Arten von Datenverkehr), kann der Upstream-Server seiner Antwort einen Link-Header wie diesen hinzufügen:

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

NGINX fängt diesen Header ab und startet einen Server-Push von /style.css. Der Pfad im Link-Header muss absolut sein – relative Pfade wie ./style.css werden nicht unterstützt. Der Pfad kann optional einen Abfragestring enthalten.

Um mehrere Objekte per Push zu übertragen, können Sie mehrere Link-Header angeben oder, noch besser, alle Objekte in einer durch Kommas getrennten Liste aufführen:

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

Wenn Sie nicht möchten, dass NGINX eine vorab geladene Ressource per Push überträgt, fügen Sie dem Header den Parameter „nopush“ hinzu:

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

Wenn http2_push_preload aktiviert ist, können Sie den Preload-Server-Push auch initiieren, indem Sie den Antwort-Header in Ihrer NGINX-Konfiguration festlegen:

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

Ressourcen selektiv an Clients pushen

Die HTTP/2-Spezifikation geht nicht auf die Herausforderung ein, zu entscheiden, ob Ressourcen per Push übertragen werden sollen. Es ist eindeutig am besten, Ressourcen nur dann per Push an Clients zu übertragen, wenn Sie sowohl wissen, dass diese die Ressource wahrscheinlich benötigen, als auch, dass sie diese wahrscheinlich noch nicht im Cache haben.

Ein möglicher Ansatz besteht darin, Ressourcen nur beim ersten Besuch der Website an die Clients zu übertragen. Sie können beispielsweise prüfen, ob ein Sitzungscookie vorhanden ist, und den Link-Header nur unter bestimmten Bedingungen setzen, sodass die Ressourcen nur dann vorab geladen werden, wenn kein Sitzungscookie vorhanden ist.

Unter der Annahme, dass sich die Clients ordnungsgemäß verhalten und das Cookie bei nachfolgenden Anfragen mitsenden, überträgt NGINX die Ressourcen mit der folgenden Konfiguration nur einmal pro Browsersitzung an die Clients:

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

Messung der Auswirkungen von HTTP/2 Server Push

Um die Auswirkungen von Server Push zu messen, haben wir eine einfache Testseite erstellt: /demo.html, die auf ein separates Stylesheet verweist: /style.css. Das Stylesheet verweist wiederum auf zwei Bilder. Wir haben die Seitenladezeiten mit drei verschiedenen Konfigurationen getestet:

  • Sequenzielle GETs (keine Optimierung) – Der Browser lud Ressourcen, sobald er feststellte, dass sie benötigt wurden
  • Preload-Hinweise – Preload-Hinweise (Link-Header) waren in der ersten Antwort enthalten, um den Browser anzuweisen, die Abhängigkeiten zu laden
  • Server-Push (nur HTTP/2) – Abhängigkeiten wurden präventiv an den Browser übertragen

Drei Konfigurationen wurden getestet, um die Auswirkungen von HTTP/2 mit Server Push zu messen

Wir haben jede Konfiguration mehrfach mit HTTP, HTTPS oder HTTP/2 getestet. Die ersten beiden Konfigurationen gelten für alle drei Protokolle, Server Push hingegen nur für HTTP/2.

Das Verhalten wurde mithilfe der Chrome-Entwicklertools gemessen. Das häufigste Verhalten jeder Konfiguration wurde ausgewertet und gemittelt. Anschließend wurden die Zeiten mit der RTT der Verbindung (gemessen mithilfe von ping) korreliert, um die mechanische Wirkung der einzelnen Methoden zu veranschaulichen.

Testergebnisse zeigen, dass bei jeder Konfiguration zahlreiche Roundtrips anfielen

Einige grundlegende Beobachtungen

  • DOM geladen bezeichnet die Zeit, die benötigt wird, um eine neue Verbindung herzustellen und die Seite demo.html abzurufen. Stylesheet bezeichnet die Zeit, die zum Abrufen der CSS-Ressource benötigt wird.
  • Der Aufbau einer Verbindung über HTTP dauert 1 RTT, über HTTPS und HTTP/2 hingegen 2 RTTs.
  • Die Nutzlast der HTML- und CSS-Ressourcen ist kleiner als die maximale Übertragungseinheit (MTU), sodass ein GET-Vorgang in etwa 1 RTT abgeschlossen ist.

Interpretation der Ergebnisse: Preload-Hinweise

  • Preload-Hinweise haben nur minimale Auswirkungen auf die CSS-Ressource, da sie direkt in der HTML-Ressource referenziert wird und die HTML-Ressource schnell bereitgestellt wird. Der Browser initiiert die CSS-Anfrage, sobald die HTML-Seite bereitgestellt wurde.
  • Preload-Hinweise bewirken, dass der Download von Ressourcen, die in der CSS-Ressource deklariert sind (hier die beiden Bilder), schneller gestartet wird. Bei Verwendung von Preload-Hinweisen können die Downloads 1 RTT früher beginnen.
  • Die Nettoeinsparung durch Preload-Hinweise kann 1 RTT oder mehr betragen. Beim parallelen Herunterladen von Ressourcen muss der Browser eine oder mehrere zusätzliche Verbindungen öffnen. Die Leistung hängt davon ab, ob langsame Anfragen (mit größeren Antworten) zuerst eingeplant oder verzögert werden, während neue Verbindungen geöffnet werden. Diese unvorhersehbare Reihenfolge der Anfragen erklärt die Beschleunigung um 1 RTT bei HTTP und HTTP/2 sowie um 2 RTT bei HTTPS.

Interpretation der Ergebnisse: Server-Push

  • Server Push verbesserte die Zeit gegenüber Preload-Hinweisen um eine weitere 1 RTT. Die Push-„Antworten“ wurden gleichzeitig mit der Antwort auf die erste Anfrage initiiert, während bei den Antworten auf die Preload-Hinweise eine Verzögerung von 1 RTT entstand – 0,5 RTT für die Antwort auf die erste Anfrage plus 0,5 RTT für die Preload-GET-Anfrage.

Testhinweise

  • Für jede Konfiguration wurden mehrere Testläufe durchgeführt. Jeder Lauf begann mit einem leeren Browser-Cache und ohne bestehende Keepalive-Verbindungen zum NGINX-Server. Die NGINX-Direktiven „keepalive_timeout“ und „http2_idle_timeout“ wurden verwendet, um Keepalive-Verbindungen schnell zu schließen.
  • Derzeit scheint es nicht möglich zu sein, Schriftartressourcen per Push an Chrome zu übertragen, möglicherweise aufgrund einer bekannten Komplikation. Chrome fordert eine Schriftartressource ausdrücklich an, selbst wenn sie bereits per Push übertragen wurde.
  • Es wurde darauf geachtet, die Browser-Caches vor jedem Test explizit zu leeren, und alle Inhalte wurden mit abgelaufenen Cache-Control-Headern ausgeliefert.
  • Chrome speichert vorab geladene Ressourcen im Cache. Diese zwischengespeicherten Ressourcen werden beim Deaktivieren des Cachings nicht immer ignoriert und auch durch das explizite Leeren des Browser-Caches nicht immer gelöscht. (In Chrome können Sie das Caching unter anderem deaktivieren, indem Sie auf der Registerkarte Cache deaktivieren im Bereich Netzwerk der Entwicklertools das entsprechende Kontrollkästchen aktivieren. Um den Browser-Cache zu leeren, können Sie bei geöffneten Entwicklertools mit der rechten Maustaste auf die Schaltfläche zum Aktualisieren des Browsers klicken und Cache leeren und vollständig aktualisieren auswählen.)
  • Bei einigen Versuchen, Inhalte vorab zu laden, konnte Chrome zwischengespeicherte Kopien nicht erneut validieren und lud die Ressource anschließend regulär herunter. Diese Versuche wurden bei den Messungen nicht berücksichtigt.
  • Chrome fügt allen neuen SSL-Verbindungen, die zuvor akzeptierte selbstsignierte Zertifikate verwenden, eine unnötige Verzögerung von 2 RTT hinzu. Der Test wurde mit von einer Zertifizierungsstelle signierten Let’s-Encrypt-Zertifikaten durchgeführt, um diese Verzögerung von 2 RTT zu vermeiden.
  • DNS-Verzögerungen wurden durch Bearbeiten der lokalen Datei „/etc/hosts“ behoben.
  • Bei diesen einfachen Tests wurde nicht versucht, die Auswirkungen von Server Push zu messen, wenn der Client bereits über eine zwischengespeicherte Kopie der Ressource verfügt. Vor jedem Test wurden alle Caches geleert, und die meisten Push-Vorgänge waren zu schnell, um sie abzubrechen.

Fazit

Dieser Test wurde bewusst einfach gehalten, um die Funktionsweise von Preload-Hinweisen und Server-Push zu verdeutlichen. In einfachen Situationen erzielt Server-Push gegenüber Preload-Hinweisen eine Verbesserung um 1 RTT und eine noch größere Verbesserung gegenüber nicht optimierten, sequenziellen GET-Anfragen und der Erkennung abhängiger Ressourcen.

Realistischere Anwendungsfälle weisen deutlich mehr Variablen auf: mehrere abhängige Ressourcen, mehrere Quellen und sogar die Möglichkeit, Bandbreite zu verschwenden, indem Ressourcen übertragen werden, die bereits im Cache vorhanden sind oder nicht sofort benötigt werden. Auch Unterschiede zwischen Browsern wirken sich auf die Leistung aus. Ihre Ergebnisse werden mit Sicherheit von denen dieses einfachen Tests abweichen.

Das Chrome-Team hat beispielsweise einige detaillierte Empfehlungen dazu veröffentlicht, wann Server Push eingesetzt werden sollte, und Messungen auf komplexeren Websites durchgeführt, um die Auswirkungen von fehlender Optimierung, Preload-Hinweisen und Server Push über HTTP/2 zu vergleichen. Der Bericht „Faustregeln für HTTP/2 Push“ ist für alle lesenswert, die den Einsatz von HTTP/2 Server Push in einer Produktivumgebung erwägen.

Die pragmatische Schlussfolgerung lautet: Wenn Sie im Voraus ermitteln können, welche Ressourcen benötigt werden, ist es durchaus von Vorteil, wenn vorgelagerte Server einen Preload-Hinweis senden. Der zusätzliche Nutzen, diese Ressourcen per Server Push zu übertragen, ist zwar gering, aber messbar. Allerdings kann dies unter Umständen zu unnötigem Bandbreitenverbrauch und Verzögerungen bei benötigten Ressourcen führen. Sie sollten daher jede Server-Push-Konfiguration sorgfältig testen und überwachen.

Anhang: Wie funktioniert HTTP/2-Push?

Die folgenden Informationen basieren zum Teil auf den Recherchen in Jake Archibalds sehr ausführlichem Blogbeitrag „HTTP/2-Push ist schwieriger, als ich dachte“.

HTTP/2 Server Push wird üblicherweise verwendet, um abhängige Ressourcen vorab zu senden, wenn der Client eine Ressource anfordert. Fordert ein Client beispielsweise eine Webseite an, kann der Server zugehörige Stylesheets, Schriftarten und Bilder an den Client übertragen.

Wenn ein Client eine HTTP/2-Verbindung herstellt, kann der Server über diese Verbindung eine oder mehrere Server-Push-Antworten initiieren. Mit diesen Pushes werden Ressourcen gesendet, die der Client nicht ausdrücklich angefordert hat.

Der Client kann einen Push entweder ablehnen (indem er einen RST_STREAM-Frame sendet) oder annehmen. Der Client speichert die per Push übertragenen Inhalte in einem lokalen „Push-Cache“, der der HTTP/2-Verbindung zugeordnet ist.

Wenn der Client später über eine bestehende HTTP/2-Verbindung eine Ressource anfordert, prüft er im Push-Cache der Verbindung, ob bereits eine vollständige oder noch laufende Antwort auf die Anfrage vorliegt. Er verwendet bevorzugt die zwischengespeicherte Ressource, anstatt eine neue HTTP/2-Anfrage für die Ressource zu stellen.

Jede per Push übertragene Ressource verbleibt im verbindungsspezifischen Push-Cache, bis sie (a) verwendet oder (b) die HTTP/2-Verbindung geschlossen wird:

  1. Wenn die Ressource verwendet wird, erstellt der Client eine Kopie und der Eintrag im Push-Cache wird gelöscht. Wenn die Ressource cachefähig ist, kann der Client seine Kopie anschließend im HTTP-Seiten-Cache speichern.
  2. Wenn die HTTP/2-Verbindung aus irgendeinem Grund geschlossen wird, wird ihr lokaler Push-Cache gelöscht.

Dies hat mehrere Auswirkungen:

  • Inhalte im HTTP-Seitencache des Browsers werden gegenüber Inhalten im Push-Cache bevorzugt verwendet, selbst wenn die per Push übertragenen Inhalte aktueller sind.
  • HTTP/2-Verbindungen können von verschiedenen Seitenaufrufen gemeinsam genutzt werden. Eine Ressource, die infolge eines Seitenaufrufs per Push übertragen wurde, kann verwendet werden, wenn sie bei einem anderen Seitenaufruf angefordert wird.
  • Anfragen mit Anmeldedaten verwenden andere HTTP/2-Verbindungen als Anfragen ohne Anmeldedaten. Beispielsweise wird eine Ressource, die mit einer ursprungsübergreifenden Anfrage (mit Anmeldedaten) übertragen wird, möglicherweise nicht gefunden, wenn der Browser die Ressource ohne Anmeldedaten anfordert.

Eine wesentlich ausführlichere Liste der Probleme finden Sie in Jake Archibalds Blogbeitrag „HTTP/2-Push ist schwieriger als gedacht“.

HTTP/2 Server Push ist eine interessante Funktion. Testen Sie Ihre HTTP/2-Server-Push-Konfiguration unbedingt gründlich und seien Sie darauf vorbereitet, auf Preload-Hinweise zurückzugreifen, wenn diese ein besser vorhersehbares, Cache-bewusstes Verhalten ermöglichen.


Teilen

Über den Autor

Owen Garrett
Owen GarrettSenior Director, Produktmanagement

Weitere Blogbeiträge von Owen Garrett

Ähnliche Blogbeiträge

Die Zukunft von NGINX: Zurück zu unseren Open-Source-Wurzeln
NGINX | 23.08.2022

Die Zukunft von NGINX: Zurück zu unseren Open-Source-Wurzeln

Beim NGINX Sprint 2022 besinnen wir uns auf unsere Open-Source-Wurzeln. Erfahren Sie mehr über die Initiativen, die wir im kommenden Jahr verfolgen, und teilen Sie uns mit, was Sie sich wünschen.

Ein Leitfaden zur Auswahl eines Ingress-Controllers, Teil 1: Ermitteln Sie Ihre Anforderungen
NGINX | 08.09.2021

Ein Leitfaden zur Auswahl eines Ingress-Controllers, Teil 1: Ermitteln Sie Ihre Anforderungen

Ein Ingress-Controller kann eines der leistungsstärksten Tools in Ihrem Kubernetes-Stack sein. Erfahren Sie, wie Sie Ihre Ingress-Anforderungen ermitteln, um die beste Option auszuwählen.

Bereitstellung des NGINX Ingress Controllers auf Amazon EKS: So haben wir getestet
NGINX | 16.08.2021

Bereitstellung des NGINX Ingress Controllers auf Amazon EKS: So haben wir getestet

Wir haben kürzlich den Lösungsleitfaden zum NGINX Ingress Controller um Empfehlungen zur Dimensionierung für Amazon Elastic Kubernetes Service (EKS) ergänzt. In diesem Blogbeitrag erläutern wir, wie wir diese Empfehlungen erarbeitet haben, und stellen Ihnen alle Informationen zur Verfügung, die Sie benötigen, um selbst ähnliche Tests durchzuführen.

Protokollierung des Datenverkehrs zwischen NGINX und Upstream-Servern bei CDN77
NGINX | 12.12.2018

Protokollierung des Datenverkehrs zwischen NGINX und Upstream-Servern bei CDN77

Bei der Fehlersuche bei Datenverkehrsproblemen zwischen NGINX und Upstream-Servern konnte CDN77 die benötigten Daten nur aus dem Fehlerprotokoll mit aktivierter Debug-Ausgabe beziehen, was die Leistung beeinträchtigt und riesige Dateien erzeugt. Daher entwickelte das Team ein neues benutzerdefiniertes Modul, das ausschließlich die benötigten Daten protokolliert.

Ankündigung von NGINX Plus R16
NGINX | 05.09.2018

Ankündigung von NGINX Plus R16

NGINX Plus R16 ist eine einheitliche, elastische Ingress- und Egress-Schicht für Anwendungen, die die Funktionen eines Load Balancers, API-Gateways und einer WAF vereint und neue Features wie clusterfähige Ratenbegrenzung, Schlüssel-Wert-Speicher, „Random with Two Choices“, verbessertes UDP-Load-Balancing und vieles mehr bietet.

5 Gründe für den Umstieg auf softwarebasiertes Load Balancing
NGINX | 13.10.2016

5 Gründe für den Umstieg auf softwarebasiertes Load Balancing

Entdecken Sie fünf Gründe für den Wechsel zu einem softwarebasierten Load Balancer wie NGINX Plus, darunter Kosteneinsparungen, die Einführung von DevOps und eine einheitliche Lösung für alle Umgebungen.