5. Streams und Multiplexing
Ein "Stream" ist eine unabhängige, bidirektionale Sequenz von Frames, die zwischen Client und Server innerhalb einer HTTP/2-Verbindung ausgetauscht werden. Streams haben mehrere wichtige Eigenschaften:
-
Eine einzelne HTTP/2-Verbindung kann mehrere gleichzeitig geöffnete Streams enthalten, wobei beide Endpunkte Frames aus mehreren Streams verschachteln können.
-
Streams können einseitig erstellt und verwendet oder von Client oder Server gemeinsam genutzt werden.
-
Streams können von jedem Endpunkt geschlossen werden.
-
Die Reihenfolge, in der Frames auf einem Stream gesendet werden, ist bedeutsam. Empfänger verarbeiten Frames in der Reihenfolge, in der sie empfangen werden. Insbesondere ist die Reihenfolge von HEADERS- und DATA-Frames semantisch bedeutsam.
-
Streams werden durch eine Ganzzahl identifiziert. Stream-Identifikatoren werden Streams vom Endpunkt zugewiesen, der den Stream initiiert.
5.1. Stream-Zustände
Der Lebenszyklus eines Streams ist in Abbildung 2 dargestellt.
+--------+
send PP | | recv PP
,--------+ idle +--------.
/ | | \
v +--------+ v
+----------+ | +----------+
| | | send H / | |
,-----+ reserved | | recv H | reserved +-----.
| | (local) | | | (remote) | |
| +---+------+ v +------+---+ |
| | +--------+ | |
| | recv ES | | send ES | |
| send H | ,-------+ open +-------. | recv H |
| | / | | \ | |
| v v +---+----+ v v |
| +----------+ | +----------+ |
| | half- | | | half- | |
| | closed | | send R / | closed | |
| | (remote) | | recv R | (local) | |
| +----+-----+ | +-----+----+ |
| | | | |
| | send ES / | recv ES / | |
| | send R / v send R / | |
| | recv R +--------+ recv R | |
| send R / `----------->| |<-----------' send R / |
| recv R | closed | recv R |
`----------------------->| |<----------------------'
+--------+
send: endpoint sends this frame
recv: endpoint receives this frame
H: HEADERS frame (with implied CONTINUATION frames)
PP: PUSH_PROMISE frame (with implied CONTINUATION frames)
ES: END_STREAM flag
R: RST_STREAM frame
Abbildung 2: Stream-Zustände
Beachten Sie, dass dieses Diagramm nur Stream-Zustandsübergänge und die Frames und Flags zeigt, die diese Zustandsänderungen beeinflussen. In dieser Hinsicht führen CONTINUATION-Frames nicht zu Zustandsübergängen und sind effektiv Teil des HEADERS- oder PUSH_PROMISE-Frames, dem sie folgen. Für Zustandsübergänge wird das END_STREAM-Flag als separates Ereignis zu dem Frame verarbeitet, das es trägt; ein HEADERS-Frame mit gesetztem END_STREAM-Flag kann zwei Zustandsübergänge verursachen.
Beide Endpunkte haben eine subjektive Sicht auf den Zustand eines Streams, die unterschiedlich sein könnte, wenn Frames unterwegs sind. Endpunkte koordinieren die Erstellung von Streams nicht; sie werden einseitig von jedem Endpunkt erstellt. Die negativen Folgen einer Zustandsinkongruenz sind auf den "closed"-Zustand nach dem Senden von RST_STREAM beschränkt, wo Frames für einige Zeit nach dem Schließen empfangen werden könnten.
Streams haben die folgenden Zustände:
idle: Alle Streams beginnen im "idle"-Zustand.
Die folgenden Übergänge sind von diesem Zustand aus gültig:
-
Das Senden oder Empfangen eines HEADERS-Frames bewirkt, dass der Stream "open" wird. Der Stream-Identifikator wird wie in Abschnitt 5.1.1 beschrieben ausgewählt. Derselbe HEADERS-Frame kann auch bewirken, dass ein Stream sofort "half-closed" wird.
-
Das Senden eines PUSH_PROMISE-Frames auf einem anderen Stream reserviert den idle Stream, der für spätere Verwendung identifiziert wird. Der Stream-Zustand für den reservierten Stream wechselt zu "reserved (local)".
-
Das Empfangen eines PUSH_PROMISE-Frames auf einem anderen Stream reserviert einen idle Stream, der für spätere Verwendung identifiziert wird. Der Stream-Zustand für den reservierten Stream wechselt zu "reserved (remote)".
-
Beachten Sie, dass der PUSH_PROMISE-Frame nicht auf dem idle Stream gesendet wird, sondern auf den neu reservierten Stream im Feld Promised Stream ID verweist.
Das Empfangen eines anderen Frames als HEADERS oder PRIORITY auf einem Stream in diesem Zustand MUSS als Verbindungsfehler (Abschnitt 5.4.1) vom Typ PROTOCOL_ERROR behandelt werden.
reserved (local): Ein Stream im Zustand "reserved (local)" ist einer, der durch Senden eines PUSH_PROMISE-Frames versprochen wurde. Ein PUSH_PROMISE-Frame reserviert einen idle Stream, indem der Stream mit einem offenen Stream verknüpft wird, der vom entfernten Peer initiiert wurde (siehe Abschnitt 8.4).
In diesem Zustand sind nur die folgenden Übergänge möglich:
-
Der Endpunkt kann einen HEADERS-Frame senden. Dies bewirkt, dass der Stream im Zustand "half-closed (remote)" geöffnet wird.
-
Jeder Endpunkt kann einen RST_STREAM-Frame senden, um zu bewirken, dass der Stream "closed" wird. Dies gibt die Stream-Reservierung frei.
Ein Endpunkt DARF in diesem Zustand keinen anderen Frame-Typ als HEADERS, RST_STREAM oder PRIORITY senden.
Ein PRIORITY- oder WINDOW_UPDATE-Frame KANN in diesem Zustand empfangen werden. Das Empfangen eines anderen Frame-Typs als RST_STREAM, PRIORITY oder WINDOW_UPDATE auf einem Stream in diesem Zustand MUSS als Verbindungsfehler (Abschnitt 5.4.1) vom Typ PROTOCOL_ERROR behandelt werden.
reserved (remote): Ein Stream im Zustand "reserved (remote)" wurde von einem entfernten Peer reserviert.
In diesem Zustand sind nur die folgenden Übergänge möglich:
-
Das Empfangen eines HEADERS-Frames bewirkt, dass der Stream zu "half-closed (local)" übergeht.
-
Jeder Endpunkt kann einen RST_STREAM-Frame senden, um zu bewirken, dass der Stream "closed" wird. Dies gibt die Stream-Reservierung frei.
Ein Endpunkt KANN in diesem Zustand einen PRIORITY-Frame senden, um den reservierten Stream neu zu priorisieren. Ein Endpunkt DARF in diesem Zustand keinen anderen Frame-Typ als RST_STREAM, WINDOW_UPDATE oder PRIORITY senden.
Das Empfangen eines anderen Frame-Typs als HEADERS, RST_STREAM oder PRIORITY auf einem Stream in diesem Zustand MUSS als Verbindungsfehler (Abschnitt 5.4.1) vom Typ PROTOCOL_ERROR behandelt werden.
open: Ein Stream im "open"-Zustand kann von beiden Peers verwendet werden, um Frames jeglichen Typs zu senden. In diesem Zustand beachten sendende Peers angekündigte Stream-Ebene-Flusskontrollgrenzen (Abschnitt 5.2).
Von diesem Zustand aus kann jeder Endpunkt einen Frame mit gesetztem END_STREAM-Flag senden, was bewirkt, dass der Stream in einen der "half-closed"-Zustände übergeht. Ein Endpunkt, der ein END_STREAM-Flag sendet, bewirkt, dass der Stream-Zustand "half-closed (local)" wird; ein Endpunkt, der ein END_STREAM-Flag empfängt, bewirkt, dass der Stream-Zustand "half-closed (remote)" wird.
Jeder Endpunkt kann aus diesem Zustand einen RST_STREAM-Frame senden, wodurch er sofort zu "closed" übergeht.
half-closed (local): Ein Stream, der sich im Zustand "half-closed (local)" befindet, kann nicht zum Senden von Frames außer WINDOW_UPDATE, PRIORITY und RST_STREAM verwendet werden.
Ein Stream wechselt von diesem Zustand zu "closed", wenn ein Frame mit gesetztem END_STREAM-Flag empfangen wird oder wenn einer der Peers einen RST_STREAM-Frame sendet.
Ein Endpunkt kann in diesem Zustand jeden Frame-Typ empfangen. Das Bereitstellen von Flusskontroll-Guthaben unter Verwendung von WINDOW_UPDATE-Frames ist notwendig, um weiterhin flusskontrollierte Frames zu empfangen. In diesem Zustand kann ein Empfänger WINDOW_UPDATE-Frames ignorieren, die für kurze Zeit ankommen könnten, nachdem ein Frame mit gesetztem END_STREAM-Flag gesendet wurde.
PRIORITY-Frames, die in diesem Zustand empfangen werden, werden verwendet, um Streams neu zu priorisieren, die vom identifizierten Stream abhängen.
half-closed (remote): Ein Stream, der "half-closed (remote)" ist, wird vom Peer nicht mehr zum Senden von Frames verwendet. In diesem Zustand ist ein Endpunkt nicht mehr verpflichtet, ein Empfänger-Flusskontrollfenster aufrechtzuerhalten.
Wenn ein Endpunkt zusätzliche Frames außer WINDOW_UPDATE, PRIORITY oder RST_STREAM für einen Stream in diesem Zustand empfängt, MUSS es mit einem Stream-Fehler (Abschnitt 5.4.2) vom Typ STREAM_CLOSED antworten.
Ein Stream, der "half-closed (remote)" ist, kann vom Endpunkt verwendet werden, um Frames jeglichen Typs zu senden. In diesem Zustand beachtet der Endpunkt weiterhin angekündigte Stream-Ebene-Flusskontrollgrenzen (Abschnitt 5.2).
Ein Stream kann von diesem Zustand zu "closed" übergehen, indem ein Frame mit gesetztem END_STREAM-Flag gesendet wird oder wenn einer der Peers einen RST_STREAM-Frame sendet.
closed: Der "closed"-Zustand ist der Endzustand.
Ein Endpunkt DARF auf einem geschlossenen Stream keine anderen Frames als PRIORITY senden. Ein Endpunkt, das nach dem Empfang eines RST_STREAM einen anderen Frame als PRIORITY empfängt, MUSS dies als Stream-Fehler (Abschnitt 5.4.2) vom Typ STREAM_CLOSED behandeln. Ebenso MUSS ein Endpunkt, das nach dem Empfang eines Frames mit gesetztem END_STREAM-Flag weitere Frames empfängt, dies als Verbindungsfehler (Abschnitt 5.4.1) vom Typ STREAM_CLOSED behandeln, es sei denn, der Frame ist wie unten beschrieben zulässig.
WINDOW_UPDATE- oder RST_STREAM-Frames können in diesem Zustand für kurze Zeit empfangen werden, nachdem ein DATA- oder HEADERS-Frame mit END_STREAM-Flag gesendet wurde. Bis der entfernte Peer RST_STREAM oder den Frame mit dem END_STREAM-Flag empfängt und verarbeitet, könnte er Frames dieser Typen senden. Endpunkte MÜSSEN WINDOW_UPDATE- oder RST_STREAM-Frames, die in diesem Zustand empfangen werden, ignorieren, obwohl Endpunkte KÖNNEN wählen, Frames, die deutlich nach dem Senden von END_STREAM ankommen, als Verbindungsfehler (Abschnitt 5.4.1) vom Typ PROTOCOL_ERROR zu behandeln.
PRIORITY-Frames können auf geschlossenen Streams gesendet werden, um Streams zu priorisieren, die vom geschlossenen Stream abhängig sind. Endpunkte SOLLTEN PRIORITY-Frames verarbeiten, obwohl sie ignoriert werden können, wenn der Stream aus dem Abhängigkeitsbaum entfernt wurde (siehe Abschnitt 5.3.3 von [RFC7540]).
Wenn dieser Zustand als Ergebnis des Sendens eines RST_STREAM-Frames erreicht wird, hat der Peer, der den RST_STREAM empfängt, möglicherweise bereits Frames auf dem Stream gesendet -- oder zur Sendung in die Warteschlange gestellt --, die nicht zurückgezogen werden können. Ein Endpunkt MUSS Frames ignorieren, die es auf geschlossenen Streams empfängt, nachdem es einen RST_STREAM-Frame gesendet hat. Ein Endpunkt KANN wählen, den Zeitraum zu begrenzen, über den es Frames ignoriert, und Frames, die nach dieser Zeit ankommen, als fehlerhaft behandeln.
Flusskontrollierte Frames (d.h. DATA), die nach dem Senden von RST_STREAM empfangen werden, werden auf das Verbindungsflusskontrollfenster angerechnet. Obwohl diese Frames möglicherweise ignoriert werden, wird der Sender die Frames auf das Flusskontrollfenster anrechnen, da sie gesendet werden, bevor der Sender den RST_STREAM empfängt.
Ein Endpunkt könnte einen PUSH_PROMISE-Frame empfangen, nachdem es RST_STREAM gesendet hat. PUSH_PROMISE bewirkt, dass ein Stream "reserved" wird, selbst wenn der zugehörige Stream zurückgesetzt wurde. Daher ist ein RST_STREAM erforderlich, um einen unerwünschten versprochenen Stream zu schließen.
In Abwesenheit spezifischerer Regeln SOLLTEN Implementierungen den Empfang eines Frames, der in der Beschreibung eines Zustands nicht ausdrücklich erlaubt ist, als Verbindungsfehler (Abschnitt 5.4.1) vom Typ PROTOCOL_ERROR behandeln. Beachten Sie, dass PRIORITY in jedem Stream-Zustand gesendet und empfangen werden kann.
Die Regeln in diesem Abschnitt gelten nur für in diesem Dokument definierte Frames. Der Empfang von Frames, deren Semantik unbekannt ist, kann nicht als Fehler behandelt werden, da die Bedingungen für das Senden und Empfangen dieser Frames ebenfalls unbekannt sind; siehe Abschnitt 5.5.
Ein Beispiel für die Zustandsübergänge für einen HTTP-Anfrage/Antwort-Austausch findet sich in Abschnitt 8.8. Ein Beispiel für die Zustandsübergänge für Server-Push findet sich in den Abschnitten 8.4.1 und 8.4.2.
5.1.1. Stream-Identifikatoren
Streams werden durch eine vorzeichenlose 31-Bit-Ganzzahl identifiziert. Von einem Client initiierte Streams MÜSSEN ungerade nummerierte Stream-Identifikatoren verwenden; vom Server initiierte MÜSSEN gerade nummerierte Stream-Identifikatoren verwenden. Ein Stream-Identifikator von Null (0x00) wird für Verbindungskontrollnachrichten verwendet; der Stream-Identifikator Null kann nicht verwendet werden, um einen neuen Stream zu etablieren.
Der Identifikator eines neu etablierten Streams MUSS numerisch größer sein als alle Streams, die der initiierende Endpunkt geöffnet oder reserviert hat. Dies regelt Streams, die mit einem HEADERS-Frame geöffnet werden, und Streams, die mit PUSH_PROMISE reserviert werden. Ein Endpunkt, der einen unerwarteten Stream-Identifikator empfängt, MUSS mit einem Verbindungsfehler (Abschnitt 5.4.1) vom Typ PROTOCOL_ERROR antworten.
Ein HEADERS-Frame wird den vom Client initiierten Stream, der durch den Stream-Identifikator im Frame-Header identifiziert wird, von "idle" zu "open" überführen. Ein PUSH_PROMISE-Frame wird den vom Server initiierten Stream, der durch das Feld Promised Stream ID in der Frame-Nutzlast identifiziert wird, von "idle" zu "reserved (local)" oder "reserved (remote)" überführen. Wenn ein Stream aus dem "idle"-Zustand übergeht, gehen alle Streams im "idle"-Zustand, die möglicherweise vom Peer mit einem niedrigeren Stream-Identifikator geöffnet wurden, sofort zu "closed" über. Das heißt, ein Endpunkt kann einen Stream-Identifikator überspringen, mit der Wirkung, dass der übersprungene Stream sofort geschlossen wird.
Stream-Identifikatoren können nicht wiederverwendet werden. Langlebige Verbindungen können dazu führen, dass ein Endpunkt den verfügbaren Bereich von Stream-Identifikatoren erschöpft. Ein Client, der keinen neuen Stream-Identifikator etablieren kann, kann eine neue Verbindung für neue Streams etablieren. Ein Server, der keinen neuen Stream-Identifikator etablieren kann, kann einen GOAWAY-Frame senden, sodass der Client gezwungen ist, eine neue Verbindung für neue Streams zu öffnen.
5.1.2. Stream-Parallelität
Ein Peer kann die Anzahl gleichzeitig aktiver Streams mit dem SETTINGS_MAX_CONCURRENT_STREAMS-Parameter (siehe Abschnitt 6.5.2) innerhalb eines SETTINGS-Frames begrenzen. Die maximale gleichzeitige Streams-Einstellung ist für jeden Endpunkt spezifisch und gilt nur für den Peer, der die Einstellung empfängt. Das heißt, Clients spezifizieren die maximale Anzahl gleichzeitiger Streams, die der Server initiieren kann, und Server spezifizieren die maximale Anzahl gleichzeitiger Streams, die der Client initiieren kann.
Streams, die sich im "open"-Zustand oder in einem der "half-closed"-Zustände befinden, zählen zur maximalen Anzahl von Streams, die ein Endpunkt öffnen darf. Streams in einem dieser drei Zustände zählen zum in der SETTINGS_MAX_CONCURRENT_STREAMS-Einstellung angekündigten Limit. Streams in einem der "reserved"-Zustände zählen nicht zum Stream-Limit.
Endpunkte DÜRFEN das von ihrem Peer gesetzte Limit NICHT überschreiten. Ein Endpunkt, der einen HEADERS-Frame empfängt, der bewirkt, dass sein angekündigtes gleichzeitiges Stream-Limit überschritten wird, MUSS dies als Stream-Fehler (Abschnitt 5.4.2) vom Typ PROTOCOL_ERROR oder REFUSED_STREAM behandeln. Die Wahl des Fehlercodes bestimmt, ob der Endpunkt automatisches Wiederholen aktivieren möchte (siehe Abschnitt 8.7 für Details).
Ein Endpunkt, der den Wert von SETTINGS_MAX_CONCURRENT_STREAMS auf einen Wert reduzieren möchte, der unter der aktuellen Anzahl offener Streams liegt, kann entweder Streams schließen, die den neuen Wert überschreiten, oder Streams abschließen lassen.
5.2. Flusskontrolle
Die Verwendung von Streams für Multiplexing führt zu Konflikten bei der Nutzung der TCP-Verbindung, was zu blockierten Streams führt. Ein Flusskontrollschema stellt sicher, dass Streams auf derselben Verbindung sich nicht destruktiv gegenseitig beeinträchtigen. Flusskontrolle wird sowohl für einzelne Streams als auch für die Verbindung als Ganzes verwendet.
HTTP/2 bietet Flusskontrolle durch Verwendung des WINDOW_UPDATE-Frames (Abschnitt 6.9).
5.2.1. Flusskontroll-Prinzipien
Die HTTP/2-Stream-Flusskontrolle zielt darauf ab, eine Vielzahl von Flusskontroll-Algorithmen zu ermöglichen, ohne Protokolländerungen zu erfordern. Flusskontrolle in HTTP/2 hat die folgenden Eigenschaften:
-
Flusskontrolle ist verbindungsspezifisch. HTTP/2-Flusskontrolle operiert zwischen den Endpunkten eines einzelnen Hops und nicht über den gesamten End-to-End-Pfad.
-
Flusskontrolle basiert auf WINDOW_UPDATE-Frames. Empfänger kündigen an, wie viele Oktette sie bereit sind, auf einem Stream und für die gesamte Verbindung zu empfangen. Dies ist ein kreditbasiertes Schema.
-
Flusskontrolle ist richtungsabhängig, wobei die Gesamtkontrolle vom Empfänger bereitgestellt wird. Ein Empfänger KANN wählen, eine beliebige Fenstergröße festzulegen, die er für jeden Stream und für die gesamte Verbindung wünscht. Ein Sender MUSS von einem Empfänger auferlegte Flusskontrollgrenzen respektieren. Clients, Server und Intermediäre kündigen alle unabhängig ihr Flusskontrollfenster als Empfänger an und halten sich beim Senden an die von ihrem Peer gesetzten Flusskontrollgrenzen.
-
Der Anfangswert für das Flusskontrollfenster beträgt 65.535 Oktette sowohl für neue Streams als auch für die gesamte Verbindung.
-
Der Frame-Typ bestimmt, ob Flusskontrolle auf einen Frame angewendet wird. Von den in diesem Dokument spezifizierten Frames unterliegen nur DATA-Frames der Flusskontrolle; alle anderen Frame-Typen verbrauchen keinen Platz im angekündigten Flusskontrollfenster. Dies stellt sicher, dass wichtige Kontroll-Frames nicht durch Flusskontrolle blockiert werden.
-
Ein Endpunkt kann wählen, seine eigene Flusskontrolle zu deaktivieren, aber ein Endpunkt kann Flusskontrollsignale von seinem Peer nicht ignorieren.
-
HTTP/2 definiert nur das Format und die Semantik des WINDOW_UPDATE-Frames (Abschnitt 6.9). Dieses Dokument legt nicht fest, wie ein Empfänger entscheidet, wann dieser Frame gesendet wird oder welcher Wert gesendet wird, noch spezifiziert es, wie ein Sender wählt, Pakete zu senden. Implementierungen können jeden Algorithmus wählen, der ihren Bedürfnissen entspricht.
Implementierungen sind auch verantwortlich für die Priorisierung des Sendens von Anfragen und Antworten, die Wahl, wie Head-of-Line-Blocking für Anfragen vermieden wird, und die Verwaltung der Erstellung neuer Streams. Algorithmus-Entscheidungen dafür könnten mit jedem Flusskontroll-Algorithmus interagieren.
5.2.2. Angemessene Verwendung von Flusskontrolle
Flusskontrolle ist definiert, um Endpunkte zu schützen, die unter Ressourcenbeschränkungen operieren. Beispielsweise muss ein Proxy Speicher zwischen vielen Verbindungen teilen und könnte auch eine langsame Upstream-Verbindung und eine schnelle Downstream-Verbindung haben. Flusskontrolle adressiert Fälle, in denen der Empfänger nicht in der Lage ist, Daten auf einem Stream zu verarbeiten, aber andere Streams in derselben Verbindung weiter verarbeiten möchte.
Bereitstellungen, die diese Fähigkeit nicht benötigen, können ein Flusskontrollfenster der maximalen Größe (2^31-1) ankündigen und dieses Fenster aufrechterhalten, indem sie einen WINDOW_UPDATE-Frame senden, wenn Daten empfangen werden. Dies deaktiviert effektiv die Flusskontrolle für diesen Empfänger. Umgekehrt unterliegt ein Sender immer dem vom Empfänger angekündigten Flusskontrollfenster.
Bereitstellungen mit beschränkten Ressourcen (z.B. Speicher) können Flusskontrolle einsetzen, um die Menge an Speicher zu begrenzen, die ein Peer verbrauchen kann. Beachten Sie jedoch, dass dies zu suboptimaler Nutzung verfügbarer Netzwerkressourcen führen kann, wenn Flusskontrolle ohne Kenntnis des Bandbreite * Verzögerung-Produkts aktiviert wird (siehe [RFC7323]).
Selbst mit vollem Bewusstsein des aktuellen Bandbreite * Verzögerung-Produkts kann die Implementierung von Flusskontrolle schwierig sein. Endpunkte MÜSSEN HTTP/2-Frames aus dem TCP-Empfangspuffer lesen und verarbeiten, sobald Daten verfügbar sind. Das Versäumnis, prompt zu lesen, könnte zu einem Deadlock führen, wenn kritische Frames wie WINDOW_UPDATE nicht gelesen und darauf reagiert wird. Das prompte Lesen von Frames setzt Endpunkte nicht Ressourcenerschöpfungsangriffen aus, da HTTP/2-Flusskontrolle Ressourcenverpflichtungen begrenzt.
5.2.3. Flusskontroll-Leistung
Wenn ein Endpunkt nicht sicherstellen kann, dass sein Peer immer verfügbaren Flusskontrollfenster-Platz hat, der größer ist als das Bandbreite * Verzögerung-Produkt des Peers auf dieser Verbindung, wird sein Empfangsdurchsatz durch HTTP/2-Flusskontrolle begrenzt. Dies führt zu degradierter Leistung.
Das rechtzeitige Senden von WINDOW_UPDATE-Frames kann die Leistung verbessern. Endpunkte werden die Notwendigkeit, den Empfangsdurchsatz zu verbessern, mit der Notwendigkeit, Ressourcenerschöpfungsrisiken zu managen, abwägen wollen und sollten Abschnitt 10.5 sorgfältig beachten, wenn sie ihre Strategie zur Verwaltung von Fenstergrößen definieren.
5.3. Priorisierung
In einem gemultiplexten Protokoll wie HTTP/2 kann die Priorisierung der Zuweisung von Bandbreite und Rechenressourcen an Streams entscheidend sein, um gute Leistung zu erzielen. Ein schlechtes Priorisierungsschema kann dazu führen, dass HTTP/2 schlechte Leistung bietet. Ohne Parallelität auf der TCP-Schicht könnte die Leistung erheblich schlechter sein als HTTP/1.1.
Ein gutes Priorisierungsschema profitiert von der Anwendung kontextuellen Wissens, wie dem Inhalt von Ressourcen, wie Ressourcen miteinander verbunden sind und wie diese Ressourcen von einem Peer verwendet werden. Insbesondere können Clients Wissen über die Priorität von Anfragen besitzen, das für die Server-Priorisierung relevant ist. In diesen Fällen kann es die Leistung verbessern, wenn Clients Prioritätsinformationen bereitstellen.
5.3.1. Hintergrund zur Priorität in RFC 7540
RFC 7540 definierte ein umfangreiches System zur Signalisierung der Priorität von Anfragen. Dieses System erwies sich jedoch als komplex und wurde nicht einheitlich implementiert.
Das flexible Schema bedeutete, dass es für Clients möglich war, Prioritäten auf sehr unterschiedliche Weise auszudrücken, mit wenig Konsistenz in den angenommenen Ansätzen. Für Server war die Implementierung generischer Unterstützung für das Schema komplex. Die Implementierung von Prioritäten war sowohl in Clients als auch in Servern uneinheitlich. Viele Server-Bereitstellungen ignorierten Client-Signale bei der Priorisierung ihrer Bearbeitung von Anfragen.
Kurz gesagt, die Prioritätssignalisierung in RFC 7540 [RFC7540] war nicht erfolgreich.
5.3.2. Prioritätssignalisierung in diesem Dokument
Diese Aktualisierung von HTTP/2 macht die in RFC 7540 [RFC7540] definierte Prioritätssignalisierung veraltet. Der Großteil des Textes zu Prioritätssignalen ist in diesem Dokument nicht enthalten. Die Beschreibung von Frame-Feldern und einige der obligatorischen Handhabung wird beibehalten, um sicherzustellen, dass Implementierungen dieses Dokuments mit Implementierungen interoperabel bleiben, die die in RFC 7540 beschriebene Prioritätssignalisierung verwenden.
Eine gründliche Beschreibung des RFC 7540-Prioritätsschemas bleibt in Abschnitt 5.3 von [RFC7540].
Die Signalisierung von Prioritätsinformationen ist in vielen Fällen notwendig, um gute Leistung zu erzielen. Wo die Signalisierung von Prioritätsinformationen wichtig ist, werden Endpunkte ermutigt, ein alternatives Schema zu verwenden, wie das in [HTTP-PRIORITY] beschriebene Schema.
Obwohl die Prioritätssignalisierung aus RFC 7540 nicht weit verbreitet angenommen wurde, können die von ihr bereitgestellten Informationen in Abwesenheit besserer Informationen dennoch nützlich sein. Endpunkte, die Prioritätssignale in HEADERS- oder PRIORITY-Frames empfangen, können davon profitieren, diese Informationen anzuwenden. Insbesondere würden Implementierungen, die diese Signale konsumieren, nicht davon profitieren, diese Prioritätssignale in Abwesenheit von Alternativen zu verwerfen.
Server SOLLTEN andere kontextuelle Informationen bei der Bestimmung der Priorität von Anfragen in Abwesenheit von Prioritätssignalen verwenden. Server KÖNNEN das vollständige Fehlen von Signalen als Hinweis darauf interpretieren, dass der Client die Funktion nicht implementiert hat. Die in Abschnitt 5.3.5 von [RFC7540] beschriebenen Standardwerte sind bekannt dafür, unter den meisten Bedingungen schlechte Leistung zu haben, und ihre Verwendung ist wahrscheinlich nicht beabsichtigt.
5.4. Fehlerbehandlung
HTTP/2-Framing erlaubt zwei Klassen von Fehlern:
-
Eine Fehlerbedingung, die die gesamte Verbindung unbrauchbar macht, ist ein Verbindungsfehler.
-
Ein Fehler in einem einzelnen Stream ist ein Stream-Fehler.
Eine Liste von Fehlercodes ist in Abschnitt 7 enthalten.
Es ist möglich, dass ein Endpunkt auf Frames stößt, die mehrere Fehler verursachen würden. Implementierungen KÖNNEN während der Verarbeitung mehrere Fehler entdecken, sollten aber höchstens einen Stream- und einen Verbindungsfehler melden.
Der erste für einen gegebenen Stream gemeldete Stream-Fehler verhindert, dass andere Fehler auf diesem Stream gemeldet werden. Im Vergleich dazu erlaubt das Protokoll mehrere GOAWAY-Frames, obwohl ein Endpunkt nur einen Typ von Verbindungsfehler melden SOLLTE, es sei denn, ein Fehler tritt während des graceful Shutdown auf. Wenn dies auftritt, KANN ein Endpunkt einen zusätzlichen GOAWAY-Frame mit dem neuen Fehlercode senden, zusätzlich zu jedem vorherigen GOAWAY, der NO_ERROR enthielt.
Wenn ein Endpunkt mehrere verschiedene Fehler erkennt, KANN es wählen, einen dieser Fehler zu melden. Wenn ein Frame einen Verbindungsfehler verursacht, MUSS dieser Fehler gemeldet werden. Zusätzlich KANN ein Endpunkt jeden anwendbaren Fehlercode verwenden, wenn es eine Fehlerbedingung erkennt; ein generischer Fehlercode (wie PROTOCOL_ERROR oder INTERNAL_ERROR) kann immer anstelle spezifischerer Fehlercodes verwendet werden.
5.4.1. Verbindungsfehlerbehandlung
Ein Verbindungsfehler ist jeder Fehler, der eine weitere Verarbeitung der Frame-Schicht verhindert oder einen Verbindungsstatus beschädigt.
Ein Endpunkt, der auf einen Verbindungsfehler stößt, SOLLTE zuerst einen GOAWAY-Frame (Abschnitt 6.8) mit dem Stream-Identifikator des letzten Streams senden, den es erfolgreich von seinem Peer empfangen hat. Der GOAWAY-Frame enthält einen Fehlercode (Abschnitt 7), der angibt, warum die Verbindung beendet wird. Nach dem Senden des GOAWAY-Frames für eine Fehlerbedingung MUSS der Endpunkt die TCP-Verbindung schließen.
Es ist möglich, dass der GOAWAY vom empfangenden Endpunkt nicht zuverlässig empfangen wird. Im Falle eines Verbindungsfehlers bietet GOAWAY nur einen Best-Effort-Versuch, mit dem Peer zu kommunizieren, warum die Verbindung beendet wird.
Ein Endpunkt kann eine Verbindung jederzeit beenden. Insbesondere KANN ein Endpunkt wählen, einen Stream-Fehler als Verbindungsfehler zu behandeln. Endpunkte SOLLTEN einen GOAWAY-Frame senden, wenn sie eine Verbindung beenden, sofern die Umstände dies erlauben.
5.4.2. Stream-Fehlerbehandlung
Ein Stream-Fehler ist ein Fehler, der sich auf einen bestimmten Stream bezieht und die Verarbeitung anderer Streams nicht beeinflusst.
Ein Endpunkt, der einen Stream-Fehler erkennt, sendet einen RST_STREAM-Frame (Abschnitt 6.4), der den Stream-Identifikator des Streams enthält, bei dem der Fehler aufgetreten ist. Der RST_STREAM-Frame enthält einen Fehlercode, der den Fehlertyp angibt.
Ein RST_STREAM ist der letzte Frame, den ein Endpunkt auf einem Stream senden kann. Der Peer, der den RST_STREAM-Frame sendet, MUSS darauf vorbereitet sein, alle Frames zu empfangen, die vom entfernten Peer gesendet oder zur Sendung in die Warteschlange gestellt wurden. Diese Frames können ignoriert werden, außer wo sie den Verbindungsstatus ändern (wie den für Feldsektionskomprimierung (Abschnitt 4.3) oder Flusskontrolle aufrechterhaltenen Zustand).
Normalerweise SOLLTE ein Endpunkt NICHT mehr als einen RST_STREAM-Frame für einen Stream senden. Ein Endpunkt KANN jedoch zusätzliche RST_STREAM-Frames senden, wenn es Frames auf einem geschlossenen Stream nach mehr als einer Roundtrip-Zeit empfängt. Dieses Verhalten ist erlaubt, um mit fehlerhaften Implementierungen umzugehen.
Um Schleifen zu vermeiden, DARF ein Endpunkt NICHT einen RST_STREAM als Antwort auf einen RST_STREAM-Frame senden.
5.4.3. Verbindungsbeendigung
Wenn die TCP-Verbindung geschlossen oder zurückgesetzt wird, während Streams im "open"- oder "half-closed"-Zustand verbleiben, können die betroffenen Streams nicht automatisch wiederholt werden (siehe Abschnitt 8.7 für Details).
5.5. Erweiterung von HTTP/2
HTTP/2 erlaubt Erweiterungen des Protokolls. Innerhalb der in diesem Abschnitt beschriebenen Einschränkungen können Protokollerweiterungen verwendet werden, um zusätzliche Dienste bereitzustellen oder jeden Aspekt des Protokolls zu ändern. Erweiterungen sind nur im Rahmen einer einzelnen HTTP/2-Verbindung wirksam.
Dies gilt für die in diesem Dokument definierten Protokollelemente. Dies beeinflusst nicht die bestehenden Optionen zur Erweiterung von HTTP, wie das Definieren neuer Methoden, Statuscodes oder Felder (siehe Abschnitt 16 von [HTTP]).
Erweiterungen dürfen neue Frame-Typen (Abschnitt 4.1), neue Einstellungen (Abschnitt 6.5) oder neue Fehlercodes (Abschnitt 7) verwenden. Registries zur Verwaltung dieser Erweiterungspunkte sind in Abschnitt 11 von [RFC7540] definiert.
Implementierungen MÜSSEN unbekannte oder nicht unterstützte Werte in allen erweiterbaren Protokollelementen ignorieren. Implementierungen MÜSSEN Frames verwerfen, die unbekannte oder nicht unterstützte Typen haben. Dies bedeutet, dass jeder dieser Erweiterungspunkte sicher von Erweiterungen ohne vorherige Vereinbarung oder Verhandlung verwendet werden kann. Erweiterungs-Frames, die in der Mitte eines Feldblocks (Abschnitt 4.3) erscheinen, sind jedoch nicht erlaubt; diese MÜSSEN als Verbindungsfehler (Abschnitt 5.4.1) vom Typ PROTOCOL_ERROR behandelt werden.
Erweiterungen SOLLTEN vermeiden, in diesem Dokument definierte Protokollelemente oder Elemente, für die kein Erweiterungsmechanismus definiert ist, zu ändern. Dies schließt Änderungen am Layout von Frames, Ergänzungen oder Änderungen an der Art und Weise, wie Frames zu HTTP-Nachrichten zusammengesetzt werden (Abschnitt 8.1), die Definition von Pseudo-Header-Feldern oder Änderungen an jedem Protokollelement ein, das ein konformer Endpunkt als Verbindungsfehler behandeln könnte (Abschnitt 5.4.1).
Eine Erweiterung, die bestehende Protokollelemente oder Zustände ändert, MUSS verhandelt werden, bevor sie verwendet wird. Beispielsweise kann eine Erweiterung, die das Layout des HEADERS-Frames ändert, nicht verwendet werden, bis der Peer ein positives Signal gegeben hat, dass dies akzeptabel ist. In diesem Fall könnte es auch notwendig sein, zu koordinieren, wann das überarbeitete Layout in Kraft tritt. Beispielsweise erfordert die Behandlung von Frames außer DATA-Frames als flusskontrolliert eine Änderung der Semantik, die beide Endpunkte verstehen müssen, sodass dies nur durch Verhandlung erfolgen kann.
Dieses Dokument schreibt keine spezifische Methode zur Verhandlung der Verwendung einer Erweiterung vor, bemerkt aber, dass eine Einstellung (Abschnitt 6.5.2) für diesen Zweck verwendet werden könnte. Wenn beide Peers einen Wert setzen, der die Bereitschaft zur Verwendung der Erweiterung anzeigt, kann die Erweiterung verwendet werden. Wenn eine Einstellung für die Erweiterungsverhandlung verwendet wird, MUSS der Anfangswert so definiert werden, dass die Erweiterung zunächst deaktiviert ist.
Kapitel 5 abgeschlossen!