HTTP bedingte Anfragen
HTTP hat ein Konzept von bedingten Anfragen, wobei das Ergebnis und sogar der Erfolg einer Anfrage durch den Vergleich der betroffenen Ressourcen mit einem Validator kontrolliert werden können. Diese Anfragen sind nützlich, um zwischengespeicherte Inhalte zu validieren, um sicherzustellen, dass sie nur abgerufen werden, wenn sie sich von der Kopie unterscheiden, die dem Browser bereits zur Verfügung steht. Bedingte Anfragen sind auch nützlich, um die Integrität eines Dokuments beim Fortsetzen eines Downloads zu gewährleisten oder um verlorene Updates zu verhindern, wenn ein Dokument auf dem Server hochgeladen oder geändert wird.
Prinzipien
HTTP-bedingte Anfragen sind Anfragen, die je nach Wert bestimmter Header unterschiedlich ausgeführt werden. Diese Header definieren eine Vorbedingung, und das Ergebnis der Anfrage wird unterschiedlich sein, je nachdem, ob die Vorbedingung erfüllt ist oder nicht.
Die unterschiedlichen Verhaltensweisen werden durch die Methode der verwendeten Anfrage und durch die Menge der für eine Vorbedingung verwendeten Header definiert:
- 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 schont die 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, das gleiche ist wie das auf dem Server gespeicherte.
Validatoren
Alle bedingten Header versuchen zu überprüfen, ob die auf dem Server gespeicherte Ressource mit einer bestimmten Version übereinstimmt. Um dies zu erreichen, müssen die bedingten Anfragen die Version der Ressource angeben. Da ein Vergleich der gesamten Ressource Byte für Byte unpraktikabel ist und nicht immer gewünscht wird, überträgt die Anfrage einen Wert, der die Version beschreibt. Solche Werte werden Validatoren genannt und sind von zwei Arten:
- das Datum der letzten Änderung des Dokuments, das last-modified Datum.
- eine undurchsichtige Zeichenkette, die jede Version eindeutig identifiziert, genannt der entity tag oder das ETag.
Der Vergleich von Versionen derselben Ressource ist ein wenig knifflig: In Abhängigkeit vom 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 User-Agent nur feststellen muss, ob zwei Ressourcen denselben Inhalt haben. Die Ressourcen können als gleich angesehen werden, auch wenn geringfügige Unterschiede bestehen, wie z. B. verschiedene Anzeigen oder ein Footer mit einem anderen Datum.
Die Art der Validierung ist unabhängig vom verwendeten Validator. Sowohl Last-Modified
als auch ETag
erlauben beide Arten der Validierung, obwohl der Komplexitätsgrad zu deren Implementierung auf der Serverseite variieren kann. HTTP verwendet standardmäßig eine starke Validierung und gibt an, wann eine schwache Validierung verwendet werden kann.
Starke Validierung
Die starke Validierung besteht darin, zu garantieren, dass die Ressource Byte für Byte identisch mit der Ressource 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 streng und kann auf Serverseite schwierig zu gewährleisten sein, sie garantiert jedoch jederzeit keinen Datenverlust, manchmal auf Kosten der Leistung.
Es ist ziemlich schwierig, einen eindeutigen Bezeichner für die starke Validierung mit Last-Modified
zu haben. Oft wird dies mit einem ETag
unter Verwendung des MD5-Hashes der Ressource (oder einer Ableitung) gemacht.
Hinweis:
Da eine Änderung der Inhaltskodierung eine Änderung eines ETags erfordert, ändern einige Server ETags beim Komprimieren von Antworten eines Ursprungsservers (z. B. Reverse-Proxy). Der Apache-Server fügt standardmäßig den Namen der Kompressionsmethode (-gzip
) zu ETags hinzu, aber dies ist mit der DeflateAlterETag
-Direktive konfigurierbar.
Schwache Validierung
Die schwache Validierung unterscheidet sich von der starken Validierung, da sie zwei Versionen des Dokuments als identisch betrachtet, wenn der Inhalt gleichwertig ist. Beispielsweise 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 werden. Diese gleichen zwei Versionen werden bei Verwendung der starken Validierung als unterschiedlich betrachtet. Der Aufbau eines ETag-Systems, das schwache Validierung verwendet, ist sehr nützlich zur Optimierung der Cache-Leistung, kann jedoch komplex sein, da es das Wissen um die Wichtigkeit der verschiedenen Elemente einer Seite erfordert.
Bedingte Header
Mehrere HTTP-Header, sogenannte bedingte Header, führen zu bedingten Anfragen. Diese sind:
If-Match
-
Erfolgreich, wenn das
ETag
der entfernten Ressource mit einem in diesem Header aufgeführten Wert übereinstimmt. Es führt eine starke Validierung durch. If-None-Match
-
Erfolgreich, wenn das
ETag
der entfernten Ressource von jedem in diesem Header aufgeführten Wert abweicht. 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 Datum. If-Unmodified-Since
-
Erfolgreich, wenn das
Last-Modified
-Datum der entfernten Ressource älter oder gleich dem in diesem Header angegebenen Datum ist. If-Range
-
Ähnlich wie
If-Match
oderIf-Unmodified-Since
, kann aber nur ein einzelnes ETag oder ein Datum haben. Wenn es fehlschlägt, schlägt die Range-Anfrage fehl, und anstelle einer206 Partial Content
-Antwort wird eine200 OK
-Antwort mit der gesamten Ressource zurückgegeben.
Anwendungsfälle
Cache-Aktualisierung
Der häufigste Anwendungsfall für bedingte Anfragen ist die Aktualisierung eines Caches. Mit einem leeren Cache oder ohne Cache wird die angeforderte Ressource mit einem Status von 200
OK
zurückgegeben.
Zusammen mit der Ressource werden die Validatoren in den Headern gesendet. In diesem Beispiel werden sowohl Last-Modified
als auch ETag
gesendet, es könnte jedoch genauso gut nur einer von ihnen gewesen sein. Diese Validatoren werden zusammen mit der Ressource (wie alle Header) zwischengespeichert und werden zur Erstellung bedingter Anfragen verwendet, sobald der Cache veraltet ist.
Solange der Cache nicht veraltet ist, werden überhaupt keine Anfragen gestellt. Aber sobald er veraltet ist, was hauptsächlich durch den Cache-Control
-Header kontrolliert wird, verwendet der Client nicht den zwischengespeicherten Wert direkt, sondern stellt eine bedingte Anfrage. Der Wert des Validators wird als Parameter der If-Modified-Since
- und If-None-Match
-Header verwendet.
Wenn sich die Ressource nicht geändert hat, sendet der Server eine 304
Not Modified
-Antwort zurück. Dadurch wird der Cache wieder frisch, und der Client verwendet die zwischengespeicherte Ressource. Obwohl eine Anfrage/Antwort-Rundreise stattfindet, die einige Ressourcen verbraucht, ist dies effizienter, als die gesamte Ressource erneut über das Netzwerk 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 ob die Anfrage nicht bedingt gewesen wäre). Der Client verwendet diese neue Ressource (und speichert sie zwischen).
Abgesehen von der Einstellung der Validatoren auf der Serverseite ist dieser Mechanismus transparent: Alle Browser verwalten einen Cache und senden solche bedingten Anfragen, ohne dass Webentwickler spezielle Arbeit leisten müssen.
Integrität eines Teildownloads
Das partielle Herunterladen von Dateien ist eine Funktionalität von HTTP, die es ermöglicht, vorherige Vorgänge fortzuführen und Bandbreite und Zeit zu sparen, indem die bereits erhaltenen Informationen erhalten bleiben:
Ein Server, der Teildownloads unterstützt, teilt dies mit, indem er den Accept-Ranges
-Header sendet. Sobald dies geschieht, kann der Client einen Download fortsetzen, indem er einen Range
-Header mit den fehlenden Bereichen sendet:
Das Prinzip ist einfach, aber es gibt ein potenzielles Problem: Wenn die heruntergeladene Ressource zwischen beiden Downloads geändert wurde, entsprechen die erhaltenen Bereiche zwei verschiedenen Versionen der Ressource 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 Methode nutzt If-Unmodified-Since
und If-Match
und der Server gibt einen Fehler zurück, wenn die Vorbedingung fehlschlägt; der Client startet dann den Download von Anfang an neu:
Selbst wenn diese Methode funktioniert, fügt sie eine zusätzliche Antwort/Anfrage-Austauschrunde 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, jedoch etwas weniger flexibel, da nur ein ETag in der Bedingung verwendet werden kann. Selten wird eine solche zusätzliche Flexibilität benötigt.
Vermeidung des verlorenen Update-Problems mit optimistischem Sperren
Eine häufige Operation in Webanwendungen ist das Aktualisieren eines entfernten Dokuments. Dies ist sehr verbreitet in jedem Dateisystem oder Quellkontrollanwendungen, aber jede Anwendung, die es erlaubt, entfernte Ressourcen zu speichern, 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 umsetzen. Der Client liest zuerst die Originaldateien, ändert sie und überträgt sie schließlich an den Server:
Leider wird es etwas ungenau, sobald wir die Gleichzeitigkeit berücksichtigen. Während ein Client seine neue Kopie der Ressource lokal modifiziert, kann ein zweiter Client dieselbe Ressource abrufen und dasselbe mit seiner Kopie tun. Was als nächstes passiert, ist sehr bedauerlich: Wenn sie zurück zum Server übertragen werden, werden die Änderungen des ersten Clients durch den nächsten Client-Upload verworfen, da dieser zweite Client die Änderungen des ersten Clients an der Ressource nicht kennt. Die Entscheidung, wer gewinnt, wird der anderen Partei nicht mitgeteilt. Welche Änderungen des Clients beibehalten werden, variiert mit der Geschwindigkeit, mit der sie übertragen werden; dies hängt von der Leistung der Clients, des Servers und sogar des Menschen ab, der das Dokument am Client bearbeitet. Der Gewinner ändert sich von einem Mal zum anderen. Dies ist eine 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 verärgern. Dennoch sollten verlorene Updates und Race Conditions vermieden werden. 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 (der von den meisten Wikis oder Quellkontrollsystemen verwendet wird). Das Konzept besteht darin, allen Clients zu erlauben, Kopien der Ressource zu erhalten, dann können sie sie lokal ändern und die Gleichzeitigkeit wird durch erfolgreiches Zulassen des ersten Clients zum Einreichen eines Updates kontrolliert. Alle nachfolgenden Aktualisierungen, die auf der jetzt veralteten Version der Ressource basieren, werden abgelehnt:
Dies wird unter Verwendung der If-Match
- oder If-Unmodified-Since
-Header implementiert. Wenn das ETag nicht mit der Originaldatei übereinstimmt oder wenn die Datei seit dem Erlangen geändert wurde, wird die Änderung mit einem 412 Precondition Failed
-Fehler abgelehnt. Es liegt dann am Client, mit dem Fehler umzugehen: entweder indem der Benutzer benachrichtigt wird, um erneut zu beginnen (diesmal auf der neuesten Version), oder indem dem Benutzer ein Diff beider Versionen angezeigt wird, um ihm zu helfen, zu entscheiden, welche Änderungen er behalten möchte.
Umgang mit dem ersten Upload einer Ressource
Der erste Upload einer Ressource ist ein Grenzfall des vorherigen. Wie jede Aktualisierung einer Ressource ist es einer Race Condition unterworfen, wenn zwei Clients versuchen, gleichzeitig zu operieren. Um dies zu verhindern, können bedingte Anfragen verwendet werden: indem If-None-Match
mit dem speziellen Wert *
, das alle ETags darstellt, hinzugefügt wird. Die Anfrage wird nur dann erfolgreich sein, wenn die Ressource zuvor nicht existiert hat:
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 eine Schlüsselkomponente von HTTP und ermöglichen den Aufbau effizienter und komplexer Anwendungen. Für das Zwischenspeichern oder das Fortsetzen von Downloads besteht die einzige Arbeit für Webmaster darin, den Server korrekt zu konfigurieren; das Setzen korrekter ETags in einigen Umgebungen kann kompliziert sein. Einmal erreicht, wird der Browser die erwarteten bedingten Anfragen ausführen.
Für Sperrmechanismen ist es das Gegenteil: Webentwickler müssen eine Anfrage mit den richtigen Headern stellen, während Webmaster meist darauf vertrauen können, dass die Anwendung die Prüfungen für sie durchführt.
In beiden Fällen ist klar: Bedingte Anfragen sind ein fundamentales Merkmal des Webs.
Siehe auch
304 Not Modified
If-None-Match
- Apache Server
mod_deflate.c
transformiert ETags während der Kompression