Cache Control bei PHP session start ändern
Ladezeiten dynamischer Webseiten verbessern und Server-Traffic reduzieren durch verändertes Cache Control beim Apache-PHP-Gespann
Hallo zusammen!
Ich betreibe eine umfangreiche, CSS-Layout-basierte Website mit vielen kleinen Bildchen, CSS-Dateien usw. Seit langem ärgerte ich mich darüber, daß die Seite so träge war und lange Ladezeiten hatte weil die Browser bei jedem Pagehit die komplette Seite inkl. aller Bildchen herunter luden anstatt ihren Cache zu benutzen.
Daß das was mit den HTTP-Cache-Headern zu tun hat war mir schnell klar. Nur findet man im Netz fast ausschließlich Anleitungen, wie man ein permanentes Neu-Laden erzwingt - hier ist aber genau das Gegenteil gefragt.
Also habe ich zuerst mal mit Wireshark die Kommunikation angeschaut und festgestellt, daß der Server bei jedem Pagehit die HTTP-Header "Cache-Control: no-cache, must-revalidate" sowie "Pragma: no-cache" schickte. Eine entsprechende Anweisung gab es aber im gesamten Script nicht. Schlimmer noch: Explizit anders lautende Cache-Anweisungen wurden ignoriert. Ich habe dann durch Auskommentieren das Problem auf die PHP-Anweisung session_start eingegrenzt. Diese bringt den Apache-Webserver dazu, andere HTTP-Cache-Header zu senden (warum auch immer, notwendig ist das eigentlich nicht).
Daher habe ich mein Script direkt um den Aufruf von session_start herum ergänzt:
Nach viel experimentieren habe ich diese Header-Konstruktion als funktionierend herausbekommen. Je nach Browser scheint die Reihenfolge sogar eine Rolle zu spielen. Wichtig ist, daß man die beiden Zeitpunkte (Generierung der HTML-Ausgabe des Scriptes sowie Verfallsdatum des Cache) angibt sonst weiß der Browser nicht genau was zu tun ist und ignoriert die Cache-Anweisung. "Cache-Control: max-age=3600, private" gibt noch einmal die Lebensdauer des Cache an sowie eine Anweisung, was zu tun ist wenn der Cache verfällt - "private" bedeutet, dass der Cache Session-bezogen ist (Danke an dog für den Hinweis)
Wirklich interessant ist die letzte Header-Anweisung "Pragma: cache": Im RFC 2616 Sektion 14 ist für Pragma als einzig gültiger Wert "no-cache" definiert. Sendet man "Pragma: no-cache" dann ignorieren alle Browser die obigen Lifetime-Anweisungen und laden die Seite dennoch jedesmal neu. Lässt man die Pragma-Anweisung weg, so folgen einige Browser (IE, Safari) der Lifetime-Anweisung, andere dagegen nicht (Firefox, Opera). Also habe ich (zugegebenermaßen blind geraten) einfach mal "Pragma: cache" angegeben. Und siehe da, alle Browser - zumindest die mir verfügbaren Versionen - folgten der Cache-Lifetime. Warum das so ist und einige Browser sich hier ausnehmend NICHT standardkonform verhalten ist mir schleierhaft.
Summa summarum: Die Ladezeiten der einzelnen Seiten haben sich drastisch von durchschnittlich 3,5 Sekunden auf etwa 0,5 Sekunden reduziert. Besonders angenehm fällt nun beim Opera-Browser auf, daß das CSS-Layout nicht erst progressiv zusammengebastelt wird sondern sofort ordentlich aussieht. Beim Server-Traffic ist der Unterschied noch stärker zu sehen: nun noch 27 MB pro Tag wo es vorher 450 MB waren.
Vielleicht hat jemand eine Erklärung für das seltsame Verhalten einiger Browser mit der "Pragma: cache"-Anweisung. Mir persönlich reicht es, daß dieser Workaround funktioniert.
Grüssle
Cody
Hallo zusammen!
Ich betreibe eine umfangreiche, CSS-Layout-basierte Website mit vielen kleinen Bildchen, CSS-Dateien usw. Seit langem ärgerte ich mich darüber, daß die Seite so träge war und lange Ladezeiten hatte weil die Browser bei jedem Pagehit die komplette Seite inkl. aller Bildchen herunter luden anstatt ihren Cache zu benutzen.
Daß das was mit den HTTP-Cache-Headern zu tun hat war mir schnell klar. Nur findet man im Netz fast ausschließlich Anleitungen, wie man ein permanentes Neu-Laden erzwingt - hier ist aber genau das Gegenteil gefragt.
Also habe ich zuerst mal mit Wireshark die Kommunikation angeschaut und festgestellt, daß der Server bei jedem Pagehit die HTTP-Header "Cache-Control: no-cache, must-revalidate" sowie "Pragma: no-cache" schickte. Eine entsprechende Anweisung gab es aber im gesamten Script nicht. Schlimmer noch: Explizit anders lautende Cache-Anweisungen wurden ignoriert. Ich habe dann durch Auskommentieren das Problem auf die PHP-Anweisung session_start eingegrenzt. Diese bringt den Apache-Webserver dazu, andere HTTP-Cache-Header zu senden (warum auch immer, notwendig ist das eigentlich nicht).
Daher habe ich mein Script direkt um den Aufruf von session_start herum ergänzt:
session_start ();
$mez_to_gmt_offset = 3600; // MEZ = GMT + 1 Stunde (3600 Sekunden), kann man sich auch dynamisch berechnen lassen
$cache_lifetime = 3600; // Lebenszeit des Browser-Cache 1 Stunde (3600 Sekunden)
header(strftime("Expires: %a, %d %h %Y %H:%M:%S GMT", time() + $mez_to_gmt_offset + $cache_lifetime)); // Zeitpunkt festlegen ab dem der Browser-Cache verfällt
header(strftime("Last-Modified: %a, %d %h %Y %H:%M:%S GMT", time() + $mez_to_gmt_offset)); // Zeitpunkt der HTML-Generierung
header("Cache-Control: max-age=$cache_lifetime, private"); // Festlegen dass der Cache nur einen bestimmten Zeitraum lang gültig ist
header("Pragma: cache"); // Eigentlich veraltete Pragma-Anweisung, die hier aber eine große Rolle spielt (siehe unten)
Nach viel experimentieren habe ich diese Header-Konstruktion als funktionierend herausbekommen. Je nach Browser scheint die Reihenfolge sogar eine Rolle zu spielen. Wichtig ist, daß man die beiden Zeitpunkte (Generierung der HTML-Ausgabe des Scriptes sowie Verfallsdatum des Cache) angibt sonst weiß der Browser nicht genau was zu tun ist und ignoriert die Cache-Anweisung. "Cache-Control: max-age=3600, private" gibt noch einmal die Lebensdauer des Cache an sowie eine Anweisung, was zu tun ist wenn der Cache verfällt - "private" bedeutet, dass der Cache Session-bezogen ist (Danke an dog für den Hinweis)
Wirklich interessant ist die letzte Header-Anweisung "Pragma: cache": Im RFC 2616 Sektion 14 ist für Pragma als einzig gültiger Wert "no-cache" definiert. Sendet man "Pragma: no-cache" dann ignorieren alle Browser die obigen Lifetime-Anweisungen und laden die Seite dennoch jedesmal neu. Lässt man die Pragma-Anweisung weg, so folgen einige Browser (IE, Safari) der Lifetime-Anweisung, andere dagegen nicht (Firefox, Opera). Also habe ich (zugegebenermaßen blind geraten) einfach mal "Pragma: cache" angegeben. Und siehe da, alle Browser - zumindest die mir verfügbaren Versionen - folgten der Cache-Lifetime. Warum das so ist und einige Browser sich hier ausnehmend NICHT standardkonform verhalten ist mir schleierhaft.
Summa summarum: Die Ladezeiten der einzelnen Seiten haben sich drastisch von durchschnittlich 3,5 Sekunden auf etwa 0,5 Sekunden reduziert. Besonders angenehm fällt nun beim Opera-Browser auf, daß das CSS-Layout nicht erst progressiv zusammengebastelt wird sondern sofort ordentlich aussieht. Beim Server-Traffic ist der Unterschied noch stärker zu sehen: nun noch 27 MB pro Tag wo es vorher 450 MB waren.
Vielleicht hat jemand eine Erklärung für das seltsame Verhalten einiger Browser mit der "Pragma: cache"-Anweisung. Mir persönlich reicht es, daß dieser Workaround funktioniert.
Grüssle
Cody
Bitte markiere auch die Kommentare, die zur Lösung des Beitrags beigetragen haben
Content-ID: 144101
Url: https://administrator.de/tutorial/cache-control-bei-php-session-start-aendern-144101.html
Ausgedruckt am: 26.12.2024 um 12:12 Uhr
15 Kommentare
Neuester Kommentar
(warum auch immer, notwendig ist das eigentlich nicht).
Natürlich ist das notwendig.
Würde PHP bei einer offenen Session erlauben eine Seite zu cachen könnte das auch ein Proxy machen.
Damit könnte eine Session zwischen allen Benutzern eines Proxys geteilt werden, was katastrophal wäre.
Wenn du schon ein Caching bei Sessions erlaubst dann immer nur mit der Option
Cache-Control: private
gibt noch einmal die Lebensdauer des Cache an (eigentlich unsinnig da durch die beiden Zeitpunkte bereits definiert)
Auch hier hätte nachschauen geholfen:
The expiration time of an entity MAY be specified by the origin server using the Expires header (see section 14.21). Alternatively, it MAY be specified using the max-age directive in a response [...] If a response includes both an Expires header and a max-age directive, the max-age directive overrides the Expires header, even if the Expires header is more restrictive
Okay... Du hast ein Session-Basiertes System, das hat man üblicherweise dann, weil man Nutzern (personalisierte) dynamische Inhalte präsentieren will. Damit geht (i.d.R.) einher, dass sich Seiteninhalte spontan ändern können. Ich sehe daher keinen Sinn darin, die dynamisch generierten Seiten cachen zu lassen.
Dein ursprünglich beschriebenes Problem, dass du haufenweise CSS-Dateien und vermutlich endlos Grafiken hast, die andauernd geladen werden, hat damit aber doch gar nichts zu tun. Du musst unterscheiden, ob die aufgerufene Ressource eine dynamische ist (Quelltext deiner Startseite z.B.) oder eine statische (deine CSS, JavaScript und Grafikdateien vermutlich).
Natürlich macht es sinn, die statischen Dateien cachen zu lassen, aber doch nicht die dynamischen?
Was du jetzt erreichst, ist ein cachen der dynamischen Seiten. Für die statischen Inhalte hast du aber gar nichts geändert, denn ich denke nicht dass oben genanntes PHP-Schnipsel in deine CSS und Grafik-Dateien eingebunden ist, oder?
Sinn voll wäre es, die notwendigen Header zum cachen von statischen Dateien mittels .htaccess Direktiven für die jeweiligen Dateitypen (css,js,gif,png,jpg,ico,...) zu setzen, das cachen von dynamischen Seiten aber weiterhin, wie ursprünglich, zu unterbinden.
Wenn es dann immer noch langsam ist, solltest du dir über die Ausführungszeit deiner dynamischen Skripte Gedanken machen. Denn dass dein jetziger Ansatz augenscheinlich funktioniert, legt nahe, dass nicht die vielen einzelnen Ressourcen das Problem sind, sondern die Ausführungszeit deiner Skripten.
Tipp: Google Chrome kann eine schöne Timeline der Lade und Ausführungszeit aller geladenen Ressourcen erstellen. Ist hierbei sicher hilfreich.
Dein ursprünglich beschriebenes Problem, dass du haufenweise CSS-Dateien und vermutlich endlos Grafiken hast, die andauernd geladen werden, hat damit aber doch gar nichts zu tun. Du musst unterscheiden, ob die aufgerufene Ressource eine dynamische ist (Quelltext deiner Startseite z.B.) oder eine statische (deine CSS, JavaScript und Grafikdateien vermutlich).
Natürlich macht es sinn, die statischen Dateien cachen zu lassen, aber doch nicht die dynamischen?
Was du jetzt erreichst, ist ein cachen der dynamischen Seiten. Für die statischen Inhalte hast du aber gar nichts geändert, denn ich denke nicht dass oben genanntes PHP-Schnipsel in deine CSS und Grafik-Dateien eingebunden ist, oder?
Sinn voll wäre es, die notwendigen Header zum cachen von statischen Dateien mittels .htaccess Direktiven für die jeweiligen Dateitypen (css,js,gif,png,jpg,ico,...) zu setzen, das cachen von dynamischen Seiten aber weiterhin, wie ursprünglich, zu unterbinden.
Wenn es dann immer noch langsam ist, solltest du dir über die Ausführungszeit deiner dynamischen Skripte Gedanken machen. Denn dass dein jetziger Ansatz augenscheinlich funktioniert, legt nahe, dass nicht die vielen einzelnen Ressourcen das Problem sind, sondern die Ausführungszeit deiner Skripten.
Tipp: Google Chrome kann eine schöne Timeline der Lade und Ausführungszeit aller geladenen Ressourcen erstellen. Ist hierbei sicher hilfreich.
Zitat von @Codehunter:
Um deine Frage zu beantworten: Der o.g. PHP-Code ist tatsächlich auch für CSS- und JS-Dateien relevant da diese
ebenfalls von PHP erzeugt werden - die Bilder allerdings nicht.
Um deine Frage zu beantworten: Der o.g. PHP-Code ist tatsächlich auch für CSS- und JS-Dateien relevant da diese
ebenfalls von PHP erzeugt werden - die Bilder allerdings nicht.
Was du vielleicht erwähnen hättest können, denn das ist kein Regelfall. Wenn also "normale" Leute deinen Tipp beherzt hätten, hätte ihnen das eben rein garnichts gebracht. Daher die Unterscheidung zwischen statischen und dynamischen Ressourcen und nicht einfach "PHP-Skript" ;)
Für dich funktioniert deine Lösung, aber Andere können das ohne diese Information weder nachvollziehen noch reproduzieren.
Zitat von @Codehunter:
Einfach gesprochen: Sobald man im PHP session_start() verwendet beginnen die Browser, die gesamte Resourcen-Wolke ohne Cache zu
laden.
Einfach gesprochen: Sobald man im PHP session_start() verwendet beginnen die Browser, die gesamte Resourcen-Wolke ohne Cache zu
laden.
Nein, wie kommst du da drauf? Der Browser behandelt jede Ressource einzeln.
In deinem Fall, wo du das Session-Script auch in CSS und JS Dateien eingebunden hast, mag die beobachtete Behandlung zwar für einen Großteil deiner Wolke zutreffen, aber nicht auf die Gesamte.
Wie wir aber schon festgestellt haben ist das bei statischen Inhalten nutzlos. Irritierend ist eben, dass dieses Verhalten
nicht dokumentiert ist.
nicht dokumentiert ist.
PHP kann nicht wissen ob deine Inhalte wirklich statisch oder dynamisch sind, also muss es annehmen dass alles innerhalb deiner Session dynamisch ist, ich finde das durchaus nachvollziehbar. In wie weit es (nicht) dokumentiert ist, weiß ich aber nicht.
Ich will nochmal deutlich machen: Dein Ansatz ist ja nicht falsch. Du beschreibst, wie du den Browser dazu veranlassen kannst, Ressourcen welche das oben gepostete PHP-Schnipsel enthalten, im Cache abzulegen, obwohl die PHP-Session Funktionen ursprünglich anderes wollten. Soweit alles okay.
Lediglich von dieser Tatsache auf einen gesamten Webauftritt zu schließen ist falsch, denn es betrifft ja nur eben die Skripten, welche den Code enthalten und das kann durchaus eine Minderheit an Ressourcen auf einem Server sein.
Es ist daher wichtig darauf hinzuweisen, dass gerade die anderen Ressourcen ohne diesen Code (und das sind eben Bilder und in den meissten Fällen auch CSS und JS usw.) auch beachtet werden sollten. Auch dort kannst du, z.B. wie oben mit .htaccess beschrieben, die notwendigen Header setzen und den Seitenaufruf so beschleunigen.
Zitat von @Codehunter:
Dann habe ich wie eingangs erwähnt, mir die GESAMTE Kommunikation zwischen Browser und Server mittels Wireshark angeschaut.
Tatsache ist, daß alle mir verfügbaren Browser begonnen haben, das statische /test.png bei jedem Pagehit zu laden
sobald session_start() nicht mehr auskommentiert war. Der Browser schickte für /test.png einen separaten GET-Request, welcher
ebenfalls mit einem "Pragma: no-cache"-Header beantwortet wurde. Also muss doch session_start() mehr tun als nur
für den einen Request die Header zu modifizieren.
Dann habe ich wie eingangs erwähnt, mir die GESAMTE Kommunikation zwischen Browser und Server mittels Wireshark angeschaut.
Tatsache ist, daß alle mir verfügbaren Browser begonnen haben, das statische /test.png bei jedem Pagehit zu laden
sobald session_start() nicht mehr auskommentiert war. Der Browser schickte für /test.png einen separaten GET-Request, welcher
ebenfalls mit einem "Pragma: no-cache"-Header beantwortet wurde. Also muss doch session_start() mehr tun als nur
für den einen Request die Header zu modifizieren.
Also... PHP hat auf die Ressource "/test.png" keinerlei Zugriff. Es weiß weder, wann und ob dein Browser die Ressource abruft, noch hätte es die Möglichkeit irgendwelche Header dafür zu ändern.
Ich gehe mal davon aus, dass dein Server beim Aufruf des "/test.png" IMMER den Header "Pragma: no-cache" sendet. Da du Wireshark laufen hast, solltest das ja mal eben prüfen können. Und hier solltest du ansetzen, anstelle PHP magische Fähigkeiten nachzureden
Also, was der Browser abruft hat ja noch nichts damit zu tun, was der Server an Headern sendet.
Erst einmal muss man sicherstellen dass der Server die Header zum Speichern der Ressource sendet, danach kann man sich Gedanken machen ob und warum der Browser sie trotzdem zu häufig abruft.
Möglicherweise existiert auf deinem Server wirklich ein Modul, welches Header je nach Gegebenheit setzt. Das einzige woran der Server aber ausmachen könnte, dass die Ressource innerhalb einer Session abgerufen wird, ist aber das Session-Cookie. Und das wird vom Browser auch noch gesendet, wenn du die session_start() Zeile wieder auskommentierst, da es dadurch ja nicht gelöscht wird. Insofern müsste dir das eigentlich aufgefallen sein, dass ein Auskommentieren der Zeile nichts hilft.
Kann es vielleicht sein, dass PHP deine png-Dateien doch interpretiert?
Entweder, weil du deren Aufruf vielleicht durch mod_rewrite in ein PHP-Skript umbiegst, oder weil du in der Apache-Konfiguration vielleicht den Content-Type image/png von PHP parsen lässt?
Nach wie vor finde ich es nämlich seltsam, wenn der Server die Header ändern würde, solange die Datei NICHT von PHP verarbeitet wird.
Erst einmal muss man sicherstellen dass der Server die Header zum Speichern der Ressource sendet, danach kann man sich Gedanken machen ob und warum der Browser sie trotzdem zu häufig abruft.
Möglicherweise existiert auf deinem Server wirklich ein Modul, welches Header je nach Gegebenheit setzt. Das einzige woran der Server aber ausmachen könnte, dass die Ressource innerhalb einer Session abgerufen wird, ist aber das Session-Cookie. Und das wird vom Browser auch noch gesendet, wenn du die session_start() Zeile wieder auskommentierst, da es dadurch ja nicht gelöscht wird. Insofern müsste dir das eigentlich aufgefallen sein, dass ein Auskommentieren der Zeile nichts hilft.
Kann es vielleicht sein, dass PHP deine png-Dateien doch interpretiert?
Entweder, weil du deren Aufruf vielleicht durch mod_rewrite in ein PHP-Skript umbiegst, oder weil du in der Apache-Konfiguration vielleicht den Content-Type image/png von PHP parsen lässt?
Nach wie vor finde ich es nämlich seltsam, wenn der Server die Header ändern würde, solange die Datei NICHT von PHP verarbeitet wird.
Zitat von @Codehunter:
Bin ich hier unwissenderweise über einen unbekannten Bug im Apache-PHP-Gespann gestolpert und habe es nur irrtümlich
für ein Problem an meinem Script gehalten?
Bin ich hier unwissenderweise über einen unbekannten Bug im Apache-PHP-Gespann gestolpert und habe es nur irrtümlich
für ein Problem an meinem Script gehalten?
Naja... Das Problem was ich mit dieser Vorstellung immer noch habe ist, dass der Apache PHP ja gar nicht ausführt, wenn du eine statische Datei lädst. Da kann PHP noch so wild um sich schlagen oder Bugs ohne Ende haben - ich wüsste nicht wie das Einfluss auf statische Inhalte haben kann.
Läuft PHP als CGI oder Modul?
Auch würde mich interessieren, was dazu führt, dass dein Browser beim zweiten Aufruf nach dem Auskommentieren das Session-Cookie nicht mehr sendet. Dafür kann es ja nur zwei Gründe geben:
Entweder sendet PHP bei gesetztem Session-Cookie und ohne session_start() ein Lösch-Cookie (also das Session-Cookie mit Datum in der Vergangenheit) oder aber PHP setzt einfach kein Cookie und dein Browser entscheidet darauf hin eigenwillig, dass das Cookie nicht mehr gesendet werden muss (was aber irgendwie gegen Spezifikationen verstößen würde).