heinetz: Browsercaching steuern

Hallo Forum,

die Problematik, dass der Browser alte Inhalte aus dem Cache holt ist ja nichts Neues. Die Überlegung, wie und ob man ihn dazu bewegt, sämtliche Inhalte oder Teile immer neu vom Server anzufordern ist auch uralt. Letztlich habe ich mich darauf geeinigt, mich grundsätzlich nicht darum zu kümmern und meinem Kunden zu erzählen, dass er den Browsercache leeren soll, wenn er mal wieder ein altes Bild sieht. Das ist natürlich nicht sehr professionell. Jetzt suche ich
nach einer eleganten Lösung, wie ich den Browser unabhängig von den Usereinstellungen bzgl. Browsercache dazu bringe, alles (css, js, jpg, swf, png, gif) neu zu laden, was notwendig ist.
Meine Do-It-Yourself-Lösung, um bei einzelnen Files das Caching zu verhindern ist seit jeher gewesen, den speziellen Request mit einem GET-Parameter a lá "styles.css?<?=time();?>" zu versehen. Damit erreicht man dann dass styles.css bei jedem Request neu angefordert wird, was langfristig natürlich nicht der Sinn der Sache sein kann. Als dauerhafte Lösung kann ich mir eine Weiterentwicklung dieser Do-It-Yourself-Lösung vorstellen a lá "styles.css?<?=filetime("styles.css");?>". Ist es das oder geht's besser?

besten dank für Tipps und

beste gruesse,
heinetz

  1. Om nah hoo pez nyeetz, heinetz!

    was spricht gegen <meta http-equiv="expires" content="0">? Gilt das nur für die HTML-Ressource?

    Matthias

    --
    1/z ist kein Blatt Papier.

    1. was spricht gegen <meta http-equiv="expires" content="0">? Gilt das nur für die HTML-Ressource?

      Da Du Dir bei dem Vorschlag sicher etwas gedacht hast, kann ich meine Antwort nur mit zwei Gegenfragen formulieren:

      1. Kann ich durch diese Angabe im <head> einer XHTML-Ressource dafür sorgen, dass ein konkretes im XHTML-Code referenziertes bspw. CSS-File neu geladen wird, wenn die XHTML-Ressource angefordert wird?

      2. Kann ich auf dieser Weise dafür sorgen, dass dieses CSS-File nur dann neu geladen wird, wenn es sich auch geändert hat?

      Ich habe meinen Ansatz in ähnlicher Weise wiedergefunden. Bei dem Ansatz wird das filemdate nicht als GET-Paramter an den Request gehängt, sondern a lá "styles.<?=filetime("styles.css");?>.css" 'in den Dateinamen geschrieben' dieser Teil wird dann per mod_rewrite wieder 'rausgeschmissen', weil der Request mit GET-Paramtern das Caching grundsätzlich verhindert. War mir garnicht klar.

      beste gruesse,
      heinetz

      1. Om nah hoo pez nyeetz, heinetz!

        was spricht gegen <meta http-equiv="expires" content="0">? Gilt das nur für die HTML-Ressource?

        Da Du Dir bei dem Vorschlag sicher etwas gedacht hast

        Nicht wirklich. Ich weiß von der Möglichkeit <meta http-equiv="expires" content="0"> und auch http://example.com?foo=bar. Erst deine Frage bringt mich zum Nachdenken, ob die Angabe für alle anderen Ressourcen gilt.

        Beim googlen findet man auch schon mal die eine oder andere urv: http://www.webweavers.de/tutorials/metatags.htm#server (vergleiche http://wiki.selfhtml.org/wiki/HTML/Kopfdaten/meta#Datei_von_Originaladresse_laden bzw. http://de.selfhtml.org/html/kopfdaten/meta.htm#laden)

        Von mir als lesenswert befunden: http://www.thomas-huehn.de/web/caching-tutorial/#steuern

        Matthias

        --
        1/z ist kein Blatt Papier.

      2. Tach!

        Ich habe meinen Ansatz in ähnlicher Weise wiedergefunden. Bei dem Ansatz wird das filemdate nicht als GET-Paramter an den Request gehängt, sondern a lá "styles.<?=filetime("styles.css");?>.css" 'in den Dateinamen geschrieben' dieser Teil wird dann per mod_rewrite wieder 'rausgeschmissen', weil der Request mit GET-Paramtern das Caching grundsätzlich verhindert. War mir garnicht klar.

        Danke für den Hinweis. Bisher hab ich auch nur à la datei.css?4 einen Versionsstand gekennzeichnet. Dass der Erfolg gleich ist wie das Cachen ganz zu verbieten war mir auch nicht bewusst. Das unterscheidende "Bit" muss also in den Pfad, damit das Cachen nicht gleich ganz aufhört. Die mod_rewrite-Lösung gefällt mir nicht, weil das Aufwand für jede Datei ist - oder zumindest einmalig, wenn sich ein eindeutiges Muster für viele Dateien finden lässt.

        Die bessere Lösung scheint mit PathInfo zu sein. Dabei wird an eine URL, die der Apache zu einer existierenden Datei mappen kann, ein weiterer Pfad-Teil angefügt: /pfad/zu/foo.css/anhang. Der Apache findet /pfad/zu/foo.css und ignoriert den Rest - vorausgesetzt AcceptPathInfo On ist konfiguriert worden.

        dedlfix.

        1. Die mod_rewrite-Lösung gefällt mir nicht, weil das Aufwand für jede Datei ist - oder zumindest einmalig, wenn sich ein eindeutiges Muster für viele Dateien finden lässt.

          Worin genau besteht der Aufwand und für wen oder was? Für die Rewriteengine weil sie alles umleiten muss, was die rule erfasst oder für Dich als Entwickler, das Muster für die rule zu entwerfen?

          beste gruesse,
          heinetz

          1. Tach!

            Die mod_rewrite-Lösung gefällt mir nicht, weil das Aufwand für jede Datei ist - oder zumindest einmalig, wenn sich ein eindeutiges Muster für viele Dateien finden lässt.
            Worin genau besteht der Aufwand und für wen oder was? Für die Rewriteengine weil sie alles umleiten muss, was die rule erfasst oder für Dich als Entwickler, das Muster für die rule zu entwerfen?

            Beides. Ersteres fällt nicht (mehr) ins Gewicht, weil die Rewrite-Engine schnell genug ist. Der Aufwand für den Entwickler, insbesondere dass er die Regel später auch noch warten muss, ist da schon interessanter.

            dedlfix.

        2. Tach!

          Die bessere Lösung scheint mit PathInfo zu sein. Dabei wird an eine URL, die der Apache zu einer existierenden Datei mappen kann, ein weiterer Pfad-Teil angefügt: /pfad/zu/foo.css/anhang. Der Apache findet /pfad/zu/foo.css und ignoriert den Rest - vorausgesetzt AcceptPathInfo On ist konfiguriert worden.

          Allerdings bekommt man dadurch eventuell andere Probleme, nämlich dann, wenn in der Ressource relative Links enthalten sind. Die beziehen sich nun nicht mehr auf /pfad/zu/foo.css sondern ebenfalls auf den .../anhang.

          dedlfix.

        3. Tach!

          [...] weil der Request mit GET-Paramtern das Caching grundsätzlich verhindert. War mir garnicht klar.
          Danke für den Hinweis. Bisher hab ich auch nur à la datei.css?4 einen Versionsstand gekennzeichnet. Dass der Erfolg gleich ist wie das Cachen ganz zu verbieten war mir auch nicht bewusst.

          Neue Erkenntnisse (zumindest für mich): Man muss nur dem Apachen konfigurieren, dass er Caching-Header mitsenden soll, dann verhält sich der Browser (getestet mit Firefox) auch brav und fordert bei den nächsten Requests die eingebetteten Querystring-Ressourcen nicht mehr mit an. So zum Beispiel:

          <Files ~ "\.(css|js)$">  
            ExpiresActive On  
            ExpiresDefault "now plus 1 year"  
          </Files>
          

          dedlfix.

      3. Moin!

        dieser Teil wird dann per mod_rewrite wieder 'rausgeschmissen', weil der Request mit GET-Paramtern das Caching grundsätzlich verhindert. War mir garnicht klar.

        Das ist auch vollkommen falsch. Query-Strings haben absolut gar nichts mit Caching zu tun, es ist nur zufällig so, dass diese oftmals zum dynamischen Abfragen von Ergebnissen benutzt werden und deshalb Caching nicht unbedingt erwünscht ist.

        - Sven Rautenberg

        1. Tach!

          dieser Teil wird dann per mod_rewrite wieder 'rausgeschmissen', weil der Request mit GET-Paramtern das Caching grundsätzlich verhindert. War mir garnicht klar.
          Das ist auch vollkommen falsch. Query-Strings haben absolut gar nichts mit Caching zu tun, es ist nur zufällig so, dass diese oftmals zum dynamischen Abfragen von Ergebnissen benutzt werden und deshalb Caching nicht unbedingt erwünscht ist.

          Und vermutlich aus diesem Grund hat sich in meinen Nachforschungen und Beobachtungen zu diesem Thema gezeigt, dass zumindest der Firefox aus der Kalten Querystring-Anfragen nicht aus dem Cache beantwortet. Man muss es ihm extra mit Expires-/Cache-Control-Headern erlauben. "Absolut gar nichts" stimmt in dieser Absolutheit nur in der Theorie.

          dedlfix.

          1. Moin!

            Tach!

            dieser Teil wird dann per mod_rewrite wieder 'rausgeschmissen', weil der Request mit GET-Paramtern das Caching grundsätzlich verhindert. War mir garnicht klar.
            Das ist auch vollkommen falsch. Query-Strings haben absolut gar nichts mit Caching zu tun, es ist nur zufällig so, dass diese oftmals zum dynamischen Abfragen von Ergebnissen benutzt werden und deshalb Caching nicht unbedingt erwünscht ist.

            Und vermutlich aus diesem Grund hat sich in meinen Nachforschungen und Beobachtungen zu diesem Thema gezeigt, dass zumindest der Firefox aus der Kalten Querystring-Anfragen nicht aus dem Cache beantwortet. Man muss es ihm extra mit Expires-/Cache-Control-Headern erlauben. "Absolut gar nichts" stimmt in dieser Absolutheit nur in der Theorie.

            Das unterscheidet sich nicht von Ressourcen ohne Query-String.

            Wenn nicht irgendeine serverseitige Dynamik dahintersteht, sondern ganz normal eine statische Ressource, bewirkt der Query-String absolut nichts. Es kommen dieselben Header (auch im Hinblick auf Caching) zurück, wie ohne.

            Eine Änderung im Query-String hat allerdings die Wirkung, beim Client als eine andere URL angesehen zu werden. Wenn diese noch nicht im Cache ist, werden neue Requests an den Server geschickt. Und das ist ja genau, was man haben möchte.

            - Sven Rautenberg

            1. Tach!

              Eine Änderung im Query-String hat allerdings die Wirkung, beim Client als eine andere URL angesehen zu werden. Wenn diese noch nicht im Cache ist, werden neue Requests an den Server geschickt. Und das ist ja genau, was man haben möchte.

              Ja, aber man möchte nicht, dass der Browser die Ressource immer anfordert, sondern nur wenn man den Querystring geändert hat. Und hier macht einem (zumindest) der Firefox einen Strich durch die Rechnung, weil er ohne explizite Caching erlaubende Header diese Ressource immer wieder neu anfordert, nicht nur bei einer Querystring-Änderung. Das ist das Problem an der Geschichte. Wenn man die Querystring-Trick anwendet, braucht man die Expires-/Cache-Control-Header, sonst bekommt man den gegenteiligen Effekt, das die Ressource nicht zu lange sondern gar nicht mehr gecacht wird. (Chrome und Opera sind übrigens ok, die cachen auch ohne Header.)

              dedlfix.

    2. Tach!

      was spricht gegen <meta http-equiv="expires" content="0">? Gilt das nur für die HTML-Ressource?

      http-equiv steht für HTTP-Äquivalent. Und als solches steht es als Ersatz für eine HTTP-Header-Angabe. Ein HTTP-Header gilt immer nur für die damit eingeleiteten Daten.

      dedlfix.

  2. Hallo,

    Als dauerhafte Lösung kann ich mir eine Weiterentwicklung dieser Do-It-Yourself-Lösung vorstellen a lá "styles.css?<?=filetime("styles.css");?>". Ist es das oder geht's besser?

    Kurzversion: Ja, das ist es.

    Langversion: Üblicherweise setzt man bei Assets HTTP-Caching-Header, die den Browser dazu anweisen, das Asset bis zum Sankt Nimmerleinstag zu cachen und niemals beim Server nachzufragen, ob es eine neue Version gibt. Beim Einspielen einer neuen Version wird dann die Adresse geändert. Auf diese Weise ist auch sichergestellt, dass ein bestimmtes HTML immer mit einem bestimmten JS/CSS angezeigt wird. Das kann etwa dadurch erfolgen, dass das Änderungsdatum, eine Versionsnummer oder ein kryptographischer Hash des Dateiinhalts in die URL aufgenommen wird.

    Um so etwas zu automatisieren gibt es Build-Tools wie beispielsweise Grunt und HTML5 Boilerplate, die auch komprimieren und optimieren sowie die Verwendung von Präprozessoren wie Sass und CoffeeScript erlauben.

    Mathias