Den Vary-Header verstehen
Veröffentlicht: 2022-03-10Der Vary-HTTP-Header wird täglich in Milliarden von HTTP-Antworten gesendet. Aber seine Verwendung hat seine ursprüngliche Vision nie erfüllt, und viele Entwickler missverstehen, was es tut, oder erkennen nicht einmal, dass ihr Webserver es sendet. Mit dem Erscheinen der Kundenhinweise, Varianten und Schlüsselspezifikationen erhalten vielfältige Antworten einen Neuanfang.
Was ist Vary?
Die Geschichte von Vary beginnt mit einer schönen Idee, wie das Web funktionieren sollte. Im Prinzip stellt eine URL keine Webseite dar, sondern eine konzeptionelle Ressource, wie Ihren Kontoauszug. Stellen Sie sich vor, Sie möchten Ihren Kontoauszug sehen: Sie gehen auf bank.com
und senden eine GET
-Anforderung für /statement
. So weit, so gut, aber Sie haben nicht angegeben, in welchem Format Sie die Anweisung haben möchten. Aus diesem Grund fügt Ihr Browser auch so etwas wie Accept: text/html
in Ihre Anfrage ein. Zumindest theoretisch bedeutet dies, dass Sie stattdessen Accept: text/csv
sagen und dieselbe Ressource in einem anderen Format erhalten könnten.
Da dieselbe URL jetzt basierend auf dem Wert des Accept
Headers unterschiedliche Antworten erzeugt, muss jeder Cache, der diese Antwort speichert, wissen, dass dieser Header wichtig ist. Der Server sagt uns, dass der Accept
-Header so wichtig ist:
Vary: Accept
Sie könnten dies folgendermaßen lesen: „Diese Antwort variiert je nach Wert des Accept
Headers Ihrer Anfrage.“
Dies funktioniert im Grunde nicht im heutigen Web. Die sogenannte „Content Negotiation“ war eine großartige Idee, aber sie ist gescheitert. Dies bedeutet jedoch nicht, dass Vary
nutzlos ist. Ein ansehnlicher Teil der Seiten, die Sie im Internet besuchen, tragen in der Antwort einen Vary
-Header – vielleicht haben Ihre Websites ihn auch, und Sie wissen es nicht. Wenn der Header also nicht für die Inhaltsaushandlung funktioniert, warum ist er dann immer noch so beliebt und wie gehen Browser damit um? Lass uns einen Blick darauf werfen.
Ich habe bereits über Vary in Bezug auf Content Delivery Networks (CDNs) geschrieben, diese zwischengeschalteten Caches (wie Fastly, CloudFront und Akamai), die Sie zwischen Ihren Servern und dem Benutzer platzieren können. Browser müssen auch Vary-Regeln verstehen und darauf reagieren, und die Art und Weise, wie sie dies tun, unterscheidet sich von der Art und Weise, wie Vary von CDNs behandelt wird. In diesem Beitrag werde ich die düstere Welt der Cache-Variation im Browser erkunden.
Heutige Anwendungsfälle zum Variieren im Browser
Wie wir bereits gesehen haben, besteht die traditionelle Verwendung von Vary darin, Inhaltsverhandlungen mit den Headern Accept
, Accept-Language
und Accept-Encoding
durchzuführen, und historisch gesehen sind die ersten beiden kläglich gescheitert. Das Variieren von Accept-Encoding
zum Liefern von Gzip- oder Brotli-komprimierten Antworten funktioniert, sofern unterstützt, meistens recht gut, aber alle Browser unterstützen heutzutage Gzip, das ist also nicht sehr aufregend.
Wie wäre es mit einigen dieser Szenarien?
- Wir möchten Bilder bereitstellen, die genau die Breite des Bildschirms des Benutzers haben. Wenn der Benutzer die Größe seines Browsers ändert, würden wir neue Bilder herunterladen (je nach Client-Hinweisen).
- Wenn sich der Benutzer abmeldet, möchten wir vermeiden, dass Seiten verwendet werden, die zwischengespeichert wurden, während er angemeldet war (unter Verwendung eines Cookies als
Key
). - Benutzer von Browsern, die das WebP-Bildformat unterstützen, sollten WebP-Bilder erhalten; Andernfalls sollten sie JPEGs erhalten.
- Bei Verwendung eines Browsers auf einem Bildschirm mit hoher Dichte sollte der Benutzer 2x Bilder erhalten. Wenn sie das Browserfenster auf einen Bildschirm mit Standarddichte verschieben und aktualisieren, sollten sie 1x-Bilder erhalten.
Caches ganz nach unten
Im Gegensatz zu Edge-Caches, die als ein gigantischer Cache fungieren, der von allen Benutzern gemeinsam genutzt wird, ist der Browser nur für einen Benutzer, aber er hat viele verschiedene Caches für unterschiedliche, spezifische Zwecke:
Einige davon sind ziemlich neu, und genau zu verstehen, aus welchem Cache der Inhalt geladen wird, ist eine komplexe Berechnung, die von Entwicklertools nicht gut unterstützt wird. Folgendes tun diese Caches:
- Bild-Cache
Dies ist ein seitenbezogener Cache, der decodierte Bilddaten speichert, sodass der Browser beispielsweise, wenn Sie dasselbe Bild mehrmals auf einer Seite einfügen, es nur einmal herunterladen und decodieren muss. - Cache vorladen
Dies ist auch seitenbezogen und speichert alles, was vorab in einemLink
-Header oder einem<link rel="preload">
-Tag geladen wurde, selbst wenn die Ressource normalerweise nicht zwischengespeichert werden kann. Wie der Bild-Cache wird der Preload-Cache zerstört, wenn der Benutzer von der Seite wegnavigiert. - Service-Worker-Cache-API
Dies stellt ein Cache-Back-End mit einer programmierbaren Schnittstelle bereit; Daher wird hier nichts gespeichert, es sei denn, Sie legen es ausdrücklich per JavaScript-Code in einem Servicemitarbeiter dort ab. Es wird auch nur überprüft, wenn Sie dies explizit in einem Service-Worker-fetch
-Handler tun. Der Service-Worker-Cache ist ursprungsbezogen und, obwohl nicht garantiert, dass er dauerhaft ist, dauerhafter als der HTTP-Cache des Browsers. - HTTP-Cache
Dies ist der Hauptcache, mit dem die Leute am vertrautesten sind. Es ist der einzige Cache, der auf HTTP-Level-Cache-Header wieCache-Control
achtet und diese mit den eigenen heuristischen Regeln des Browsers kombiniert, um zu bestimmen, ob und wie lange etwas zwischengespeichert werden soll. Es hat den größten Anwendungsbereich, da es von allen Websites geteilt wird; Wenn also zwei unabhängige Websites dasselbe Asset laden (z. B. Google Analytics), teilen sie möglicherweise denselben Cache-Treffer. - HTTP/2-Push-Cache (oder „H2-Push-Cache“)
Diese befindet sich bei der Verbindung und speichert Objekte, die vom Server gepusht, aber noch nicht von einer Seite angefordert wurden, die die Verbindung verwendet. Es ist auf Seiten beschränkt, die eine bestimmte Verbindung verwenden, was im Wesentlichen der Beschränkung auf einen einzelnen Ursprung entspricht, aber es wird auch zerstört, wenn die Verbindung geschlossen wird.
Von diesen sind der HTTP-Cache und der Service-Worker-Cache am besten definiert. Was die Bild- und Preload-Caches betrifft, so implementieren einige Browser sie möglicherweise als einzelnen „Speicher-Cache“, der an das Rendern einer bestimmten Navigation gebunden ist, aber das mentale Modell, das ich hier beschreibe, ist immer noch die richtige Art, über den Prozess nachzudenken. Sehen Sie sich bei Interesse den Spezifikationshinweis zum preload
an. Im Fall des H2-Server-Pushs bleibt die Diskussion über das Schicksal dieses Caches aktiv.
Die Reihenfolge, in der eine Anfrage diese Caches überprüft, bevor sie sich auf das Netzwerk wagt, ist wichtig, da eine Anfrage etwas von einer äußeren Caching-Schicht in eine innere ziehen könnte. Wenn Ihr HTTP/2-Server beispielsweise ein Stylesheet zusammen mit einer Seite, die es benötigt, pusht und diese Seite das Stylesheet auch mit einem <link rel="preload">
-Tag vorlädt, dann berührt das Stylesheet drei Caches im Browser. Zuerst sitzt es im H2-Push-Cache und wartet darauf, angefordert zu werden. Wenn der Browser die Seite rendert und zum preload
-Tag gelangt, zieht er das Stylesheet aus dem Push-Cache durch den HTTP-Cache (der es je nach Cache-Control
Header des Stylesheets speichern kann) und speichert es es im Preload-Cache.
Einführung von Vary als Validator
OK, was passiert also, wenn wir diese Situation nehmen und Vary zum Mix hinzufügen?
Im Gegensatz zu zwischengeschalteten Caches (z. B. CDNs) implementieren Browser normalerweise nicht die Fähigkeit, mehrere Variationen pro URL zu speichern . Der Grund dafür ist, dass die Dinge, für die wir Vary
normalerweise verwenden (hauptsächlich Accept-Encoding
und Accept-Language
), sich im Kontext eines einzelnen Benutzers nicht häufig ändern. Accept-Encoding
kann sich bei einem Browser-Upgrade ändern (aber wahrscheinlich nicht), und Accept-Language
würde sich höchstwahrscheinlich nur ändern, wenn Sie die Sprachgebietsschemaeinstellungen Ihres Betriebssystems bearbeiten. Es ist auch viel einfacher, Vary auf diese Weise zu implementieren, obwohl einige Spezifikationsautoren glauben, dass dies ein Fehler war.
Meistens ist es für einen Browser kein großer Verlust, nur eine Variante zu speichern, aber es ist wichtig, dass wir nicht versehentlich eine Variante verwenden, die nicht mehr gültig ist, wenn sich die „variierten“ Daten ändern.
Der Kompromiss besteht darin, Vary
als Validator und nicht als Schlüssel zu behandeln. Browser berechnen Cache-Schlüssel auf die übliche Weise (im Wesentlichen unter Verwendung der URL), und wenn sie einen Treffer erzielen, prüfen sie, ob die Anfrage alle Vary-Regeln erfüllt, die in die zwischengespeicherte Antwort eingebrannt sind. Wenn dies nicht der Fall ist, behandelt der Browser die Anforderung als Fehlschlag im Cache und geht zur nächsten Cache-Schicht oder zum Netzwerk über. Wenn eine neue Antwort eingeht, wird die zwischengespeicherte Version überschrieben, obwohl es sich technisch gesehen um eine andere Variante handelt.
Variierendes Verhalten demonstrieren
Um zu demonstrieren, wie Vary
gehandhabt wird, habe ich eine kleine Testsuite erstellt. Der Test lädt eine Reihe verschiedener URLs mit unterschiedlichen Headern und erkennt, ob die Anfrage den Cache erreicht hat oder nicht. Ursprünglich habe ich dafür ResourceTiming verwendet, aber aus Gründen der besseren Kompatibilität habe ich schließlich dazu übergegangen, nur zu messen, wie lange die Anforderung zum Abschließen dauert (und den serverseitigen Antworten absichtlich eine Verzögerung von 1 Sekunde hinzugefügt, um den Unterschied wirklich deutlich zu machen).
Schauen wir uns die einzelnen Cache-Typen an und wie Vary
funktionieren sollte und ob es tatsächlich so funktioniert. Für jeden Test zeige ich hier, ob wir mit einem Ergebnis aus dem Cache rechnen müssen („HIT“ versus „MISS“) und was tatsächlich passiert ist.
Vorladen
Vorabladen wird derzeit nur in Chrome unterstützt, wo vorab geladene Antworten in einem Speichercache gespeichert werden, bis sie von der Seite benötigt werden. Die Antworten füllen auch den HTTP-Cache auf ihrem Weg zum Preload-Cache, wenn sie HTTP-cachefähig sind. Da das Angeben von Anforderungsheadern mit einem Preload unmöglich ist und der Preload-Cache nur so lange wie die Seite dauert, ist das Testen schwierig, aber wir können zumindest sehen, dass Objekte mit einem Vary
-Header erfolgreich vorgeladen werden:
Service-Worker-Cache-API
Chrome und Firefox unterstützen Service Worker, und bei der Entwicklung der Service Worker-Spezifikation wollten die Autoren beheben, was sie als fehlerhafte Implementierungen in Browsern betrachteten, damit Vary
im Browser mehr wie CDNs funktioniert. Das bedeutet, dass der Browser zwar nur eine Variante im HTTP-Cache speichern soll, aber mehrere Varianten in der Cache-API speichern soll. Firefox (54) macht dies korrekt, während Chrome die gleiche Vary-as-Validator-Logik verwendet, die es für den HTTP-Cache verwendet (der Fehler wird verfolgt).
HTTP-Cache
Der Haupt-HTTP-Cache sollte Vary
beobachten und tut dies konsistent (als Validator) in allen Browsern. Viel, viel mehr dazu finden Sie in Mark Nottinghams Beitrag „State of Browser Caching, Revisited“.
HTTP/2-Push-Cache
Vary
sollte beobachtet werden, aber in der Praxis respektiert es kein Browser tatsächlich, und Browser passen Push-Antworten gerne an Anfragen an, die zufällige Werte in Headern enthalten, auf denen die Antworten variieren.
Die Falte „304 (nicht modifiziert)“.
Der HTTP-Antwortstatus „304 (Not Modified)“ ist faszinierend. Unser „lieber Führer“, Artur Bergman, wies mich auf dieses Juwel in der HTTP-Caching-Spezifikation hin (Hervorhebung von mir):
Der Server, der eine 304-Antwort generiert, muss eines der folgenden Header-Felder generieren, die in einer 200 (OK)-Antwort auf dieselbe Anfrage gesendet worden wären:
Cache-Control
,Content-Location
,Date
,ETag
,Expires
undVary
.
Warum würde eine 304
Antwort einen Vary
-Header zurückgeben? Die Handlung verdichtet sich, wenn Sie darüber lesen, was Sie tun sollen, wenn Sie eine 304
Antwort erhalten, die diese Header enthält:
Wenn eine gespeicherte Antwort zur Aktualisierung ausgewählt wird, muss der Cache \[…] andere Header-Felder verwenden, die in der 304-Antwort (Not Modified) bereitgestellt werden, um alle Instanzen der entsprechenden Header-Felder in der gespeicherten Antwort zu ersetzen.
Warte was? Wenn sich also der Vary
-Header des 304
von demjenigen im vorhandenen zwischengespeicherten Objekt unterscheidet, sollen wir das zwischengespeicherte Objekt aktualisieren? Aber das könnte bedeuten, dass es nicht mehr mit unserer Anfrage übereinstimmt!
In diesem Szenario scheint Ihnen der 304
auf den ersten Blick gleichzeitig zu sagen, dass Sie die zwischengespeicherte Version verwenden können und nicht. Wenn der Server wirklich nicht wollte, dass Sie die zwischengespeicherte Version verwenden, hätte er natürlich eine 200
gesendet, keine 304
; Daher sollte die zwischengespeicherte Version auf jeden Fall verwendet werden – aber nachdem die Aktualisierungen darauf angewendet wurden, wird sie möglicherweise nicht mehr für eine zukünftige Anfrage verwendet, die mit derjenigen identisch ist, die den Cache ursprünglich gefüllt hat.
(Nebenbemerkung: Bei Fastly respektieren wir diese Eigenart der Spezifikation nicht. Wenn wir also eine 304
von Ihrem Ursprungsserver erhalten, werden wir das zwischengespeicherte Objekt weiterhin unverändert verwenden, abgesehen davon, dass die TTL zurückgesetzt wird.)
Browser scheinen dies zu respektieren, aber mit einer Eigenart. Sie aktualisieren nicht nur die Antwort-Header, sondern auch die mit ihnen gepaarten Anfrage-Header, um zu garantieren, dass die zwischengespeicherte Antwort nach der Aktualisierung mit der aktuellen Anfrage übereinstimmt. Dies scheint sinnvoll zu sein. Die Spezifikation erwähnt dies nicht, also können die Browser-Anbieter tun, was sie wollen; Glücklicherweise zeigen alle Browser dasselbe Verhalten.
Client-Tipps
Die Funktion „Client-Hinweise“ von Google ist eines der wichtigsten neuen Dinge, die seit langem mit Vary im Browser passiert sind. Im Gegensatz zu Accept-Encoding
und Accept-Language
beschreiben Client-Hinweise Werte, die sich regelmäßig ändern können, wenn sich ein Benutzer auf Ihrer Website bewegt, insbesondere die folgenden:
-
DPR
Pixelverhältnis des Geräts, die Pixeldichte des Bildschirms (kann variieren, wenn der Benutzer mehrere Bildschirme hat) -
Save-Data
Ob der Benutzer den Datensparmodus aktiviert hat -
Viewport-Width
Pixelbreite des aktuellen Darstellungsbereichs -
Width
Gewünschte Ressourcenbreite in physischen Pixeln
Diese Werte können sich nicht nur für einen einzelnen Benutzer ändern, sondern der Wertebereich für die breitenbezogenen Werte ist groß. Wir können Vary
also vollständig mit diesen Headern verwenden, riskieren jedoch, unsere Cache-Effizienz zu verringern oder das Caching sogar unwirksam zu machen.
Der Key-Header-Vorschlag
Client-Hinweise und andere hochgradig granulare Header eignen sich für einen Vorschlag namens Key, an dem Mark gearbeitet hat. Schauen wir uns ein paar Beispiele an:
Key: Viewport-Width;div=50
Dies besagt, dass die Antwort basierend auf dem Wert des Viewport-Width
Anforderungsheaders variiert, aber auf das nächste Vielfache von 50 Pixeln abgerundet wird!
Key: cookie;param=sessionAuth;param=flags
Das Hinzufügen dieses Headers zu einer Antwort bedeutet, dass wir zwei spezifische Cookies verwenden: sessionAuth
und flags
. Wenn sie sich nicht geändert haben, können wir diese Antwort für eine zukünftige Anfrage wiederverwenden.
Die Hauptunterschiede zwischen Key
und Vary
sind also:
-
Key
erlaubt das Variieren von Unterfeldern innerhalb von Headern, was es plötzlich möglich macht, Cookies zu variieren, da Sie nur ein Cookie variieren können – das wäre riesig; - einzelne Werte können in Bereiche zusammengefasst werden, um die Wahrscheinlichkeit eines Cache-Treffers zu erhöhen, was besonders nützlich ist, um Dinge wie die Breite des Ansichtsfensters zu variieren.
- alle Variationen mit derselben URL müssen denselben Schlüssel haben. Wenn also ein Cache eine neue Antwort für eine URL erhält, für die er bereits einige Varianten hat, und der
Key
-Header-Wert der neuen Antwort nicht mit den Werten dieser vorhandenen Varianten übereinstimmt, müssen alle Varianten aus dem Cache entfernt werden.
Zum Zeitpunkt des Verfassens dieses Artikels unterstützt kein Browser oder CDN Key
, obwohl Sie in einigen CDNs möglicherweise den gleichen Effekt erzielen können, indem Sie eingehende Header in mehrere private Header aufteilen und diese variieren (siehe unseren Beitrag „Das Beste aus Variieren mit Fastly“), daher sind Browser der Hauptbereich, in dem Key
Wirkung erzielen kann.
Die Anforderung, dass alle Variationen das gleiche Schlüsselrezept haben müssen, ist etwas einschränkend, und ich würde gerne eine Art „Early Exit“-Option in der Spezifikation sehen. Auf diese Weise könnten Sie Dinge tun wie: „Variieren Sie den Authentifizierungsstatus, und wenn Sie angemeldet sind, variieren Sie auch die Einstellungen.“
Der Variantenvorschlag
Key
ist ein netter generischer Mechanismus, aber einige Header haben komplexere Regeln für ihre Werte, und das Verständnis der Semantik dieser Werte kann uns helfen, automatisierte Wege zur Reduzierung der Cache-Variation zu finden. Stellen Sie sich beispielsweise vor, dass zwei Anfragen mit unterschiedlichen Accept-Language
Werten eingehen, en-gb
und en-us
, aber obwohl Ihre Website Sprachvariationen unterstützt, haben Sie nur ein „English“. Wenn wir die Anfrage für US-Englisch beantworten und diese Antwort in einem CDN zwischengespeichert wird, kann sie nicht für die Anfrage für britisches Englisch wiederverwendet werden, da der Accept-Language
Wert anders wäre und der Cache nicht intelligent genug ist, um es besser zu wissen .
Geben Sie mit beträchtlichem Tamtam den Variantenvorschlag ein. Dies würde es Servern ermöglichen, zu beschreiben, welche Varianten sie unterstützen, wodurch Caches intelligentere Entscheidungen darüber treffen könnten, welche Varianten sich tatsächlich unterscheiden und welche effektiv gleich sind.
Im Moment ist Variants ein sehr früher Entwurf, und da es darauf ausgelegt ist, bei Accept-Encoding
und Accept-Language
zu helfen, ist seine Nützlichkeit eher auf gemeinsam genutzte Caches wie CDNs als auf Browser-Caches beschränkt. Aber es passt gut zu Key
und vervollständigt das Bild für eine bessere Kontrolle der Cache-Variation.
Fazit
Hier gibt es viel zu lernen, und obwohl es interessant sein kann zu verstehen, wie der Browser unter der Haube funktioniert, gibt es auch einige einfache Dinge, die Sie daraus destillieren können:
- Die meisten Browser behandeln
Vary
als Validator. Wenn Sie möchten, dass mehrere separate Variationen zwischengespeichert werden, finden Sie stattdessen eine Möglichkeit, unterschiedliche URLs zu verwenden. - Browser ignorieren
Vary
für Ressourcen, die mit HTTP/2-Server-Push gepusht werden, also variieren Sie nichts, was Sie pushen. - Browser haben eine Menge Caches und sie funktionieren auf unterschiedliche Weise. Es lohnt sich zu verstehen, wie sich Ihre Caching-Entscheidungen auf die Leistung in jedem Fall auswirken, insbesondere im Kontext von
Vary
. -
Vary
ist nicht so nützlich, wie es sein könnte, undKey
gepaart mit Client-Hinweisen beginnt, dies zu ändern. Folgen Sie der Browserunterstützung, um herauszufinden, wann Sie sie verwenden können.
Geh hin und sei variabel.