Die ganze Wahrheit über Client-Caches

Caches nennt man im Entwicklungsumfeld jene Systeme, die eine Zwischenspeicherung von Daten zwecks schnellerem Abruf anlegen. Viele Entwickler glauben allerdings, dass Caches etwas Böses sind, da sie dem Webmaster die Kontrolle über die Site entziehen und stattdessen ältere Inhalte ausliefern. Das ist auch wahr, allerdings nur, wenn man dem Cache nicht sagt, wer die Zügel in der Hand und was er zu tun hat. Dieser Beitrag soll zeigen, was wirklich hinter Caches auf der Client-Seite (Proxy- und Browser-Cache) steckt.

Erstmal möchte ich aufzeigen, wie ein Cache überhaupt arbeitet. Alle Caches folgen bestimmten Regeln:

  • Wenn die Response-Header dem Cache sagen, dass er die Seite nicht cachen soll, wird er es auch nicht tun
  • Seiten hinter einer htaccess-Autentifizierung oder einer SSL-Verschlüsselung werden grundsätzlich nicht gecacht
  • Wenn keine Validierung im Antwort-Header vorliegt(ein ETag oder Last-Modified-Header), und zusätzlich keine Information über die Aktualität gegeben wurde, wird die Seite als nicht cache-fähig angesehen
  • Eine gecachte Version wird als aktuell angesehen, wenn
    • sie eine Ablaufzeit (Expiration date) oder eine andere alterskontrollierende Angabe hat und diese Angabe noch nicht abgelaufen ist oder
    • Wenn der Browser bereits eine Version in dieser Session gespeichert hat und in den Browser-Einstellungen festgelegt ist, dass pro Session die Datei nur ein mal geladen werden soll oder
    • wenn vom Proxy-Server die Seite vor kurzem angefragt wurde und das letzte Änderungsdatum der Datei relativ weit zurück liegt.

    In diesen Fällen wird die angeforderte Datei direkt aus dem Cache geladen, ohne den Server zu kontaktieren.

  • Wenn die Datei im Cache schon relativ alt ist, wird beim Server angefragt, ob es sich dabei noch um die aktuelle Datei handelt.

Aktualität und Validierung sind also die Grundpfeiler eines jeden Caches. Eine aktuelle Version einer Datei kann sofort, ohne viel Aufwand geladen werden, während eine gültige Version dazu führt, dass nicht die gesamt Datei erneut geladen werden muss, wenn sich nichts geändert hat.

Wenn wir wissen, wie er arbeitet, müssen wir nur noch wissen, wie wir als Webmaster die Kontrolle über diese Arbeit erlangen.
Zuerst sind da die Meta-Tags zu erwähnen. Viele Entwickler packen dann einfach einen Mata-Tag in ihre Seite und denken ihre Seite sei nun „uncacheable“, aber das ist nicht der Fall. Meta-Tags sind in dieser Beziehung (wie mittlerweile in vielen anderen auch) uneffektiv. Der Grund dafür ist, dass nur einige Browser-Caches den HTML-Code wirklich lesen, ein Proxy-Cache schaut sich lediglich den Response-Header an, der Quelltext ist ihm egal.

Wir sehen also, dass wir mit den HTTP-Headern arbeiten müssen, um sowohl Proxy- als auch Browser-Caches effektiv zu erreichen. Diese Header werden normalerweise automatisch vom Webserver generiert, können aber bis zu einem bestimmten Grad auch von der Anwendung selbst bearbeitet werden.

Bevor ich auf die unterschiedlichen Header-Parameter zur Cache-Steuerung eingehe, möchte ich hier kurz noch erwähnen, wie mit PHP der Header bearbeitet wird: Vor allen Ausgaben kann man mit der Funktion header() die gewünschte Eigenschaft setzen. Als Beispiel:

header("Last-Modified: Fri, 25 May 2007 02:28:12 GMT
");

Oft sieht man in Anwendungen den Pragma HTTP-Header, um Caching zu verhindern. Pragma: no-cache wird in den HTTP-Header-Spezifikationen allerdings nicht als Antwort-Header vorgesehen. Lediglich als Request-Header ist er dort aufgeführt. Es kann sein, dass ihn manche Caches beachten, aber die meisten werden es nicht tun.

Um die Zeit einzustellen, wann die Seite nicht mehr aktuell ist und neu geladen werden muss, ist Expires die richtige Einstellung. Ich kann die Nutzung allerdings nicht unbedingt empfehlen, da ein exakter, absoluter Zeitpunkt, wann eine Datei abläuft oft schwierig festzulegen ist, als zusätzliche Angabe zu den weiter unten stehenden ist er aber allemal geeignet, zumal er bereits in HTTP 1.0 enthalten ist.

header("Last-Modified: " . gmdate("D, d M Y H:i:s",time() . " GMT");

Komfortabler sind aber relative Angaben wie die in Cache-Control:

  • max-age=[seconds] — definiert die maximale Anzahl von Sekunden ab „jetzt“ (Zeitpunkt der Auslieferung), die die Datei als aktuell angesehen wird. Ähnlich wie Expires, aber eben mit relativer Zeitangabe.
  • s-maxage=[seconds] — ähnlich wie max-age, bezieht sich aber nur auf geteilte (shared) caches (z.B. Proxy)
  • public — kennzeichnet Seiten als cache-fähig, wenn sie sich hinter einem geschützten Bereich befinden. Normalerweise sind Seiten, auf die nur mittels HTTP-Autentifizierung zugegriffen werden kann, „uncacheable“.
  • no-cache — zwingt den Cache bei jeder Anfrage zuerst zwecks Validierung auf dem Server nachzufragen, bevor die gecachte Version ausgeliefert wird. Nützlich ist das beispielsweise, wenn public gesetzt ist, und trotzdem geprüft werden soll, ob die Autentifizierung noch gültig ist.
  • no-store — sagt dem Cache, dass er auf keinen Fall eine Kopie der Datei speichert
  • must-revalidate — sagt dem Cache, dass er jegliche Aktualitätsinformation, die mit dem Header mitkommt, beachtet werden muss. HTTP erlaubt es unter bestimmten Bedingungen auch ältere Dateien auszuliefern; wenn dieser Parameter gesetzt ist, muss immer mit dem Server validiert werden, ob es sich um die aktuelle Version im Cache handelt.
  • proxy-revalidate — ähnlich wie must-revalidate, aber nur für Proxys

Wenn man also zum Beispiel angeben möchte, dass eine Seite eine Stunde lang gecacht und dieser Zeitpunkt stets beachtet werden, schreibt man also:

header("Cache-Control: max-age=3600, must-revalidate");

Zusätzlich zu den oben gemachten Angaben gibt es seit HTTP 1.1 auch das ETag. Dieses wird automatisch vom Server generiert und ändert sich jedes Mal, wenn die lokale Version abgelaufen ist. Mithilfe dieses Tags kann man in der Anwendung prüfen, ob eine lokale Version der Datei bereit vorliegt, die aktuell ist. Wenn sie nämlich aktuell ist, ist die Variable $_SERVER[‚HTTP_IF_MODIFIED_SINCE‘] gesetzt. Eine geeignete Anwendung sieht dann so aus:

if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
  header("HTTP/1.0 304 Not Modified");
  exit;
}

Bei statischen Dateien werden diese Header automatisch genutzt, ohne dass man auf dem Server irgendetwas tun muss.

Im Internet findet man viele weitere Informationen, wie man seine Seiten cache-aware programmieren kann, wenn man das möchte (also dass nicht die gecachte Version genutzt wird). Ich hoffe aber, dass ich geholfen habe, die Angst vor Caches zu verlieren und sie als mächtiges Performance-Instrument anzusehen.

Jan hat 152 Beiträge geschrieben

9 Kommentare zu “Die ganze Wahrheit über Client-Caches

  1. Arian sagt:

    Dieses Verfahren ist im Übrigen auch nützlich, um bei komplexen Webanwendungen die Last zu minimieren, die durch Suchmaschinencrawler erzeugt wird.

    Gerade Seiten, wo nur statistische Informationen nicht aber Inhalte anders sind, werden normalerweise immer wieder neu indiziert. Die Suchmaschine kann nicht wissen, dass sich eigentlich nichts wichtiges geändert hat.

    Daher kann man den Suchmaschinen einen eigenen Last-Modified-Header schicken, hier kann man dann selbst in der Anwendung kontrollieren, wann die Seite wirklich neu ist.

    Man muss dafür natürlich selbst wissen, wann es sich um eine Suchmaschine handelt.

    Normalerweise wird dieses Problem sonst immer mit einer eigenen Version nur für Suchmaschinen gelöst – hier besteht aber das Problem des double-content.

  2. Sven sagt:

    „… Seiten hinter einer htaccess-Autentifizierung … werden grundsätzlich nicht gecacht …“

    Bitte korrigiert mich, aber das kann nicht stimmen. Jedenfalls, wenn es den normalen htaccess passwort Schutz betrifft.

    Sonst müsste ich im aktuellen Projekt nicht ständig den cache leeren.

  3. admin sagt:

    Im Browser werden sie schon gecached, denn sonst müsste man sich bei jedem Aktualisieren im htaccess-Bereich neu einlogen. Ist der Browser aber einmal zu, wird erneut nachgefragt. Ist also eigentlich nur ne Art Session, die gespeichert wird – kein Caching. Oder was sehe ich falsch.

  4. Marco sagt:

    Wie man auch im Artikel „http://phpperformance.de/optimierungen-von-css-und-javascript-on-the-fly/“ lesen kann, ist so ein Cache ja bei CSS- und JavaScript-Dateien sehr sinnvoll. Dazu habe ich aber noch eine Frage: Könnte man das „max-age“ nicht auf 5 Jahre oder so stellen und dann die CSS-Datei in HTML so einbinden:

    Bei jeder Änderung erhöht man die Versionsnummer. Diese ist völlig egal für den Inhalt der Datei, aber bei einer anderen Nummer gilt die Datei als neu und wird neu heruntergeladen.
    Wie findet ihr diese Methode?

  5. Jan sagt:

    Ja, diese Methode ist legitim und wird oft auch so eingesetzt. Obwohl aber empfohlen wird max-age nicht höher als 1 Jahr (in Sekunden natürlich) zu setzen (ebenso wie Expires nicht später als 1 Jahr in der Zukunft).

  6. Madelaine sagt:

    Hallo zusammen,
    kann mir jemand sage, ob es eine Möglichkeit gibt von Client Seite aus zu steuern, dass sämtliche Proxy und Webserver Caches umgangen werden sollen? Die Proxies in unserer Firma haben caching eingestellt und werden das auch nicht ändern. All meine Versuche auf bekannte weise vom Browser aus eine „frische“ Seite aufzurufen sind bisher fehlgeschlagen (Browser Cache löschen, minimale Chache Größe einstellen, Neuere Version beim Aufrufen der Seite, etc.). Bestimmte Seiten werden einfach immer wieder aus einem Cache geladen.
    Merci & viele Grüße Madelaine

  7. Jan sagt:

    @Madelaine: Ich gehe jetzt einfach mal davon aus, dass Du Zugriff auf den Quellcode hast und nicht einfach irgendeine Webseite am Proxy vorbeischleuden möchtest.

    Ja, das ist möglich. Geht bspw. mit
    Pragma:no-cache
    Cache-Control: must-revalidate oder Cache-Control:no-cache
    If-Modified-Since: Sat, 1 Jan 2000 00:00:00 GMT (bzw. Datum vor dem Erstellungsdatum der Datei)

    Letzteres gilt allgemein als sicherster Weg ein Caching zu umgehen.

Eine Antwort schreiben

Ihre E-Mail-Adresse wird nicht veröffentlicht. Benötigte Felder sind markiert mit *

You may use these HTML tags and attributes: <a href=""> <blockquote cite=""> <pre lang=""> <b> <strong> <i> <em>