Über HTTP Cache Steuerung
und die Bereitstellung von XHTMLJede Webseite wird grundsätzlich als Kaskade einzelner Dateiabrufe durch den Browser vom Ursprungsserver geholt. Das dafür zuständige Protokoll 'HTTP' ist sehr einfach: Eine Anfrage erhält eine Antwort (Request / Response). Wenn also ein Surfer auf den Link für eine neue Seite klickt, holt sich der Browser zuerst die Seite an sich, danach alle darin enthaltenen Elemente – Grafiken, Stylesheets und ggf. JavaScript-Dateien – jedes durch einen eigenen Abruf. Um den Seitenaufbau zu beschleunigen und das Aufkommen von Datenverkehr im Internet zu drosseln, versucht man i.a. Elemente, die erst kürzlich geholt worden sind, aus dem Cache (lokalen Gedächtnis) zu bedienen, anstatt sie erneut vom Webserver zu holen. Bei einer durchschnittlichen Website gibt es pro Webseite viele Elemente die wiederholt benutzt werden, etwa Stylesheets, gemeinsame JavaScript Dateien und allen voran Grafiken, die in der Navigation oder im graphischen Seitenaufbau verwendet werden. Aus diesem Grund lohnt es sich grundsätzlich sehr, caching einzusetzen. Dabei muss immer entschieden werden, ob ein lokal vorrätiges Element noch aktuell ist, oder vom Server erneut geholt werden muss, falls es sich in der Zwischenzeit geändert hat. Meta-Tags und HTTP-HeadersSowohl HTTP wie auch HTML sehen Möglichkeiten der Cache-Steuerung vor. Meta-Tags im <head> des HTML sind dabei die schlechtere Wahl, da sie von Proxy-Servern (eine Art Verteiler oder Zwischenspeicher, ohne die die Last im Internet nicht mehr zu bewältigen wäre) nicht beachtet werden. Da sowohl Browser wie auch Proxies die Angaben in HTTP-Headern beachten, sollten nur diese benutzt werden. HTTP ist ein text-basiertes Protokoll, d.h. jede Anfrage wie auch jede Antwort ist nichts anderes als eine Folge von Textzeilen – Binärdateien wie PDF oder Zip werden für die Übertragung als Text verschlüsselt. Und HTTP-Headers sind auch nichts anderes als Textzeilen am Anfang der jeweiligen Nachricht, die vom Rumpf (dem "eigentlichen" Inhalt, den sie beschreiben) durch eine Leerzeile getrennt sind. Einige Headers enthalten Angaben zur Aktualität – bei einer Anfrage ggf. Daten über die lokal vorhandene Kopie, bei einer Antwort sind es Angaben zum nachfolgenden Inhalt – es sei denn, es wird lediglich mitgeteilt, dass die lokale Kopie dem Original immer noch gleich ist. Bedingtes GETSo heißt der zuletzt geschilderte Vorgang - er ist der wichtigste überhaupt im Bereich des Caching. Der Browser (oder Proxy) sendet ein GET request an den Server, mit Angaben zur gecached Kopie. Das sind das Datum der letzten Änderung der lokalen Kopie, und / oder ein "ETag" (Entity Tag) mit einer eindeutigen (versionsspezifischen) Kennung des Elements. Diese Angaben sind freilich nur dann möglich, wenn der Webserver sie bei der Auslieferung mitgegeben hat, weswegen die meisten Webserver diese Angaben den Antwort-Headers standardmäßig hinzufügen. Ist das Element am Server unverändert, anwortet er mit einem "Not modified" Header und unterläßt die Sendung des Elements selbst. Etwas ausführlicher: Der Browser sendet ein "If Modified Since" Header, falls der Server das Element ursprunglich mit einem "Last Modified" Datum geliefert hat. Ferner sendet er ein "If None Match" Header plus ETag, falls der Server ein ETag Header mitgab. Das ganze Spiel sieht man übrigens als Mitschnitt hier in der rechten Spalte - zuerst die ursprungliche Lieferung einer Grafik, danach das bedingte GET mit "Not Modified" Antwort. Die "Last Modified" Methode gilt als "schwache" Validierung, da nur Sekundengenau, ist aber aus historischen Gründen noch am weitesten verbreitet. Die ETag-Methode ("starke" Validierung), die später hinzukam, ist hingegen vollkommen eindeutig, und setzt sich daher durch. Freshness und ValidationSo weit so gut – nur, wann kann ein Element aus dem eigenen Cache ohne Rückfrage benutzt werden, und wann ist eine Validierung (bedingtes GET) nötig? Genau hier setzt die Arbeit des Webmasters ein. Der Webserver liefert zwar Angaben zur Identifizierung einer Elementversion automatisch mit aus, weiss aber nichts darüber, ob eine Website sehr dynamisch ist oder sich praktisch nie ändert, d.h. ob eine Rückfrage immer gewünscht ist, oder erst nach Tagen oder Wochen im Cache. Deswegen kann es hier keine allgemeingültige Automatiserung geben - der Webmaster muss dafür sorgen, dass relevante Angaben in den ausgelieferten Headers erscheinen. Der hierfür vorgesehene Header lautet "Expires" und gibt ein Datum an, vor dem eine Rückfrage NICHT nötig ist. Auch innerhalb einer Website wird es meist sinnvoll sein, für verschiedene Dateitypen unterschiedliche Intervalle anzugeben, innerhalb derer das jeweilige Element noch "fresh" ist. Grafiken haben meistens ein vergleichsweise langes Leben, Stylesheets auch (sobald Ruhe einkehrt), aber bei PDFs, ZIPs und auch die HTML-Dateien selbst müssen individuelle Werte bestimmt werden. ACHTUNG: Da diese Frage für die Performance so entscheidend ist, raten Browser und Proxies, ob ein bedingtes GET nötig ist, wenn die Angabe "Expires" fehlt – weswegen der Webmaster unbedingt dafür sorgen soll, dass sie vorhanden ist, sonst gibt es kein berechenbares Verhalten bzgl. Aktualität. Apache-Webserver konfigurierenDer für jede Antwort obligatorische "Content-Type:" Header enthält ein MIME Type für das nachfolgende Dokument (Nachrichtenkörper), sowie auch den dafür verwendeten Zeichensatz. Mit den entsprechenden Anweisungen kann Apache für jeden MIME Type und Datei einen individuell berechneten Expires-Header hinzugeben. Für diesen Mechanismus benötigt Apache mod_expires, der in den meisten Konfigurationen bereits eingebunden ist. Dies vorausgesetzt können die Anweisungen "ExpiresDefault" und "ExpiresByType" an fast beliebiger Stelle der ganzen Apache Konfig-Dateien stehen, zweckmäßig ist jedoch meist der Einsatz von .htaccess Dateien, da diese an beliebig vielen Stellen der Website deponiert werden können, wo sie für das jeweilige Verzeichnis und alle darunterliegenden Ordner gelten – ohne die allgemeinen Konfigdateien anzurühren (bei Webhosting Paketen ist dies der einzig möglicher Weg). Die Beispieldatei hier in der rechten Spalte zeigt, wie für die unterschiedlichen Dateitypen der Website entsprechend ihren MIME Typen ExpiresByType individuell angewendet wird. Der Server generiert in den Antworten hierfür jeweils ein "Expires:" Header, sowie ein "Cache-Control: max-age" Header, der in etwa das Gleiche aussagt, aber für Cache-Algorithmen vorrangiger ist. Eingangs habe ich erwähnt, dass Elemente wie Grafiken oder Stylesheets grundsätzlich gecached gehören. Hier einige Tipps in Bezug auf die "eigentlichen" Seiteninhalte: Haben Sie eine dynamische Anwendung, z.B. Online-Shop, Nachrichtenportal, Blog etc. dann müssen die Skripte, die diese Seiten erzeugen, für Headers sorgen, die Caching ganz unterbinden (s. unten). Vielleicht haben Sie jedoch Seiten in PHP, wo die Navigation dazu erzeugt wird, aber der eigentliche Inhalt eher statisch ist. In diesem Fall sollte der Skript ein "Last Modified" Header erzeugen, der das Datum der jüngsten includierten Datei enthält. Ein anderes Szenario wäre z.B. eine Frames-basierte Website, dann sind die Quelldateien für Navigation und Inhalt ohnehin getrennt und es reicht, das Änderungsdatum der jeweiligen Datei einsetzen zu lassen. Serverseitige Vorkehrungen bei XHTML 1.0Laut Empfehlung des W3C (s. Verweis unten) sollten Dateien, die XHTML enthalten, auch "*.xhtml" heißen. Wenn man dies (statt ".html" auch macht, dann setzt Apache den Antwort Header "Content-Type: application/xhtml+xml", was ebenfalls einer W3C-Empfehlung für XHTML entspricht. Haken an der Geschichte ist, dass Internet Explorer (auch Vn. 7) diesen MIME-Type leider nicht kennt, und die Datei zum Download anbietet! Von daher kann man diese Datei-Endung in der Praxis nicht nutzen, obgleich Apache und alle anderen modernen Browser damit bestens zurecht kommen. Das ist schade, weil bei "Content-Type: text/html" die Browser den Inhalt der Datei nicht wirklich als XHTML (also mit ihren XML-Parser) bearbeiten, da MIME Type und DTD (Document Type Definition, am Anfang der Datei) effektiv in Konflikt stehen. Es gibt jedoch die Möglichkeit, wenn man die Webseiten als PHP- oder Perl-Skripte realisiert, innerhalb des Skriptes die Fähigkeiten des Browsers abzufragen und ggf. doch den "richtigen" MIME-Type zu setzen. Dieser Code läßt sich mit Code für Caching-Headers prima kombinieren und so können rundum tadellose Headers in der Antwort gesetzt werden. Wer diesen Weg geht wird allerdings schnell merken, dass der Gecko XML-Parser absolut gnadenlos ist, und dass die Auswirkungen der Sprachwahl sich bis in JavaScript hinein auswirken :) Caching von Script-Seiten (PHP, Perl)Generell lohnt sich je nach Anwendung auch das Caching von durch Skripte erzeugten Webseiten. Da Apache jedoch nicht wissen kann, was ein Skript macht, wird er ohne besonderen Anweisungen die Skriptseiten mit Headers versehen, die jegliches Caching unterbinden. Um ein sinnvolles Caching zu implementieren, sind Vorkehrungen sowohl bei Apache wie auch im Skript selbst nötig – wobei diese sorgfältig aufeinander abgestimmt sein müssen, denn die Apache-Anweisungen greifen nach dem Erzeugen der Seite durch den Skript, und können ggf. Headers, die im Skript erzeugt wurden, überbuttern. Für diesen Mechanismus benötigt Apache mod_headers, der meistens nicht standarmäßig geladen wird. Kurz skizziert: Der Skript muss das Datum seines jüngsten Seiteninhalts feststellen und ein entsprechendes "Last Modified" Header erzeugen. Optional kann ein "Expires" Header dazukommen, falls Caching der Seite erlaubt sein sollte. Ein "Cache-Control: max-age" Header läßt sich besser von Apache dazudichten, da dieser sonst ein unpassendes Cache-Control Header erzeugt. Ferner muss bei Apache ein "Pragma: no-cache" Header unterbunden werden. Letzter Schritt ist die Prüfung im Skript der Request-Headers, ob der Client ein "If modified since" Header mitgeliefert hat - ggf. erzeugt der Skript dann keine Seite mehr sondern nur ein "Not modified" Header. Das ganze läßt sich in den Dateien des Downloads (hier am Ende der rechten Spalte) nachvollziehen. Weiterführende LinksCaching Tutorial for Web Authors and Webmasters Cache-Control (offizieller Standard des W3C) The PHP Anthology Volume 2, Chapter 5 – Caching Serving XHTML with the correct mime type using PHP XHTML Media Types (W3C Definition) The 'application/xhtml+xml' Media Type (IETF Internet-Draft) XHTML media type test – results The Road to XHTML 2.0: MIME Types Wer ernsthaft sein Caching auf Vordermann bringen will, braucht unbedingt ein Plugin-Werkzeug zur laufenden Anzeige der Headers in seinem Browser: Firefox: Live HTTP Headers (Mozdev) Internet Explorer: ieHTTPHeaders (Blunck Software) |
Beispiel HTTP-GETGET /images/tn_pf.jpg HTTP/1.1 Host: www.timreeves.de User-Agent: Mozilla/5.0 (Windows; ...) Accept: text/xml,application/xml,... Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 HTTP/1.x 200 OK Date: Sat, 27 May 2006 16:43:25 GMT Server: Apache/2.0.49 (Linux/SuSE) Last-Modified: Mon, 15 May 2006 09:34:48 GMT Etag: "9ee615f-294c-65bf3e00" Accept-Ranges: bytes Content-Length: 10572 Cache-Control: max-age=2592000 Expires: Mon, 26 Jun 2006 16:43:25 GMT Content-Type: image/jpeg Bedingtes HTTP-GETGET /neu/images/tn_pf.jpg HTTP/1.1 Host: www.timreeves.de User-Agent: Mozilla/5.0 (Windows; ...) Accept: text/xml,application/xml,... Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 If-Modified-Since: Mon, 15 May 2006 09:34:48 GMT If-None-Match: "9ee615f-294c-65bf3e00" Cache-Control: max-age=0 HTTP/1.x 304 Not Modified Date: Sat, 27 May 2006 17:28:50 GMT Server: Apache/2.0.49 (Linux/SuSE) Etag: "9ee615f-294c-65bf3e00" Expires: Mon, 26 Jun 2006 17:28:50 GMT Cache-Control: max-age=2592000 Beispieldatei .htaccess# Evtl. braucht httpd.conf die Anweisung # "AllowOverride All" um .htaccess über- # haupt wirksam werden zu lassen! DirectoryIndex index.php index.xml index.xhtml index.shtml index.html index.htm # Binde .php Dateien zu ihrem Händler falls nötig # AddType application/x-httpd-php43 .php # AddType application/x-httpd-php5 .php # Weitere MIME Verknüpfungen die oft fehlen # oder falsch sind in /etc/mime.types AddType application/javascript .js AddType image/vnd.microsoft.icon .ico # Expirations (Verfall-Angaben) erlauben ExpiresActive On # Bilder verfallen nach 30 T. in einem Cache ExpiresByType image/jpeg "access plus 30 days" ExpiresByType image/gif "access plus 30 days" ExpiresByType image/png "access plus 30 days" ExpiresByType image/vnd.microsoft.icon "access plus 30 days" # 1. (Untauglich) HTML-Dateien verfallen eine # Woche nach ihrer letzten Änderung. Achtung: # Dies führt dazu dass alte (stabile) Dateien # bereits bei Auslieferung abgelaufen sind. # ExpiresByType text/html "modification plus 7 days" # 2. (Stümpermethode) HTML-Dokumente # verfallen 3 Tage ab ihrer Auslieferung # ExpiresByType text/html "access plus 3 days" # 3. (Besser) Erzwinge eine Validierung # der Seite nach 1 Stunde ab Auslieferung. # d.h. ein Website-Besuch geht im Cache ab. ExpiresByType application/xhtml+xml "access plus 1 hour" ExpiresByType text/html "access plus 1 hour" ExpiresByType text/css "access plus 1 hour" ExpiresByType application/javascript "access plus 1 hour" # Es folgen die Download-Typen, nicht in Kraft # - sonst wird ggf. ein Download aus dem Cache # bedient und der Zähler am Server nicht erhöht. # ExpiresByType application/pdf "access plus 1 hour" # ExpiresByType application/msword "access plus 1 hour" # ExpiresByType application/zip "access plus 1 hour" # ExpiresByType application/x-download "access plus 1 hour" # PHP- und Perl-Skripte sollen selbst eine # passende Header-Behandlung durchführen, # weswegen sie NICHT per ExpiresDefault # erwischt werden dürfen. # Wenn man aber keine Skripte einsetzt: # Alles ausser Bilder über einen Kamm. # ExpiresDefault "access plus 1 hour" # Skripte werden von Apache torpediert # wenn man ihn nicht daran hindert. # Header directive braucht mod_headers <Files "*.php"> Header set Cache-Control "max-age=3600" Header unset Pragma Header set Service-Time "%D" </Files> Zum Herunterladen bitte einloggen. |
