Einführung in HTTP/2

Mit HTTP/2 werden unsere Anwendungen schneller, einfacher und robuster – eine seltene Kombination –, da wir viele der bisher in unseren Anwendungen durchgeführten HTTP/1.1-Umgehungen rückgängig machen und diese Probleme in der Transportschicht selbst beheben können. Darüber hinaus eröffnen sich auch ganz neue Möglichkeiten, unsere Anwendungen zu optimieren und die Leistung zu verbessern.

Das Hauptziel von HTTP/2 besteht darin, die Latenz zu reduzieren, indem ein vollständiges Multiplexing von Anfragen und Antworten aktiviert wird, der Protokoll-Overhead durch eine effiziente Komprimierung von HTTP-Header-Feldern zu minimieren und die Unterstützung für die Priorisierung von Anfragen und den Server-Push hinzugefügt wird. Für die Implementierung dieser Anforderungen gibt es eine Vielzahl weiterer Protokollverbesserungen, z. B. neue Ablaufsteuerung, Fehlerbehandlung und Upgradeverfahren. Dies sind jedoch die wichtigsten Funktionen, die jeder Webentwickler verstehen und in seinen Anwendungen nutzen sollte.

HTTP/2 ändert die Anwendungssemantik von HTTP in keiner Weise. Alle wichtigen Konzepte wie HTTP-Methoden, Statuscodes, URIs und Headerfelder bleiben unverändert. Stattdessen ändert HTTP/2 die Art und Weise, wie die Daten formatiert (Framed) und zwischen dem Client und dem Server übertragen werden. Beide verwalten den gesamten Prozess, sodass die Komplexität unserer Anwendungen auf der neuen Framing-Ebene verschwendet wird. Dadurch können alle vorhandenen Anwendungen unverändert bereitgestellt werden.

Warum nicht HTTP/1.2?

Um die von der HTTP-Arbeitsgruppe festgelegten Leistungsziele zu erreichen, führt HTTP/2 eine neue Binär-Framing-Ebene ein, die nicht abwärtskompatibel mit früheren HTTP/1.x-Servern und -Clients ist – daher die Erhöhung der Hauptprotokollversion auf HTTP/2.

Wenn Sie jedoch keinen Webserver (oder einen benutzerdefinierten Client) unter Verwendung von Roh-TCP-Sockets implementieren, werden Sie keinen Unterschied feststellen: Das gesamte neue, Low-Level-Framing wird von dem Client und dem Server für Sie ausgeführt. Die einzigen beobachtbaren Unterschiede sind verbesserte Leistung und Verfügbarkeit neuer Funktionen wie Anfragepriorisierung, Ablaufsteuerung und Server-Push.

Ein kurzer Überblick über SPDY und HTTP/2

SPDY war ein experimentelles Protokoll, das von Google entwickelt und Mitte 2009 angekündigt wurde. Ziel war es, die Ladelatenz von Webseiten durch die Behebung einiger bekannter Leistungseinschränkungen von HTTP/1.1 zu verringern. Konkret wurden die skizzierten Projektziele wie folgt festgelegt:

  • Eine Reduzierung der Seitenladezeit um 50% anstreben.
  • Sie müssen keine Änderungen an Inhalten seitens der Autoren vornehmen.
  • Minimieren Sie die Komplexität der Bereitstellung und vermeiden Sie Änderungen an der Netzwerkinfrastruktur.
  • Entwicklung dieses neuen Protokolls in Zusammenarbeit mit der Open-Source-Community.
  • Es werden echte Leistungsdaten erfasst, um das experimentelle Protokoll zu entwerten.

Kurz nach der ersten Ankündigung teilten Mike Belshe und Roberto Peon, beide Softwareentwickler bei Google, erste Ergebnisse, eine Dokumentation und einen Quellcode für die experimentelle Implementierung des neuen SPDY-Protokolls:

Bisher haben wir SPDY nur unter Laborbedingungen getestet. Die ersten Ergebnisse sind sehr ermutigend: Wenn wir die 25 beliebtesten Websites über simulierte Heimnetzwerkverbindungen herunterladen, sehen wir eine deutliche Leistungsverbesserung – die Seiten wurden um bis zu 55% schneller geladen. (Chromium-Blog)

2012 wurde das neue experimentelle Protokoll in Chrome, Firefox und Opera unterstützt und eine schnell wachsende Anzahl von Websites, sowohl große Websites (z. B. Google, Twitter, Facebook) als auch kleine, stellten SPDY in ihrer Infrastruktur bereit. Mit der zunehmenden Akzeptanz in der Branche war SPDY auf dem richtigen Weg, ein De-facto-Standard zu werden.

In Anbetracht dieses Trends begann die HTTP-Arbeitsgruppe (HTTP-WG) einen neuen Versuch, die Erkenntnisse aus SPDY zu nutzen, sie weiterzuentwickeln und zu verbessern und einen offiziellen „HTTP/2“-Standard zu liefern. Es wurde eine neue Charta verfasst und ein offener Aufruf zu HTTP/2-Angeboten wurde gestellt. Nach vielen Diskussionen in der Arbeitsgruppe wurde die SPDY-Spezifikation als Ausgangspunkt für das neue HTTP/2-Protokoll übernommen.

In den nächsten Jahren entwickelten sich SPDY und HTTP/2 parallel weiter, wobei SPDY als experimenteller Zweig zum Testen neuer Funktionen und Vorschläge für den HTTP/2-Standard agierte. Was auf dem Papier gut aussieht, funktioniert in der Praxis möglicherweise nicht und umgekehrt. SPDY bot eine Möglichkeit an, jeden Vorschlag zu testen und zu bewerten, bevor er in den HTTP/2-Standard aufgenommen wurde. Dieser Prozess dauerte drei Jahre und führte zu über einem Dutzend Zwischenentwürfen:

  • März 2012: Aufruf zur Angebotsanforderung für HTTP/2
  • November 2012: Erster Entwurf von HTTP/2 (basierend auf SPDY)
  • August 2014: HTTP/2 draft-17 und HPACK draft-12 veröffentlicht
  • August 2014: Letzte Aufforderung der Arbeitsgruppe zu HTTP/2
  • Februar 2015: IESG hat HTTP/2- und HPACK-Entwürfe genehmigt
  • Mai 2015: Veröffentlichung von RFC 7540 (HTTP/2) und RFC 7541 (HPACK)

Anfang 2015 hat das IESG den neuen HTTP/2-Standard zur Veröffentlichung geprüft und genehmigt. Kurz darauf gab das Google Chrome-Team die geplante Einstellung der SPDY- und NPN-Erweiterung für TLS bekannt:

Bei den primären Änderungen bei HTTP/2 im Vergleich zu HTTP/1.1 liegt der Schwerpunkt auf einer verbesserten Leistung. Einige wichtige Features wie Multiplexing, Header-Komprimierung, Priorisierung und Protokollaushandlung haben sich aus der Arbeit in einem früheren offenen, aber nicht standardmäßigen Protokoll namens SPDY entwickelt. SPDY wird seit Chrome 6 von Chrome unterstützt. Da HTTP/2 jedoch größtenteils die Vorteile bietet, sollten wir uns verabschieden. Wir planen, die Unterstützung für SPDY Anfang 2016 einzustellen und gleichzeitig auch die Unterstützung für die TLS-Erweiterung NPN durch ALPN in Chrome zu ersetzen. Serverentwicklern wird dringend empfohlen, zu HTTP/2 und ALPN zu wechseln.

Wir freuen uns, dass wir zum offenen Standardprozess beigetragen haben, der zu HTTP/2 geführt hat, und hoffen auf eine breite Akzeptanz angesichts des großen Interesses der Branche an Standardisierung und Implementierung. (Chromium-Blog)

Durch die Zusammenführung von SPDY- und HTTP/2-fähigen Server-, Browser- und Websiteentwicklern konnten sie bereits während der Entwicklung des neuen Protokolls praktische Erfahrungen sammeln. Daher gehört der HTTP/2-Standard von Anfang an zu den besten und am häufigsten getesteten Standards. Als HTTP/2 vom IESG genehmigt wurde, gab es Dutzende gründlich getesteter und produktionsfertiger Client- und Serverimplementierungen. Nur wenige Wochen nach der Genehmigung des endgültigen Protokolls kamen viele Nutzer bereits in den Genuss der Vorteile, da mehrere gängige Browser (und viele Websites) vollständigen HTTP/2-Support bereitstellten.

Design- und technische Ziele

Frühere Versionen des HTTP-Protokolls wurden bewusst für eine einfache Implementierung entwickelt: HTTP/0.9 war ein einzeiliges Protokoll zum Bootstrapping des World Wide Web; HTTP/1.0 dokumentierte die beliebten Erweiterungen von HTTP/0.9 in einem Informationsstandard; HTTP/1.1 führte einen offiziellen IETF-Standard ein; siehe Kurze Geschichte von HTTP. Damit hat HTTP/0.9-1.x genau das geliefert, was es sich vorgenommen hatte: HTTP ist eines der am weitesten verbreiteten Anwendungsprotokolle im Internet.

Leider verursacht die einfache Implementierung auch Kosten für die Anwendungsleistung: HTTP/1.x-Clients müssen mehrere Verbindungen verwenden, um Nebenläufigkeit zu erreichen und die Latenz zu reduzieren. HTTP/1.x komprimiert keine Anfrage- und Antwortheader, was unnötigen Netzwerkverkehr verursacht. HTTP/1.x lässt keine effektive Ressourcenpriorisierung zu, was zu einer schlechten Nutzung der zugrunde liegenden TCP-Verbindung führt.

Diese Einschränkungen waren nicht faszinierend, aber als die Webanwendungen in ihrem Umfang, ihrer Komplexität und ihrer Bedeutung im Alltag immer weiter zunahmen, stellten sie sowohl den Entwicklern als auch den Nutzern des Webs eine zunehmende Last auf, die genau mit HTTP/2 behoben werden sollte:

HTTP/2 ermöglicht eine effizientere Nutzung von Netzwerkressourcen und eine reduzierte Wahrnehmung von Latenz durch die Header-Feld-Komprimierung und den gleichzeitigen Austausch über dieselbe Verbindung. Insbesondere ermöglicht es die Verschränkung von Anfrage- und Antwortnachrichten in derselben Verbindung und nutzt eine effiziente Codierung für HTTP-Header-Felder. Außerdem können Anfragen priorisiert werden, sodass wichtigere Anfragen schneller abgeschlossen werden, was die Leistung weiter verbessert.

Das resultierende Protokoll ist netzwerkfreundlicher, da im Vergleich zu HTTP/1.x weniger TCP-Verbindungen verwendet werden können. Dies bedeutet weniger Konkurrenz mit anderen Abläufen und langlebigere Verbindungen, was wiederum zu einer besseren Nutzung der verfügbaren Netzwerkkapazität führt. Schließlich ermöglicht HTTP/2 auch eine effizientere Verarbeitung von Nachrichten durch binäres Nachrichten-Framing. (HyperText Transfer Protocol Version 2, Entwurf 17)

Beachten Sie, dass HTTP/2 die vorherigen HTTP-Standards nicht ersetzt, sondern erweitert. Die Anwendungssemantik von HTTP ist gleich und es wurden keine Änderungen an den angebotenen Funktionen oder Kernkonzepten wie HTTP-Methoden, Statuscodes, URIs und Headerfeldern vorgenommen. Diese Änderungen fielen explizit nicht in den Bereich der HTTP/2-Initiative. Die übergeordnete API bleibt zwar gleich, es ist jedoch wichtig zu verstehen, wie die Änderungen auf niedriger Ebene mit den Leistungseinschränkungen der vorherigen Protokolle umgehen. Sehen wir uns die Binär-Framing- Ebene und ihre Funktionen kurz an.

Binary-Framing-Ebene

Herzstück aller Leistungsverbesserungen von HTTP/2 ist die neue Binär-Framing-Ebene, die vorgibt, wie die HTTP-Nachrichten gekapselt und zwischen dem Client und dem Server übertragen werden.

HTTP/2-Binär-Framing-Schicht

Die "Ebene" bezieht sich auf eine Designentscheidung, bei der ein neuer optimierter Codierungsmechanismus zwischen der Socket-Schnittstelle und der höheren HTTP-API für unsere Anwendungen eingeführt wird: Die HTTP-Semantik, z. B. Verben, Methoden und Header, bleibt unberührt, aber die Art und Weise, wie sie während der Übertragung codiert werden, ist anders. Im Gegensatz zum durch Zeilenumbruch getrennten Klartext-HTTP/1.x-Protokoll wird die gesamte HTTP/2-Kommunikation in kleinere Nachrichten und Frames aufgeteilt, die jeweils im Binärformat codiert sind.

Daher müssen sowohl der Client als auch der Server den neuen binären Codierungsmechanismus verwenden, um einander zu verstehen: Ein HTTP/1.x-Client kann einen reinen HTTP/2-Server nicht verstehen und umgekehrt. Glücklicherweise sind sich unsere Anwendungen all dieser Änderungen nicht bewusst, da der Client und der Server die notwendigen Framing-Aufgaben für uns ausführen.

Streams, Nachrichten und Frames

Durch die Einführung des neuen binären Framing-Mechanismus ändert sich der Datenaustausch zwischen Client und Server. Machen wir uns zur Beschreibung dieses Prozesses mit der HTTP/2-Terminologie vertraut:

  • Stream: Ein bidirektionaler Fluss von Byte innerhalb einer hergestellten Verbindung, die eine oder mehrere Nachrichten übertragen kann.
  • Nachricht: Eine vollständige Abfolge von Frames, die einer logischen Anfrage- oder Antwortnachricht zugeordnet sind.
  • Frame: Die kleinste Kommunikationseinheit in HTTP/2, die jeweils einen Frame-Header enthält, der mindestens den Stream identifiziert, zu dem der Frame gehört.

Die Beziehung zwischen diesen Begriffen lässt sich wie folgt zusammenfassen:

  • Die gesamte Kommunikation erfolgt über eine einzelne TCP-Verbindung, die eine beliebige Anzahl bidirektionaler Streams übertragen kann.
  • Jeder Stream hat eine eindeutige Kennung und optionale Prioritätsinformationen, die zum Übertragen bidirektionaler Nachrichten verwendet werden.
  • Jede Nachricht ist eine logische HTTP-Nachricht, z. B. eine Anfrage oder Antwort, die aus einem oder mehreren Frames besteht.
  • Der Frame ist die kleinste Kommunikationseinheit, die einen bestimmten Datentyp enthält, z.B. HTTP-Header, Nachrichtennutzlast usw. Frames aus verschiedenen Streams können überlappen und dann über die eingebettete Stream-ID im Header jedes Frames wieder zusammengesetzt werden.

HTTP/2-Streams, -Nachrichten und -Frames

Kurz gesagt schlüsselt HTTP/2 die HTTP-Protokollkommunikation in einen Austausch von binärcodierten Frames auf, die dann Nachrichten zugeordnet werden, die zu einem bestimmten Stream gehören und die alle innerhalb einer einzelnen TCP-Verbindung Multiplexing sind. Dies ist die Grundlage, auf der alle anderen Funktionen und Leistungsoptimierungen des HTTP/2-Protokolls aktiviert werden.

Multiplexing von Anfragen und Antworten

Wenn der Client mit HTTP/1.x mehrere parallele Anfragen stellen möchte, um die Leistung zu verbessern, müssen mehrere TCP-Verbindungen verwendet werden (siehe Mehrere TCP-Verbindungen verwenden). Dieses Verhalten ist eine direkte Konsequenz des HTTP/1.x-Bereitstellungsmodells, das dafür sorgt, dass pro Verbindung nur eine Antwort gleichzeitig zugestellt werden kann (Antwortwarteschlange). Schlimmer noch, dies führt auch zu Head-of-Line-Blockierung und ineffizienter Nutzung der zugrunde liegenden TCP-Verbindung.

Durch die neue Binär-Framing-Schicht in HTTP/2 werden diese Einschränkungen beseitigt und ein vollständiges Multiplexing von Anfragen und Antworten ermöglicht. Dabei können Client und Server eine HTTP-Nachricht in unabhängige Frames zerlegen, sie verschachteln und dann am anderen Ende wieder zusammensetzen.

Multiplexing von HTTP/2-Anfragen und -Antworten innerhalb einer gemeinsam genutzten Verbindung

Im Snapshot werden mehrere Streams innerhalb derselben Verbindung erfasst. Der Client überträgt einen DATA-Frame (Stream 5) an den Server, während der Server für die Streams 1 und 3 eine verschachtelte Sequenz von Frames an den Client überträgt. Daher laufen drei parallele Streams.

Die Möglichkeit, eine HTTP-Nachricht in unabhängige Frames zu zerlegen, sie zu verschachteln und am anderen Ende wieder zusammenzufügen, ist die wichtigste Verbesserung von HTTP/2. Tatsächlich bringt sie zahlreiche Leistungsvorteile über den gesamten Stapel aller Webtechnologien mit sich, sodass wir:

  • Mehrere Anfragen gleichzeitig verschachteln, ohne sie zu blockieren.
  • Sie können mehrere Antworten parallel verschachteln, ohne eine davon zu blockieren.
  • Verwenden Sie eine einzelne Verbindung, um mehrere Anfragen und Antworten gleichzeitig zu senden.
  • Entfernen Sie unnötige HTTP/1.x-Problemumgehungen (siehe Optimierung für HTTP/1.x), z. B. verkettete Dateien, Bild-Sprites und Domain-Fragmentierung.
  • Sorgen Sie für kürzere Seitenladezeiten, indem Sie unnötige Latenz beseitigen und die Auslastung der verfügbaren Netzwerkkapazität verbessern.
  • Und vieles mehr...

Die neue Schicht für binäres Framing in HTTP/2 löst das Problem mit der Head-of-Line-Blockierung in HTTP/1.x und macht mehrere Verbindungen für die parallele Verarbeitung und Zustellung von Anfragen und Antworten überflüssig. Dadurch werden unsere Anwendungen schneller, einfacher und kostengünstiger bereitgestellt.

Stream-Priorisierung

Wenn eine HTTP-Nachricht in viele einzelne Frames aufgeteilt werden kann und Frames aus mehreren Streams gleichzeitig Multiplex sind, ist die Reihenfolge, in der die Frames verschachtelt und sowohl vom Client als auch vom Server übertragen werden, ein entscheidender Faktor für die Leistung. Um dies zu vereinfachen, lässt der HTTP/2-Standard jedem Stream eine zugehörige Gewichtung und Abhängigkeit zu:

  • Jedem Stream kann eine ganzzahlige Gewichtung zwischen 1 und 256 zugewiesen werden.
  • Jeder Stream kann eine explizite Abhängigkeit von einem anderen Stream haben.

Die Kombination aus Streamabhängigkeiten und Gewichtungen ermöglicht es dem Client, einen "Priorisierungsbaum" zu erstellen und zu kommunizieren, der ausdrückt, wie er Antworten bevorzugt erhalten möchte. Der Server kann diese Informationen wiederum verwenden, um die Streamverarbeitung zu priorisieren. Dazu wird die Zuweisung von CPU, Arbeitsspeicher und anderen Ressourcen gesteuert. Sobald die Antwortdaten verfügbar sind, kann die Bandbreite zugewiesen werden, um eine optimale Übermittlung von Antworten mit hoher Priorität an den Client zu ermöglichen.

Abhängigkeiten und Gewichtungen für HTTP/2-Streams

Eine Stream-Abhängigkeit in HTTP/2 wird deklariert, indem auf die eindeutige ID eines anderen Streams als deren übergeordnetes Element verwiesen wird. Wird die Kennung weggelassen, gilt der Stream als abhängig von dem "Stammstream". Das Deklarieren einer Streamabhängigkeit weist darauf hin, dass dem übergeordneten Stream nach Möglichkeit Ressourcen vor seinen Abhängigkeiten zugewiesen werden sollten. Mit anderen Worten: „Bitte bearbeite und liefere Antwort D vor Antwort C.“

Streams mit demselben übergeordneten Element (mit anderen Worten, gleichgeordnete Streams) sollten Ressourcen im Verhältnis zu ihrer Gewichtung zugewiesen werden. Wenn Stream A beispielsweise eine Gewichtung von 12 und sein gleichgeordnetes Element B eine Gewichtung von 4 hat, müssen Sie den Anteil der Ressourcen bestimmen, die jeder dieser Streams erhalten soll:

  1. Summe aller Gewichtungen: 4 + 12 = 16
  2. Jede Stream-Gewichtung durch die Gesamtgewichtung teilen: A = 12/16, B = 4/16

Daher sollte Stream A drei Viertel und Stream B ein Viertel der verfügbaren Ressourcen erhalten; Stream B sollte ein Drittel der für Stream A zugewiesenen Ressourcen erhalten. Sehen wir uns in der Abbildung oben weitere praktische Beispiele an. Von links nach rechts:

  1. Weder Stream A noch B geben eine übergeordnete Abhängigkeit an und sind vom impliziten "Stammstream" abhängig. A hat eine Gewichtung von 12 und B hat eine Gewichtung von 4. Auf Basis proportionaler Gewichtungen sollte daher Stream B ein Drittel der Ressourcen erhalten, die Stream A zugewiesen ist.
  2. Stream D ist vom Stammstream abhängig; C ist von D abhängig. Daher sollte D vor C die vollständige Zuweisung von Ressourcen erhalten. Die Gewichtungen sind bedeutsam, da die C-Abhängigkeit eine stärkere Präferenz vermittelt.
  3. Stream D sollte alle Ressourcen vor C erhalten; C sollte die vollständige Zuweisung der Ressourcen vor A und B erhalten; Stream B sollte ein Drittel der Ressourcen erhalten, die Stream A zugewiesen ist.
  4. Stream D sollte die vollständige Zuweisung von Ressourcen vor E und C erhalten; E und C sollten die gleiche Zuweisung vor A und B erhalten; A und B sollten eine proportionale Zuweisung basierend auf ihrer Gewichtung erhalten.

Wie die obigen Beispiele veranschaulichen, bietet die Kombination von Streamabhängigkeiten und Gewichtungen eine aussagekräftige Sprache für die Ressourcenpriorisierung. Dies ist eine wichtige Funktion zur Verbesserung der Browserleistung, wenn viele Ressourcentypen mit unterschiedlichen Abhängigkeiten und Gewichtungen vorhanden sind. Und was noch besser ist: Mit dem HTTP/2-Protokoll kann der Client diese Einstellungen jederzeit aktualisieren, was weitere Optimierungen im Browser ermöglicht. Mit anderen Worten, wir können Abhängigkeiten ändern und Gewichtungen als Reaktion auf Nutzerinteraktionen und andere Signale neu zuordnen.

Eine Verbindung pro Ursprung

Mit dem neuen Binär-Framing-Mechanismus benötigt HTTP/2 nicht mehr mehrere TCP-Verbindungen zu parallelen Multiplex-Streams. Jeder Stream wird in viele Frames aufgeteilt, die verschränkt und priorisiert werden können. Dadurch sind alle HTTP/2-Verbindungen dauerhaft und es ist nur eine Verbindung pro Ursprung erforderlich, was zahlreiche Leistungsvorteile bietet.

Sowohl bei SPDY als auch bei HTTP/2 ist das Hauptfeature ein willkürliches Multiplexing auf einem einzelnen Kanal mit Überlastungskontrolle. Es ist erstaunt, wie wichtig das ist und wie gut es funktioniert. Ein großartiger Messwert in diesem Bereich ist der Anteil der erstellten Verbindungen, die nur eine einzige HTTP-Transaktion ausführen (und dadurch den gesamten Aufwand übernehmen). Bei HTTP/1 führen 74% unserer aktiven Verbindungen nur eine einzige Transaktion durch – dauerhafte Verbindungen sind einfach nicht so nützlich, wie wir es alle wünschen. Bei HTTP/2 sinkt diese Zahl jedoch auf 25%. Das ist ein großer Gewinn bei der Reduzierung des Aufwands. (HTTP/2 ist in Firefox „Live“, Patrick McManus)

Die meisten HTTP-Übertragungen sind kurz und stoßweise, während TCP für langlebige Bulk-Datenübertragungen optimiert ist. Durch die Wiederverwendung derselben Verbindung kann HTTP/2 jede TCP-Verbindung effizienter nutzen und den Protokoll-Overhead insgesamt erheblich reduzieren. Außerdem reduziert die Verwendung von weniger Verbindungen den Speicher- und Verarbeitungsbedarf entlang des gesamten Verbindungspfads (d. h. Client, Vermittler und Ursprungsserver). Dies reduziert die Gesamtbetriebskosten und verbessert die Netzwerkauslastung und -kapazität. Die Umstellung auf HTTP/2 sollte daher nicht nur die Netzwerklatenz reduzieren, sondern auch den Durchsatz verbessern und die Betriebskosten senken.

Ablaufsteuerung

Die Ablaufsteuerung ist ein Mechanismus, der verhindert, dass der Sender den Empfänger mit Daten überlastet, die er möglicherweise nicht benötigt oder verarbeiten kann: Der Empfänger ist möglicherweise beschäftigt, unter hoher Auslastung oder ist nur bereit, einem bestimmten Stream eine feste Menge an Ressourcen zuzuweisen. Angenommen, der Client hat einen großen Videostream mit hoher Priorität angefordert, aber der Nutzer hat das Video pausiert und der Client möchte nun die Auslieferung vom Server anhalten oder drosseln, um das Abrufen und Zwischenspeichern unnötiger Daten zu vermeiden. Alternativ kann ein Proxyserver schnelle Downstream- und langsame Upstream-Verbindungen haben und in ähnlicher Weise steuern, wie schnell der Downstream Daten an die Upstream-Geschwindigkeit liefert, um die Ressourcennutzung zu steuern und so weiter.

Erinnern Sie die obigen Anforderungen an die TCP-Ablaufsteuerung? Dies sollten sie tun, da das Problem praktisch identisch ist (siehe Ablaufsteuerung). Da die HTTP/2-Streams jedoch innerhalb einer einzelnen TCP-Verbindung Multiplexing sind, ist die TCP-Ablaufsteuerung nicht detailliert genug und stellt nicht die erforderlichen APIs auf Anwendungsebene bereit, um die Bereitstellung einzelner Streams zu regulieren. HTTP/2 bietet dazu eine Reihe einfacher Bausteine, mit denen der Client und der Server ihre eigene Ablaufsteuerung auf Stream- und Verbindungsebene implementieren können:

  • Die Ablaufsteuerung ist richtungsabhängig. Jeder Empfänger kann eine beliebige Fenstergröße für jeden Stream und die gesamte Verbindung einstellen.
  • Die Ablaufsteuerung basiert auf Guthaben. Jeder Empfänger bietet seine anfängliche Verbindung und sein Startkontrollfenster (in Byte) an. Diese werden reduziert, wenn der Absender einen DATA-Frame ausgibt und über einen vom Empfänger gesendeten WINDOW_UPDATE-Frame erhöht wird.
  • Ablaufsteuerung kann nicht deaktiviert werden. Wenn die HTTP/2-Verbindung hergestellt ist, tauschen Client und Server SETTINGS-Frames aus, die die Fenstergrößen der Ablaufsteuerung in beide Richtungen festlegen. Der Standardwert des Ablaufsteuerungsfensters ist auf 65.535 Byte eingestellt. Der Empfänger kann jedoch eine große maximale Fenstergröße (2^31-1 Byte) festlegen und diese beibehalten,indem er einen WINDOW_UPDATE-Frame sendet, wenn Daten empfangen werden.
  • Die Ablaufsteuerung erfolgt Hop-für-Hop, nicht Ende-zu-Ende. Das heißt, ein Vermittler kann damit die Ressourcennutzung steuern und Mechanismen zur Ressourcenzuweisung auf der Grundlage eigener Kriterien und Heuristiken implementieren.

HTTP/2 gibt keinen bestimmten Algorithmus zur Implementierung der Ablaufsteuerung an. Stattdessen stellt sie die einfachen Bausteine bereit und übergibt die Implementierung auf den Client und Server, der damit benutzerdefinierte Strategien zur Regulierung der Ressourcennutzung und -zuweisung implementieren sowie neue Bereitstellungsfunktionen implementieren kann, mit denen sich sowohl die tatsächliche als auch die wahrgenommene Leistung verbessern lässt (siehe Geschwindigkeit, Leistung und menschliche Wahrnehmung) unserer Webanwendungen.

Mit der Ablaufsteuerung auf Anwendungsebene kann der Browser beispielsweise nur einen Teil einer bestimmten Ressource abrufen, den Abruf anhalten, indem er das Fenster für die Streamflusssteuerung auf null reduziert und ihn später wieder aktiviert. Mit anderen Worten: Der Browser kann eine Vorschau oder einen ersten Scan eines Bildes abrufen, es anzeigen und andere Abrufe mit hoher Priorität zulassen. Der Abruf wird fortgesetzt, sobald weitere kritische Ressourcen geladen sind.

Server-Push

Eine weitere leistungsstarke neue Funktion von HTTP/2 ist die Möglichkeit des Servers, mehrere Antworten auf eine einzelne Clientanfrage zu senden. Das bedeutet, dass der Server zusätzlich zur Antwort auf die ursprüngliche Anfrage zusätzliche Ressourcen an den Client senden kann (Abbildung 12.5), ohne dass der Client jede einzeln anfordern muss.

Server initiiert neue Streams (Versprechungen) für Push-Ressourcen

Wozu brauchen wir in einem Browser einen solchen Mechanismus? Eine typische Webanwendung besteht aus Dutzenden von Ressourcen, die alle vom Client durch Prüfung des vom Server bereitgestellten Dokuments erkannt werden. Warum sollten Sie also nicht die zusätzliche Latenz eliminieren und den Server die zugehörigen Ressourcen im Voraus senden lassen? Der Server weiß bereits, welche Ressourcen der Client benötigt. Das ist der Server-Push.

Wenn Sie schon einmal ein CSS-, JavaScript- oder ein anderes Asset über einen Daten-URI inline eingefügt haben (siehe Ressourcen-Inlinen), haben Sie bereits praktische Erfahrung mit Server-Push. Durch manuelles Einfügen der Ressource in das Dokument übertragen wir diese Ressource an den Client, ohne darauf warten zu müssen, dass der Client sie anfordert. Mit HTTP/2 können wir die gleichen Ergebnisse erzielen, aber mit zusätzlichen Leistungsvorteilen. Mögliche Push-Ressourcen:

  • Vom Client im Cache gespeichert
  • Wiederverwendet auf verschiedenen Seiten
  • Multiplexing mit anderen Ressourcen
  • Vom Server priorisiert
  • Vom Kunden abgelehnt

PUSH_PROMISE – Einführung

Alle Server-Push-Streams werden über PUSH_PROMISE-Frames initiiert. Dies signalisiert die Absicht des Servers, die beschriebenen Ressourcen an den Client zu senden. Sie müssen vor den Antwortdaten bereitgestellt werden, die die übertragenen Ressourcen anfordern. Diese Bereitstellungsreihenfolge ist entscheidend: Der Client muss wissen, welche Ressourcen vom Server übertragen werden sollen, damit keine doppelten Anfragen für diese Ressourcen erstellt werden. Die einfachste Strategie, um diese Anforderung zu erfüllen, besteht darin, alle PUSH_PROMISE-Frames, die nur die HTTP-Header der zugesagten Ressource enthalten, vor der Antwort des übergeordneten Elements zu senden (mit anderen Worten, DATA-Frames).

Sobald der Client einen PUSH_PROMISE-Frame empfängt, kann er den Stream bei Bedarf über einen RST_STREAM-Frame ablehnen. Dies kann beispielsweise der Fall sein, wenn sich die Ressource bereits im Cache befindet. Dies ist eine wichtige Verbesserung gegenüber HTTP/1.x. Im Gegensatz dazu entspricht die Verwendung des Ressourcen-Inline-Objekts, das eine beliebte „Optimierung“ für HTTP/1.x ist, einem „erzwungenen Push“: Der Client kann die Inline-Ressource nicht deaktivieren, abbrechen oder einzeln verarbeiten.

Bei HTTP/2 behält der Client die volle Kontrolle darüber, wie der Server-Push verwendet wird. Der Client kann die Anzahl der gleichzeitig übertragenen Streams begrenzen, das anfängliche Ablaufsteuerungsfenster anpassen, um zu steuern, wie viele Daten übertragen werden, wenn der Stream zum ersten Mal geöffnet wird, oder den Server-Push vollständig deaktivieren. Diese Einstellungen werden zu Beginn der HTTP/2-Verbindung über die SETTINGS-Frames gesendet und können jederzeit aktualisiert werden.

Jede übertragene Ressource ist ein Stream, der im Gegensatz zu einer Inline-Ressource es ermöglicht, vom Client einzeln Multiplexing, Priorisierung und Verarbeitung durchzuführen. Die einzige vom Browser erzwungene Sicherheitsbeschränkung besteht darin, dass übertragene Ressourcen der Same-Origin-Richtlinie entsprechen müssen: Der Server muss für die bereitgestellten Inhalte autoritativ sein.

Header-Komprimierung

Jede HTTP-Übertragung enthält eine Reihe von Headern, die die übertragene Ressource und ihre Attribute beschreiben. In HTTP/1.x werden diese Metadaten immer als Klartext gesendet und mit 500 bis 800 Byte Overhead pro Übertragung und manchmal auch Kilobyte mehr, wenn HTTP-Cookies verwendet werden, hinzukommen. (Weitere Informationen finden Sie unter Protokoll-Overhead messen und kontrollieren.) Um diesen Aufwand zu reduzieren und die Leistung zu verbessern, komprimiert HTTP/2 die Metadaten der Anfrage- und Antwort-Header mit dem HPACK-Komprimierungsformat, das zwei einfache, aber leistungsstarke Techniken verwendet:

  1. Sie ermöglicht die Codierung der übertragenen Header-Felder mit einem statischen Huffman-Code, wodurch ihre individuelle Übertragungsgröße reduziert wird.
  2. Dazu müssen sowohl der Client als auch der Server eine indexierte Liste zuvor gesehener Headerfelder pflegen und aktualisieren (es wird also ein gemeinsamer Komprimierungskontext erstellt), der dann als Referenz verwendet wird, um zuvor übertragene Werte effizient zu codieren.

Die Huffman-Codierung ermöglicht das Komprimieren der einzelnen Werte bei der Übertragung. Mit der indexierten Liste der zuvor übertragenen Werte können wir doppelte Werte codieren. Dazu werden Indexwerte übertragen, mit denen die vollständigen Headerschlüssel und ‐werte effizient gesucht und rekonstruiert werden können.

HPACK: Header-Komprimierung für HTTP/2

Als weitere Optimierung besteht der HPACK-Komprimierungskontext aus einer statischen und dynamischen Tabelle: Die statische Tabelle ist in der Spezifikation definiert und stellt eine Liste mit allgemeinen HTTP-Header-Feldern bereit, die wahrscheinlich von allen Verbindungen verwendet werden (z. B. gültige Headernamen). Die dynamische Tabelle ist anfangs leer und wird basierend auf ausgetauschten Werten innerhalb einer bestimmten Verbindung aktualisiert. Dadurch wird die Größe jeder Anfrage reduziert, indem die statische Huffman-Codierung für bislang unbekannte Werte verwendet und Indexe durch Werte ersetzt werden, die bereits in den statischen oder dynamischen Tabellen auf jeder Seite vorhanden sind.

Sicherheit und Leistung von HPACK

In früheren Versionen von HTTP/2 und SPDY wurde zlib mit einem benutzerdefinierten Wörterbuch verwendet, um alle HTTP-Header zu komprimieren. Dadurch konnte die Größe der übertragenen Headerdaten um 85 bis 88 % reduziert und die Latenz beim Laden der Seite erheblich verbessert werden:

Bei einem DSL-Link mit geringerer Bandbreite, bei dem der Uploadlink nur 375 kbit/s beträgt, hat die Komprimierung des Anfrageheaders bei bestimmten Websites (mit anderen Worten, bei denen eine große Anzahl von Ressourcenanfragen gesendet wurde) zu erheblichen Verbesserungen der Seitenladezeit geführt. Wir haben festgestellt, dass die Seitenladezeit allein durch die Header-Komprimierung um 45 bis 1.142 ms verkürzt wird. (SPDY-Whitepaper, chromium.org)

Im Sommer 2012 wurde jedoch ein „CRIME“-Sicherheitsangriff gegen TLS- und SPDY-Komprimierungsalgorithmen veröffentlicht, der zu Session-Hijacking führen könnte. Daher wurde der zlib-Komprimierungsalgorithmus durch HPACK ersetzt, das speziell dafür entwickelt wurde, die erkannten Sicherheitsprobleme zu beheben, effizient und einfach korrekt zu implementieren und natürlich eine gute Komprimierung von HTTP-Header-Metadaten zu ermöglichen.

Ausführliche Informationen zum HPACK-Komprimierungsalgorithmus finden Sie unter IETF HPACK – Header Compression for HTTP/2.

Weitere Informationen