HTTP-Bedingungsanfragen
HTTP hat ein Konzept der Bedingungsanfragen, bei denen das Ergebnis und sogar der Erfolg einer Anfrage durch den Vergleich der betroffenen Ressourcen mit einem Validierer gesteuert werden können. Diese Anfragen sind nützlich, um zwischengespeicherte Inhalte zu validieren und sicherzustellen, dass sie nur abgerufen werden, wenn sie sich von der bereits im Browser verfügbaren Kopie unterscheiden. Bedingte Anfragen sind auch nützlich, um die Integrität eines Dokuments beim Fortsetzen eines Downloads zu gewährleisten oder zu verhindern, dass Aktualisierungen beim Hochladen oder Ändern eines Dokuments auf dem Server verloren gehen.
Grundprinzipien
HTTP-Bedingungsanfragen sind Anfragen, die unterschiedlich ausgeführt werden, abhängig vom Wert spezifischer Header. Diese Header definieren eine Vorbedingung, und das Ergebnis der Anfrage wird unterschiedlich sein, je nachdem, ob die Vorbedingung erfüllt wird oder nicht.
Die unterschiedlichen Verhaltensweisen werden durch die verwendete Methode der Anfrage und durch die für eine Vorbedingung verwendeten Header bestimmt:
- für sichere Methoden wie
GET
, die normalerweise versuchen, ein Dokument abzurufen, kann die bedingte Anfrage verwendet werden, um das Dokument nur dann zurückzusenden, wenn es relevant ist. Dies spart daher Bandbreite. - für unsichere Methoden wie
PUT
, die normalerweise ein Dokument hochladen, kann die bedingte Anfrage verwendet werden, um das Dokument nur dann hochzuladen, wenn das Original, auf dem es basiert, mit dem auf dem Server gespeicherten übereinstimmt.
Validierer
Alle bedingten Header versuchen zu überprüfen, ob die auf dem Server gespeicherte Ressource mit einer bestimmten Version übereinstimmt. Dazu müssen die Bedingungsanfragen die Version der Ressource angeben. Da es unpraktisch ist, die gesamte Ressource Byte für Byte zu vergleichen, und dies nicht immer gewünscht ist, überträgt die Anfrage einen Wert, der die Version beschreibt. Solche Werte werden Validierer genannt und sind von zwei Arten:
- das Datum der letzten Änderung des Dokuments, das Last-Modified-Datum.
- eine undurchsichtige Zeichenfolge, die jede Version eindeutig identifiziert, genannt Entity-Tag oder ETag.
Der Vergleich von Versionen derselben Ressource ist ein wenig kompliziert: Je nach Kontext gibt es zwei Arten von Gleichheitsprüfungen:
- Starke Validierung wird verwendet, wenn eine Byte-für-Byte-Identität erwartet wird, beispielsweise beim Fortsetzen eines Downloads.
- Schwache Validierung wird verwendet, wenn der Benutzer-Agent nur feststellen muss, ob zwei Ressourcen denselben Inhalt haben. Die Ressourcen können als gleich betrachtet werden, selbst wenn kleinere Unterschiede existieren, wie unterschiedliche Anzeigen oder ein Footer mit einem anderen Datum.
Die Art der Validierung ist unabhängig vom verwendeten Validierer. Sowohl Last-Modified
als auch ETag
erlauben beide Arten der Validierung, obwohl die Komplexität der Implementierung auf der Serverseite variieren kann. HTTP verwendet standardmäßig die starke Validierung und spezifiziert, wann die schwache Validierung verwendet werden kann.
Starke Validierung
Die starke Validierung besteht darin, zu garantieren, dass die Ressource Byte für Byte identisch mit derjenigen ist, mit der sie verglichen wird. Dies ist für einige bedingte Header zwingend erforderlich und der Standard für die anderen. Die starke Validierung ist sehr strikt und kann auf der Serverseite schwer zu garantieren sein, aber sie garantiert zu keinem Zeitpunkt einen Datenverlust, manchmal auf Kosten der Leistung.
Es ist ziemlich schwierig, einen eindeutigen Identifikator für die starke Validierung mit Last-Modified
zu haben. Dies wird häufig mit einem ETag
unter Verwendung des MD5-Hashes der Ressource (oder eines Derivats) erreicht.
Hinweis:
Da eine Änderung der Inhaltskodierung eine Änderung der ETag erfordert, ändern einige Server die ETags beim Komprimieren von Antworten von einem Quellserver (zum Beispiel Reverse-Proxys).
Der Apache-Server fügt standardmäßig den Namen der Komprimierungsmethode (-gzip
) zu ETags hinzu, dies kann jedoch mit der Direktive DeflateAlterETag
konfiguriert werden.
Schwache Validierung
Die schwache Validierung unterscheidet sich von der starken Validierung, da sie zwei Versionen des Dokuments als identisch betrachtet, wenn der Inhalt äquivalent ist. Zum Beispiel würde eine Seite, die sich von einer anderen nur durch ein anderes Datum im Footer oder unterschiedliche Werbung unterscheidet, mit schwacher Validierung als identisch angesehen. Dieselben zwei Versionen gelten bei starker Validierung als verschieden. Der Aufbau eines Systems von ETags, das schwache Validierung verwendet, ist sehr nützlich zur Optimierung der Cache-Leistung, kann aber komplex sein, da es die Bedeutung der verschiedenen Elemente einer Seite kennt.
Bedingte Header
Mehrere HTTP-Header, sogenannte bedingte Header, führen zu bedingten Anfragen. Diese sind:
If-Match
-
Erfolgreich, wenn das
ETag
der entfernten Ressource gleich einem der in diesem Header aufgelisteten ist. Es führt eine starke Validierung durch. If-None-Match
-
Erfolgreich, wenn das
ETag
der entfernten Ressource ungleich jedem der in diesem Header aufgelisteten ist. Es führt eine schwache Validierung durch. If-Modified-Since
-
Erfolgreich, wenn das
Last-Modified
-Datum der entfernten Ressource neuer ist als das in diesem Header angegebene. If-Unmodified-Since
-
Erfolgreich, wenn das
Last-Modified
-Datum der entfernten Ressource älter oder gleich dem in diesem Header angegebenen ist. If-Range
-
Ähnlich wie
If-Match
oderIf-Unmodified-Since
, kann jedoch nur ein einziges ETag oder ein Datum haben. Wenn es fehlschlägt, schlägt die Bereichsanfrage fehl, und anstelle einer206 Partial Content
Antwort wird eine200 OK
Antwort mit der vollständigen Ressource gesendet.
Anwendungsfälle
Cache-Aktualisierung
Der häufigste Anwendungsfall für bedingte Anfragen ist die Aktualisierung eines Caches. Bei einem leeren Cache oder ohne Cache wird die angeforderte Ressource mit einem Status von 200
OK
zurückgesendet.
Zusammen mit der Ressource werden die Validierer in den Headern gesendet. In diesem Beispiel werden sowohl Last-Modified
als auch ETag
gesendet, es könnte genauso gut nur einer von beiden sein. Diese Validierer werden zusammen mit der Ressource (wie alle Header) zwischengespeichert und werden verwendet, um bedingte Anfragen zu erstellen, sobald der Cache veraltet ist.
Solange der Cache nicht veraltet ist, werden überhaupt keine Anfragen ausgeführt. Aber einmal veraltet, hauptsächlich gesteuert durch den Cache-Control
-Header, verwendet der Client nicht direkt den zwischengespeicherten Wert, sondern stellt eine bedingte Anfrage. Der Wert des Validierers wird als Parameter der Header If-Modified-Since
und If-None-Match
verwendet.
Wenn sich die Ressource nicht geändert hat, sendet der Server eine 304
Not Modified
Antwort zurück. Dies macht den Cache wieder aktuell, und der Client verwendet die zwischengespeicherte Ressource. Obwohl es einen Antwort-/Anforderungs-Rundlauf gibt, der einige Ressourcen verbraucht, ist dies effizienter, als die gesamte Ressource erneut zu übertragen.
Wenn sich die Ressource geändert hat, sendet der Server einfach eine 200 OK
-Antwort mit der neuen Version der Ressource zurück (als wäre die Anfrage nicht bedingt gewesen). Der Client verwendet diese neue Ressource (und speichert sie im Cache).
Neben der Einstellung der Validierer auf der Serverseite ist dieser Mechanismus transparent: Alle Browser verwalten einen Cache und senden solche bedingten Anfragen, ohne dass die Webentwickler besondere Arbeiten ausführen müssen.
Integrität eines teilweisen Downloads
Das teilweise Herunterladen von Dateien ist eine Funktionalität von HTTP, die es ermöglicht, frühere Vorgänge fortzusetzen, Bandbreite und Zeit zu sparen, indem die bereits erhaltenen Informationen beibehalten werden:
Ein Server, der Teil-Downloads unterstützt, signalisiert dies durch das Senden des Accept-Ranges
Headers. Sobald dies geschieht, kann der Client einen Download fortsetzen, indem er einen Ranges
Header mit den fehlenden Bereichen sendet:
Das Prinzip ist einfach, jedoch gibt es ein potenzielles Problem: Wenn die heruntergeladene Ressource zwischen beiden Downloads geändert wurde, werden die erhaltenen Bereiche zwei verschiedenen Versionen der Ressource entsprechen, und das endgültige Dokument wird beschädigt sein.
Um dies zu verhindern, werden bedingte Anfragen verwendet. Für Bereiche gibt es zwei Möglichkeiten, dies zu tun. Die flexiblere Möglichkeit verwendet If-Unmodified-Since
und If-Match
, wobei der Server einen Fehler zurückgibt, wenn die Vorbedingung fehlschlägt; der Client startet dann den Download von Anfang an neu:
Auch wenn diese Methode funktioniert, fügt sie einen zusätzlichen Antwort-/Anforderungsaustausch hinzu, wenn das Dokument geändert wurde. Dies beeinträchtigt die Leistung, und HTTP hat einen speziellen Header, um dieses Szenario zu vermeiden: If-Range
:
Diese Lösung ist effizienter, aber etwas weniger flexibel, da nur ein ETag in der Bedingung verwendet werden kann. Solch zusätzliche Flexibilität wird selten benötigt.
Vermeidung des "verlorenen Aktualisierungsproblems" mit optimistischem Sperren
Eine häufige Operation in Webanwendungen ist das Aktualisieren eines entfernten Dokuments. Dies ist in jedem Dateisystem oder Quellkontrollanwendung sehr üblich, aber jede Anwendung, die das Speichern von entfernten Ressourcen ermöglicht, benötigt einen solchen Mechanismus. Gewöhnliche Websites, wie Wikis und andere CMS, haben einen solchen Bedarf.
Mit der PUT
-Methode können Sie dies implementieren. Der Client liest zuerst die Originaldateien, ändert sie und überträgt sie schließlich auf den Server:
Leider werden die Dinge ein wenig ungenau, sobald wir die Gleichzeitigkeit berücksichtigen. Während ein Client seine neue Kopie der Ressource lokal bearbeitet, kann ein zweiter Client dieselbe Ressource abrufen und dasselbe an seiner Kopie tun. Was als nächstes passiert, ist sehr bedauerlich: Wenn sie ihre Änderungen auf den Server übertragen, werden die Änderungen des ersten Clients durch die nächste Clientübertragung verworfen, da dieser zweite Client nichts von den Änderungen des ersten Clients an der Ressource weiß. Die Entscheidung, wer gewinnt, wird der anderen Partei nicht mitgeteilt. Welche Änderungen des Clients beibehalten werden, variiert mit der Geschwindigkeit, mit der sie ihre Änderungen übertragen; dies hängt von der Leistung der Clients, des Servers und sogar des Benutzers ab, der das Dokument beim Client bearbeitet. Der Gewinner wechselt von Mal zu Mal. Diese Situation ist ein Race Condition und führt zu problematischen Verhaltensweisen, die schwer zu erkennen und zu debuggen sind:
Es gibt keinen Weg, mit diesem Problem umzugehen, ohne einen der beiden Clients zu stören. Verlorene Aktualisierungen und Race Conditions sind jedoch zu vermeiden. Wir wollen vorhersehbare Ergebnisse und erwarten, dass die Clients benachrichtigt werden, wenn ihre Änderungen abgelehnt werden.
Bedingte Anfragen ermöglichen die Implementierung des optimistischen Sperralgorithmus (verwendet von den meisten Wikis oder Quellkontrollsystemen). Das Konzept besteht darin, dass alle Clients Kopien der Ressource erhalten, sie dann lokal bearbeiten und die Gleichzeitigkeit kontrollieren, indem der erste Client erfolgreich ein Update übermitteln darf. Alle nachfolgenden Updates, die auf der nun veralteten Version der Ressource basieren, werden abgelehnt:
Dies wird mit den Headern If-Match
oder If-Unmodified-Since
implementiert. Wenn das ETag nicht mit der Originaldatei übereinstimmt oder wenn die Datei seit dem Abrufen geändert wurde, wird die Änderung mit einem 412 Precondition Failed
-Fehler abgelehnt. Es liegt dann am Client, mit dem Fehler umzugehen: entweder indem er den Nutzer benachrichtigt, erneut zu beginnen (diesmal mit der neuesten Version), oder indem er dem Nutzer einen Diff beider Versionen zeigt, um ihm zu helfen, zu entscheiden, welche Änderungen beibehalten werden sollen.
Umgang mit dem ersten Upload einer Ressource
Der erste Upload einer Ressource ist ein Grenzfall des Vorherigen. Wie jede Aktualisierung einer Ressource unterliegt er einem Race Condition, wenn zwei Clients versuchen, es zu ähnlichen Zeiten durchzuführen. Um dies zu verhindern, können bedingte Anfragen verwendet werden: durch das Hinzufügen von If-None-Match
mit dem speziellen Wert *
, der jedes ETag repräsentiert. Die Anfrage wird nur dann erfolgreich sein, wenn die Ressource vorher nicht vorhanden war:
If-None-Match
funktioniert nur mit HTTP/1.1 (und später) konformen Servern. Wenn Sie sich nicht sicher sind, ob der Server konform ist, müssen Sie zuerst eine HEAD
-Anfrage an die Ressource stellen, um dies zu überprüfen.
Fazit
Bedingte Anfragen sind ein Schlüsselfeature von HTTP und ermöglichen den Aufbau effizienter und komplexer Anwendungen. Für das Caching oder die Wiederaufnahme von Downloads besteht die einzige Arbeit, die für Webmaster erforderlich ist, darin, den Server korrekt zu konfigurieren; das Einstellen korrekter ETags kann in einigen Umgebungen knifflig sein. Sobald dies erreicht ist, werden die erwarteten bedingten Anfragen vom Browser bereitgestellt.
Für Sperrmechanismen ist es umgekehrt: Webentwickler müssen eine Anfrage mit den richtigen Headern ausstellen, während Webmaster sich hauptsächlich darauf verlassen können, dass die Anwendung die Überprüfungen für sie durchführt.
In beiden Fällen ist klar, dass bedingte Anfragen ein fundamentales Merkmal hinter dem Web sind.
Siehe auch
304 Not Modified
If-None-Match
- Apache Server
mod_deflate.c
transformiert ETags während der Komprimierung