Einführung in HTTP/2

HTTP/2 macht unsere Anwendungen schneller, einfacher und robuster – eine seltene Kombination –, da wir viele der HTTP/1.1-Umgehungen, die zuvor in unseren Anwendungen ausgeführt wurden, rückgängig machen und diese Probleme in der Transportschicht selbst beheben können. Darüber hinaus bieten sie auch eine Reihe von völlig neuen Möglichkeiten zur Optimierung unserer Anwendungen und zur Verbesserung der Leistung.

Die Hauptziele von HTTP/2 bestehen darin, die Latenz zu reduzieren, indem das vollständige Multiplexing von Anfragen und Antworten aktiviert wird, der Protokoll-Overhead durch eine effiziente Komprimierung von HTTP-Headerfeldern zu minimieren sowie Unterstützung für die Priorisierung von Anfragen und die Übertragung von Serveranfragen hinzuzufügen. Zur Umsetzung dieser Anforderungen gibt es zahlreiche weitere Protokollverbesserungen, z. B. neue Ablaufsteuerung, Fehlerbehandlung und Upgrademechanismen. Dies sind jedoch die wichtigsten Funktionen, die jeder Webentwickler kennen und in seinen Anwendungen nutzen sollte.

Durch HTTP/2 wird die Anwendungssemantik von HTTP in keiner Weise geändert. Alle Kernkonzepte wie HTTP-Methoden, Statuscodes, URIs und Headerfelder bleiben bestehen. Stattdessen ändert HTTP/2, wie die Daten formatiert (als Frames) formatiert und zwischen Client und Server übertragen werden. Beide verwalten den gesamten Prozess und die gesamte Komplexität wird innerhalb der neuen Framing-Ebene vor unseren Anwendungen verborgen. Infolgedessen können alle vorhandenen Anwendungen unverändert bereitgestellt werden.

Warum nicht HTTP/1.2?

Um die von der HTTP-Arbeitsgruppe festgelegten Leistungsziele zu erreichen, wird mit HTTP/2 eine neue binäre Framing-Ebene eingeführt, die nicht abwärtskompatibel mit früheren HTTP/1.x-Servern und -Clients ist. Daher wird die Hauptprotokollversion auf HTTP/2 erhöht.

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

Ein kurzer Verlauf von SPDY und HTTP/2

SPDY war ein experimentelles Protokoll, das von Google entwickelt und Mitte 2009 angekündigt wurde. Das Hauptziel war, die Ladelatenz von Webseiten durch die Überwindung einiger der bekannten Leistungseinschränkungen von HTTP/1.1 zu reduzieren. Konkret wurden die beschriebenen Projektziele wie folgt festgelegt:

  • Verkürzung der Seitenladezeit um 50% anstreben
  • Vermeiden Sie es, Änderungen am Inhalt durch Website-Autoren vorzunehmen.
  • Minimieren Sie die Bereitstellungskomplexität und vermeiden Sie Änderungen an der Netzwerkinfrastruktur.
  • Entwickle dieses neue Protokoll in Zusammenarbeit mit der Open-Source-Community.
  • Es werden echte Leistungsdaten erfasst, um das experimentelle Protokoll zu (Un-)gültig zu machen.

Kurz nach der Ankündigung teilten Mike Belshe und Roberto Peon, beide Softwareentwickler bei Google, ihre ersten Ergebnisse, Dokumentationen und den 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, stellen wir eine erhebliche Leistungssteigerung fest. Die Seiten wurden um bis zu 55% schneller geladen. (Chromium-Blog)

2012 wurde das neue experimentelle Protokoll in Chrome, Firefox und Opera unterstützt. Immer mehr Websites, sowohl große (z. B. Google, Twitter, Facebook) als auch kleine Websites, implementierten SPDY in ihrer Infrastruktur. SPDY war auf dem besten Weg, durch die zunehmende Einführung in der Branche zum De-facto-Standard zu werden.

Angesichts dieses Trends hat die HTTP Working Group (HTTP-WG) neue Anstrengungen unternommen, um die von SPDY gewonnenen Erkenntnisse zu nutzen, sie zu entwickeln und zu verbessern und einen offiziellen HTTP/2-Standard bereitzustellen. Es wurde eine neue Charta verfasst, ein offener Aufruf zu HTTP/2-Vorschlägen abgegeben und nach regen Diskussionen in der Arbeitsgruppe wurde die SPDY-Spezifikation als Ausgangspunkt für das neue HTTP/2-Protokoll übernommen.

Im Laufe der nächsten Jahre entwickelten sich SPDY und HTTP/2 parallel weiter, wobei SPDY als experimenteller Zweig diente, um neue Funktionen und Vorschläge für den HTTP/2-Standard zu testen. Was auf dem Papier gut aussieht, funktioniert in der Praxis möglicherweise nicht und umgekehrt. SPDY bot eine Route zum Testen und Bewerten der einzelnen Angebote an, bevor sie in den HTTP/2-Standard aufgenommen werden. Dieser Prozess dauerte drei Jahre und führte schließlich zu mehr als einem Dutzend Zwischenentwürfen:

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

Anfang 2015 hat das IESG den neuen HTTP/2-Standard für die Veröffentlichung geprüft und genehmigt. Kurz danach kündigte das Google Chrome-Team seinen Zeitplan zur Einstellung der SPDY- und NPN-Erweiterung für TLS an:

Die primären Änderungen von HTTP/2 gegenüber HTTP/1.1 konzentrieren sich auf eine verbesserte Leistung. Einige wichtige Funktionen wie Multiplexing, Header-Komprimierung, Priorisierung und Protokollverhandlung haben sich aus der Arbeit eines früheren offenen, aber nicht standardmäßigen Protokolls namens SPDY entwickelt. Chrome unterstützt SPDY seit Chrome 6. Da die meisten Vorteile jedoch in HTTP/2 vorhanden sind, ist es an der Zeit, sich zu verabschieden. Wir planen, die Unterstützung für SPDY Anfang 2016 einzustellen und gleichzeitig die Unterstützung für die TLS-Erweiterung NPN zugunsten von ALPN in Chrome einzustellen. Serverentwicklern wird dringend empfohlen, auf HTTP/2 und ALPN umzustellen.

Wir freuen uns, zum Prozess der offenen Standards beigetragen zu haben, der zur Entwicklung von HTTP/2 geführt hat, und hoffen auf eine breite Akzeptanz angesichts des breiten branchenweiten Engagements in Bezug auf Standardisierung und Implementierung. (Chromium-Blog)

Die Entwicklung von SPDY- und HTTP/2-fähigen Server-, Browser- und Websiteentwicklern, um praktische Erfahrungen mit dem neuen Protokoll in seiner Entwicklung zu sammeln. Daher ist der HTTP/2-Standard von Anfang an einer der besten und am umfangreichsten getesteten Standards. Als HTTP/2 vom IESG genehmigt wurde, gab es Dutzende von sorgfältig getesteten und produktionsreifen Client- und Serverimplementierungen. Nur wenige Wochen nach der Genehmigung des endgültigen Protokolls kamen viele Nutzer bereits in den Genuss der Vorteile, da mehrere beliebte Browser (und viele Websites) vollen HTTP/2-Support bereitstellten.

Designbezogene und technische Ziele

Frühere Versionen des HTTP-Protokolls wurden absichtlich 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. Mit HTTP/1.1 wurde ein offizieller IETF-Standard eingeführt. Weitere Informationen finden Sie unter Brief History of HTTP. Daher lieferte HTTP/0.9-1.x genau das, was es sich vorstellte: HTTP ist eines der am häufigsten verwendeten Anwendungsprotokolle im Internet.

Leider führte die einfache Implementierung auch zu Kosten der Anwendungsleistung: HTTP/1.x-Clients müssen mehrere Verbindungen verwenden, um Gleichzeitigkeit zu erzielen und die Latenz zu reduzieren. HTTP/1.x komprimiert keine Anfrage- und Antwortheader, was zu unnötigem Netzwerkverkehr führt. HTTP/1.x ermöglicht keine effektive Ressourcenpriorisierung, was zu einer schlechten Nutzung der zugrunde liegenden TCP-Verbindung führt usw.

Diese Einschränkungen waren nicht gravierend, aber da der Umfang, die Komplexität und die Bedeutung der Webanwendungen in unserem Alltag immer weiter wuchsen, stellten sie sowohl für die Entwickler als auch für die Nutzer des Webs eine immer größere Last dar. Dies ist genau die Lücke, die HTTP/2 schließen soll:

HTTP/2 ermöglicht eine effizientere Nutzung von Netzwerkressourcen und eine reduzierte Wahrnehmung von Latenz, da Header-Feldkomprimierung eingeführt wird und mehrere gleichzeitige Austausche über dieselbe Verbindung möglich sind. Insbesondere ermöglicht es das Verschachteln von Anfrage- und Antwortnachrichten über dieselbe Verbindung und verwendet eine effiziente Codierung für HTTP-Headerfelder. Außerdem können Anfragen priorisiert werden, sodass wichtigere Anfragen schneller bearbeitet werden können, was die Leistung weiter verbessert.

Das resultierende Protokoll ist besser für das Netzwerk geeignet, da im Vergleich zu HTTP/1.x weniger TCP-Verbindungen verwendet werden können. Dies bedeutet weniger Wettbewerb mit anderen Datenflüssen 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 die Verwendung von binärem Nachrichten-Framing. (Hypertext Transfer Protocol Version 2, Entwurf 17)

Dabei ist zu beachten, dass HTTP/2 die vorherigen HTTP-Standards nicht ersetzt, sondern erweitert. Die Anwendungssemantik von HTTP ist dieselbe und es wurden keine Änderungen an den angebotenen Funktionen oder Kernkonzepten wie HTTP-Methoden, Statuscodes, URIs und Headerfeldern vorgenommen. Diese Änderungen wurden explizit für HTTP/2 ausgeschlossen. Die übergeordnete API bleibt zwar unverändert, aber es ist wichtig zu verstehen, wie die Low-Level-Änderungen die Leistungseinschränkungen der vorherigen Protokolle angehen. Sehen wir uns kurz die binäre Framing-Ebene und ihre Funktionen an.

Binary Framing-Ebene

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

HTTP/2-Binär-Framing-Ebene

Die "Ebene" bezieht sich auf eine Designentscheidung zur Einführung eines neuen optimierten Codierungsmechanismus zwischen der Socket-Schnittstelle und der höheren HTTP API, die unseren Anwendungen zur Verfügung gestellt wird: Die HTTP-Semantik, wie Verben, Methoden und Header, ist nicht betroffen, aber die Art und Weise, wie sie während der Übertragung codiert wird, ändert sich. Im Gegensatz zum durch Zeilenumbruch getrennten HTTP/1.x-Klartextprotokoll 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 versteht einen reinen HTTP/2-Server nicht und umgekehrt. Glücklicherweise sind unsere Anwendungen vollkommen unbewusst von all diesen Änderungen, da Client und Server die erforderlichen Framing-Arbeiten für uns ausführen.

Streams, Nachrichten und Frames

Mit der Einführung des neuen binären Framing-Mechanismus ändert sich der Datenaustausch zwischen Client und Server. Um diesen Vorgang zu beschreiben, machen wir uns mit der HTTP/2-Terminologie vertraut:

  • Stream: Ein bidirektionaler Bytefluss innerhalb einer hergestellten Verbindung, der 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. Sie enthält jeweils einen Frame-Header, der zumindest den Stream identifiziert, zu dem der Frame gehört.

Die Beziehung dieser Begriffe 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 von bidirektionalen 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 eine bestimmte Art von Daten überträgt, z.B. HTTP-Header, Nachrichtennutzlast usw. Frames aus verschiedenen Streams können verschränkt und dann über die eingebettete Stream-ID im Header der einzelnen Frames wieder zusammengefügt werden.

HTTP/2-Streams, -Nachrichten und -Frames

Kurz gesagt: HTTP/2 schlüsselt die HTTP-Protokollkommunikation in einen Austausch von binär codierten Frames auf. Diese werden dann Nachrichten zugeordnet, die zu einem bestimmten Stream gehören und die alle innerhalb einer einzelnen TCP-Verbindung Multiplexverfahren werden. Dies bildet die Grundlage für alle anderen Funktionen und Leistungsoptimierungen, die vom HTTP/2-Protokoll bereitgestellt werden.

Multiplexing von Anfragen und Antworten

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

Mit der neuen binären Framing-Ebene in HTTP/2 werden diese Einschränkungen beseitigt und das vollständige Multiplexing von Anfragen und Antworten ermöglicht, da Client und Server eine HTTP-Nachricht in unabhängige Frames unterteilen, diese verschränken und am anderen Ende wieder zusammenfügen.

HTTP/2-Anfrage- und -Antwort-Multiplexing innerhalb einer gemeinsam genutzten Verbindung

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

Die Möglichkeit, eine HTTP-Nachricht in unabhängige Frames aufzuteilen, diese zu verschränken und am anderen Ende wieder zusammenzuführen, ist die wichtigste Verbesserung von HTTP/2. Tatsächlich bringt es zahlreiche Leistungsvorteile im gesamten Stack aller Webtechnologien mit sich, die es uns ermöglichen:

  • Sie können mehrere Anfragen parallel verschachteln, ohne eine Anfrage zu blockieren.
  • Sie können mehrere Antworten parallel verschachteln, ohne eine Antwort zu blockieren.
  • Verwenden Sie eine einzige Verbindung, um mehrere Anfragen und Antworten parallel 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.
  • Verkürzen Sie die Seitenladezeiten, indem Sie unnötige Latenzen vermeiden und die Auslastung der verfügbaren Netzwerkkapazität verbessern.
  • Und vieles mehr...

Die neue binäre Framing-Ebene in HTTP/2 löst das Head-of-Line-Blockierungsproblem in HTTP/1.x und macht die Notwendigkeit mehrerer Verbindungen für die parallele Verarbeitung und Übermittlung von Anfragen und Antworten überflüssig. Dadurch werden unsere Anwendungen schneller, einfacher und kostengünstiger in der Bereitstellung.

Streampriorisierung

Sobald eine HTTP-Nachricht in viele einzelne Frames aufgeteilt werden kann und das Multiplexing von Frames aus mehreren Streams möglich ist, spielt die Reihenfolge, in der die Frames verschränkt und sowohl vom Client als auch vom Server gesendet werden, eine wichtige Leistungsüberlegung. Um dies zu vereinfachen, ermöglicht der HTTP/2-Standard jedem Stream die zugehörige Gewichtung und Abhängigkeit:

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

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 würde. Der Server kann diese Informationen wiederum verwenden, um die Streamverarbeitung zu priorisieren. Dazu steuert er die Zuweisung von CPU, Arbeitsspeicher und anderen Ressourcen. Sobald die Antwortdaten verfügbar sind, wird die Bandbreite zugewiesen, um eine optimale Übermittlung von Antworten mit hoher Priorität an den Client zu gewährleisten.

Abhängigkeiten und Gewichtungen von HTTP/2-Streams

Eine Streamabhängigkeit in HTTP/2 wird deklariert, indem auf die eindeutige ID eines anderen Streams als übergeordnetes Element verwiesen wird. Wenn die Kennung weggelassen wird, ist der Stream vom Root-Stream abhängig. Wenn Sie eine Streamabhängigkeit deklarieren, bedeutet dies, dass dem übergeordneten Stream nach Möglichkeit vor seinen Abhängigkeiten Ressourcen zugewiesen werden sollten. Mit anderen Worten: „Bitte verarbeiten und liefern Sie Antwort D vor Antwort C.“

Streams, die dasselbe übergeordnete Element haben (mit anderen Worten, gleichgeordnete Streams) sollten Ressourcen im Verhältnis zu ihrer Gewichtung zugewiesen werden. Wenn Stream A beispielsweise eine Gewichtung von 12 hat und sein gleichgeordnetes Element B eine Gewichtung von 4 hat, können Sie den Anteil der Ressourcen bestimmen, die jeder dieser Streams empfangen soll:

  1. Alle Gewichtungen summieren: 4 + 12 = 16
  2. Jedes Streamgewicht durch das Gesamtgewicht dividieren: A = 12/16, B = 4/16

Daher sollte Stream A drei Viertel und Stream B ein Viertel der verfügbaren Ressourcen erhalten und Stream B ein Drittel der Ressourcen erhalten sollte, die Stream A zugewiesen sind. Sehen wir uns im Bild oben noch einige weitere praktische Beispiele an. Von links nach rechts:

  1. Weder Stream A noch B gibt eine übergeordnete Abhängigkeit an und sollen vom impliziten „Stammstream“ abhängig sein. A hat eine Gewichtung von 12 und B eine Gewichtung von 4. Basierend auf proportionalen Gewichtungen sollte Stream B also ein Drittel der Ressourcen erhalten, die Stream A zugewiesen sind.
  2. Stream D ist vom Stammstream abhängig; C ist von D abhängig. Daher sollte D die vollständige Ressourcenzuweisung vor C erhalten. Die Gewichtungen sind bedeutsam, da die Abhängigkeit von C eine stärkere Präferenz vermittelt.
  3. Stream D sollte die vollständige Zuweisung von Ressourcen vor C erhalten; C sollte die vollständige Zuweisung von Ressourcen vor A und B erhalten; Stream B sollte ein Drittel der Ressourcen erhalten, die Stream A zugewiesen sind.
  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 aus Streamabhängigkeiten und -gewichtungen eine aussagekräftige Sprache für die Ressourcenpriorisierung. Dies ist ein wichtiges Feature zur Verbesserung der Browserleistung, wenn wir viele Ressourcentypen mit unterschiedlichen Abhängigkeiten und Gewichtungen haben. Noch besser: Das HTTP/2-Protokoll ermöglicht dem Client außerdem, diese Einstellungen jederzeit zu 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 zuweisen.

Eine Verbindung pro Ursprung

Mit dem neuen binären 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. Daher sind alle HTTP/2-Verbindungen dauerhaft und nur eine Verbindung pro Ursprung erforderlich, was zahlreiche Leistungsvorteile bietet.

Sowohl für SPDY als auch für HTTP/2 ist das willkürliche Multiplexing auf einem einzelnen gut überlasteten Kanal das Killer. Erstaunlich, wie wichtig das ist und wie gut es funktioniert. Ein großer Messwert, den ich dabei finde, ist der Anteil der hergestellten Verbindungen, die nur eine einzige HTTP-Transaktion ausführen (und somit den gesamten Aufwand für diese Transaktion tragen). Bei HTTP/1 führen 74% unserer aktiven Verbindungen nur eine einzige Transaktion durch – dauerhafte Verbindungen sind einfach nicht so hilfreich, wie wir alle es möchten. In HTTP/2 sinkt diese Zahl auf 25%. Das ist ein großer Gewinn bei der Reduzierung des Aufwands. (HTTP/2 is Live in Firefox, Patrick McManus)

Die meisten HTTP-Übertragungen sind kurz und stoßweise, während TCP für langlebige, Massendatenübertragungen optimiert ist. Durch die Wiederverwendung derselben Verbindung kann HTTP/2 die einzelnen TCP-Verbindungen effizienter nutzen und gleichzeitig den gesamten Protokoll-Overhead erheblich reduzieren. Außerdem reduziert sich durch die Verwendung von weniger Verbindungen der Arbeitsspeicher- und Verarbeitungsaufwand entlang des gesamten Verbindungspfads (mit anderen Worten, auf Client-, Zwischen- und Ursprungsservern). 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 dazu beitragen, den Durchsatz zu verbessern und die Betriebskosten zu senken.

Ablaufsteuerung

Die Ablaufsteuerung ist ein Mechanismus, um zu verhindern, dass der Sender den Empfänger mit Daten überfordert, die er möglicherweise nicht verarbeiten möchte oder nicht verarbeiten kann: Der Empfänger ist möglicherweise ausgelastet, stark ausgelastet oder nur bereit, eine feste Menge an Ressourcen für einen bestimmten Stream zuzuweisen. Beispiel: Der Client hat möglicherweise einen großen Videostream mit hoher Priorität angefordert, der Nutzer hat das Video aber pausiert und der Client möchte nun die Übertragung vom Server anhalten oder drosseln, um das Abrufen und Zwischenspeichern unnötiger Daten zu vermeiden. Alternativ kann ein Proxyserver schnelle nachgelagerte und langsame Upstream-Verbindungen haben und in ähnlicher Weise steuern, wie schnell der nachgelagerte Daten an die Geschwindigkeit des Upstreams angepasst wird, um seine Ressourcennutzung zu steuern usw.

Erinnern Sie sich durch die oben genannten Anforderungen an die TCP-Ablaufsteuerung? Sie sollten sie sollten, da das Problem praktisch identisch ist (siehe Ablaufsteuerung). Da die HTTP/2-Streams jedoch innerhalb einer einzelnen TCP-Verbindung multipliziert werden, ist die TCP-Ablaufsteuerung nicht detailliert genug und bietet nicht die erforderlichen APIs auf Anwendungsebene, um die Zustellung einzelner Streams zu regulieren. HTTP/2 bietet eine Reihe von einfachen Bausteinen, mit denen der Client und der Server ihre eigene Ablaufsteuerung auf Stream- und Verbindungsebene implementieren können:

  • Die Ablaufsteuerung ist direktional. Jeder Empfänger kann eine beliebige Fenstergröße für jeden Stream und die gesamte Verbindung festlegen.
  • Die Ablaufsteuerung ist kreditbasiert. Jeder Empfänger bewirbt sein anfängliches Verbindungs- und Stream-Ablaufsteuerungsfenster (in Byte), das reduziert wird, wenn der Sender 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 der Client und der Server SETTINGS-Frames aus, die die Größe des Ablaufsteuerungsfensters in beide Richtungen festlegen. Der Standardwert des Ablaufsteuerungsfensters ist auf 65.535 Byte festgelegt. Der Empfänger kann jedoch eine große maximale Fenstergröße (2^31-1 Byte) festlegen und diese durch Senden eines WINDOW_UPDATE-Frames beibehalten, wenn Daten empfangen werden.
  • Die Ablaufsteuerung erfolgt Schritt für Hop, nicht durchgängig. Das heißt, ein Vermittler kann damit die Ressourcennutzung steuern und Ressourcenzuweisungsmechanismen auf der Grundlage eigener Kriterien und Heuristiken implementieren.

HTTP/2 spezifiziert keinen bestimmten Algorithmus für die Implementierung der Ablaufsteuerung. Stattdessen stellt es die einfachen Bausteine bereit und überlegt die Implementierung auf den Client und den Server. Diese können damit benutzerdefinierte Strategien zur Steuerung der Ressourcennutzung und -zuweisung implementieren sowie neue Bereitstellungsfunktionen implementieren, die zur Verbesserung der tatsächlichen und der wahrgenommenen Leistung unserer Webanwendungen beitragen können (siehe Geschwindigkeit, Leistung und menschliche Wahrnehmung).

Beispielsweise ermöglicht die Ablaufsteuerung auf Anwendungsebene, dass der Browser nur einen Teil einer bestimmten Ressource abrufen und den Abruf anhalten kann, indem er das Fenster für die Ablaufsteuerung des Streams auf null verkleinert, und ihn später fortsetzt. 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 und den Abruf fortsetzen, sobald weitere kritische Ressourcen fertig geladen sind.

Server-Push

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

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

Wozu dient ein solcher Mechanismus in einem Browser? Eine typische Webanwendung besteht aus Dutzenden von Ressourcen, die alle vom Client anhand des vom Server bereitgestellten Dokuments erkannt werden. Warum vermeiden Sie es daher, die zusätzliche Latenz zu eliminieren und den Server die zugehörigen Ressourcen vorzeitig übertragen zu lassen? Der Server weiß bereits, welche Ressourcen der Client benötigt. Das ist Server-Push.

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

  • Vom Client im Cache gespeichert
  • Auf verschiedenen Seiten wiederverwendet
  • Multiplex und andere Ressourcen
  • Vom Server priorisiert
  • Vom Kunden abgelehnt

PUSH_PROMISE – erste Schritte

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

Sobald der Client einen PUSH_PROMISE-Frame erhält, kann er den Stream bei Bedarf über einen RST_STREAM-Frame ablehnen. (Dies kann beispielsweise auftreten, wenn sich die Ressource bereits im Cache befindet.) Dies ist eine wichtige Verbesserung im Vergleich zu HTTP/1.x. Im Gegensatz dazu ist die Verwendung von Ressourcen-Inlining – einer beliebten "Optimierung" für HTTP/1.x – gleichbedeutend mit 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 Server-Push verwendet wird. Der Client kann die Anzahl der gleichzeitig übertragenen Streams begrenzen, das anfängliche Fenster für die Ablaufsteuerung anpassen, um zu steuern, wie viele Daten übertragen werden, wenn der Stream zum ersten Mal geöffnet wird, oder Server-Push vollständig deaktivieren. Diese Einstellungen werden über die SETTINGS-Frames zu Beginn der HTTP/2-Verbindung übermittelt und können jederzeit aktualisiert werden.

Jede gesendete Ressource ist ein Stream, der es im Gegensatz zu einer Inline-Ressource zulässt, dass sie einzeln vom Client in ein Multiplexsystem, priorisiert und verarbeitet wird. Die einzige vom Browser durchgesetzte Sicherheitsbeschränkung besteht darin, dass übertragene Ressourcen der Same-Origin-Richtlinie entsprechen müssen: Der Server muss für den bereitgestellten Inhalt 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 fügen zwischen 500 und 800 Byte an Overhead pro Übertragung und manchmal Kilobyte mehr hinzu, wenn HTTP-Cookies verwendet werden. Weitere Informationen finden Sie unter Protokollaufwand messen und steuern. Um diesen Aufwand zu reduzieren und die Leistung zu verbessern, komprimiert HTTP/2 die Metadaten von Anfrage- und Antwortheadern mithilfe des HPACK-Komprimierungsformats, das zwei einfache, aber leistungsstarke Techniken verwendet:

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

Mit der Huffman-Codierung können die einzelnen Werte bei der Übertragung komprimiert werden. Die indexierte Liste der zuvor übertragenen Werte ermöglicht es uns, doppelte Werte durch Übertragen von Indexwerten zu codieren, die zur effizienten Suche und Rekonstruktion der vollständigen Header-Schlüssel und -Werte verwendet werden können.

HPACK: Header-Komprimierung für HTTP/2

Zur weiteren Optimierung besteht der HPACK-Komprimierungskontext aus einer statischen und dynamischen Tabelle: Die statische Tabelle wird in der Spezifikation definiert und bietet eine Liste allgemeiner HTTP-Header-Felder, 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. Infolgedessen wird die Größe jeder Anfrage reduziert, indem die statische Huffman-Codierung für Werte verwendet wird, die noch nicht erfasst wurden, und das Ersetzen von Indexen für Werte, die bereits in den statischen oder dynamischen Tabellen auf beiden Seiten vorhanden sind.

Sicherheit und Leistung von HPACK

Frühere Versionen von HTTP/2 und SPDY verwendeten zlib mit einem benutzerdefinierten Wörterbuch zur Komprimierung aller HTTP-Header. Dadurch konnte die Größe der übertragenen Headerdaten um 85% bis 88% reduziert und die Latenz der Seitenladezeit erheblich verbessert werden:

Bei der DSL-Verbindung mit geringerer Bandbreite, bei der der Upload-Link nur 375 Kbit/s beträgt, führte die Anforderung der Header-Komprimierung insbesondere zu erheblichen Verbesserungen der Seitenladezeit für bestimmte Websites (also für solche, die eine große Anzahl von Ressourcenanfragen gesendet haben). Wir haben festgestellt, dass die Seitenladezeit allein durch die Header-Komprimierung um 45 bis 1.142 ms reduziert wird. (SPDY-Whitepaper, chromium.org)

Im Sommer 2012 wurde jedoch ein „CRIME“-Sicherheitsangriff gegen die TLS- und SPDY-Komprimierungsalgorithmen veröffentlicht, der zu einem Sitzungsdiebstahl führen könnte. Daher wurde der zlib-Komprimierungsalgorithmus durch HPACK ersetzt, der speziell darauf ausgelegt ist, erkannte Sicherheitsprobleme zu beheben, eine effiziente und einfache korrekte Implementierung zu ermöglichen und natürlich eine gute Komprimierung von HTTP-Header-Metadaten zu ermöglichen.

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

Weitere Informationen