2010-08-11 7 views
15

Wenn ich richtig verstehe, speichert ein Broswor Bilder, JS-Dateien usw. basierend auf dem Dateinamen. Es besteht also die Gefahr, dass der Browser, wenn eine solche Datei aktualisiert wird (auf dem Server), stattdessen die zwischengespeicherte Kopie verwendet.clientseitige Datei-Caching

Eine Umgehung für dieses Problem besteht darin, alle Dateien (als Teil des Builds) umzubenennen, sodass der Dateiname einen MD5-Hash seines Inhalts enthält, z.

foo.js -> foo_AS577688BC87654.js 
me.png -> me_32126A88BC3456BB.png 

Zusätzlich zu dem Umbenennen der Dateien müssen jedoch alle Verweise auf diese Dateien geändert werden. Zum Beispiel sollte ein Tag wie <img src="me.png"/> zu <img src="me_32126A88BC3456BB.png"/> geändert werden.

Offensichtlich kann dies ziemlich kompliziert werden, insbesondere wenn Sie bedenken, dass Verweise auf diese Dateien dynamisch innerhalb des serverseitigen Codes erstellt werden können.

Eine Lösung besteht natürlich darin, die Zwischenspeicherung im Browser (und alle Caches zwischen dem Server und dem Browser) mithilfe von HTTP-Headern vollständig zu deaktivieren. Wenn Sie jedoch kein Caching durchführen, wird dies zu einer Reihe von Problemen führen.

Gibt es eine bessere Lösung?

Danke, Don

+2

Ist das wirklich ein Problem? Moderne Browser überprüfen mindestens einmal pro Sitzung, wenn nicht alle Seiten aktualisiert werden, um zu sehen, ob ein Bild oder eine js-Datei oder eine andere referenzierte Datei durch Hinzufügen eines if-modified-since-Headers in ihrer Anfrage aktualisiert wird. Wenn die Datei nicht geändert wird, gibt der Webserver 304 zurück und der Browser verwendet die zwischengespeicherte Datei. – GrandmasterB

+0

Ich habe einen Fall bemerkt, bei dem Safari den Cache nicht aktualisiert hat, obwohl der Benutzer F5 verwendet hat. Leider habe ich keine gründliche Überprüfung vorgenommen, da es über das Telefon lief und nur der Client es zu dieser Zeit ansah, also ließ ich den Benutzer seinen Browser-Cache leeren und dann griff Safari zur neuen Version. –

+0

* basierend auf dem Dateinamen * - nicht ganz, siehe ETags: http://en.wikipedia.org/wiki/HTTP_ETag Was Sie tun würden, ist, verwenden Sie Ihren Hash als ETag der Ressource. – Douglas

Antwort

15

Die beste Lösung scheint zu Version Dateinamen durch Anhängen der letzten modifizierten Zeit zu sein.

Sie können es auf diese Weise tun: eine Rewrite-Regel zu Ihrer Apache-Konfiguration hinzufügen, etwa so:

RewriteRule ^(.+)\.(.+)\.(js|css|jpg|png|gif)$ $1.$3 

Dies wird jegliche „versioniert“ URL zu den „normalen“ einer Umleitung. Die Idee ist, Ihre Dateinamen gleich zu halten, aber vom Cache zu profitieren. Die Lösung, einen Parameter an die URL anzuhängen, ist bei einigen Proxies, die keine URLs mit Parametern zwischenspeichern, nicht optimal.

Dann wird anstelle des Schreibens:

<img src="image.png" /> 

Nur eine Funktion PHP rufen:

<img src="<?php versionFile('image.png'); ?>" /> 

Mit versionFile() wie folgt aussehen:

function versionFile($file){ 
    $path = pathinfo($file); 
    $ver = '.'.filemtime($_SERVER['DOCUMENT_ROOT'].$file).'.'; 
    echo $path['dirname'].'/'.str_replace('.', $ver, $path['basename']); 
} 

Und das ist es! Der Browser fragt nach image.123456789.png, Apache leitet dies zu image.png um, so dass Sie in allen Fällen vom Cache profitieren und kein veraltetes Problem haben, während Sie sich nicht mit der Versionierung des Dateinamens herumschlagen müssen .

Sie können eine detaillierte Erklärung dieser Technik siehe hier: http://particletree.com/notebook/automatically-version-your-css-and-javascript-files/

+0

Dies ist bei weitem die beste Lösung hier. –

+0

Danke! :-) Aber der Autor von Particletree verdient den ganzen Kredit. – PJP

9

warum die Version jedes Mal nicht nur eine Abfragezeichenfolgeflag "Version" Nummer hinzufügen und aktualisieren?

foo.js - zu aktualisieren, müssen Sie die Versionsnummern aber Dateinamen nicht ändern> foo.js version = 5

Es gibt immer noch ein bisschen Arbeit während des Build ist.

+3

vereinbart als gemeinsame/gute Praxis. – darma

+4

Abfragestrings sind eine schlechte Idee, wenn Sie sich um das Caching kümmern, da die meisten Proxy-Server keine Ressourcen mit einem '?' in ihrer URL. Siehe http://code.google.com/speed/page-speed/docs/caching.html. –

+2

Paul - Ich denke, dass die Empfehlungen auf dieser Seite unvollständig sind. Abschnitt 13.9 von RFC 2616 (die HTTP 1.1-Spezifikation) scheint zu implizieren, dass Abfragestrings im Cache gespeichert werden können, wenn ein expliziter Expires-Header vorhanden ist, sodass die Verwendung eines Querystrings in Ordnung ist. Aber das bedeutet nicht, dass alle Caches dies honorieren, also bleibe ich wahrscheinlich bei der Änderung der Dateinamen. –

6

Ihre Ressourcen Umbenennung ist der Weg zu gehen, obwohl wir eine Build-Nummer und einbetten verwenden, die in den Dateinamen anstelle einem MD5-Hash

foo.js -> foo.123.js 

, da es bedeutet, dass alle Ihre Ressourcen in umbenannt werden deterministisch und zur Laufzeit aufgelöst.

Wir verwenden dann benutzerdefinierte Steuerelemente zum Generieren von Links zu Ressourcen beim Laden der Seite basierend auf der Build-Nummer, die in einer App-Einstellung gespeichert ist.

1

Ich habe auch darüber nachgedacht für eine Website, die ich unterstütze, wo es eine große Aufgabe wäre, alle Referenzen zu ändern. Ich habe zwei Ideen:

1. Legen Sie entfernte Ablaufverzeichnisse des Cache fest und wenden Sie die Änderungen an, die Sie für die am häufigsten heruntergeladenen Dateien vorschlagen. Bei anderen Dateien setzen Sie die Header so, dass sie nach kurzer Zeit ablaufen - zB. 10 Minuten. Wenn Sie beim Aktualisieren der Anwendung eine 10-minütige Downtime haben, werden die Caches aktualisiert, sobald die Benutzer die Site aufrufen. Die allgemeine Websitenavigation sollte verbessert werden, da die Dateien nur alle 10 Minuten heruntergeladen werden müssen, nicht bei jedem Klick.

2. Jedes Mal, wenn eine neue Version der Anwendung in einem anderen Kontext bereitgestellt wird, der die Versionsnummer enthält. z.B. www.site.com/app_2_6_0/ Ich bin mir nicht wirklich sicher, da die Lesezeichen der Benutzer bei jedem Update beschädigt werden.

3

Wir folgten einem ähnlichen Muster wie PJP, Rails und Nginx mit.

Wir wollten, dass Benutzer-Avatar-Bilder im Browser zwischengespeichert werden, aber bei einer Änderung des Avatars mussten wir den Cache so schnell wie möglich ungültig machen.

Wir haben eine Methode zum Avatar-Modell einen Zeitstempel an den Dateinamen anhängen:

return "/images/#{sourcedir}/#{user.login}-#{self.updated_at.to_s(:flat_string)}.png" 

In allen Stellen im Code, wo Avatare verwendet wurden, wir diese Methode verwiesen wird, anstatt eine URL. In der Nginx Konfiguration, haben wir diese Rewrite:

rewrite "^/images/avatars/(.+)-[\d]{12}.png" /images/avatars/$1.png; 
rewrite "^/images/small-avatars/(.+)-[\d]{12}.png"  /images/small-avatars/$1.png; 

Das bedeutet, wenn eine Datei geändert wird, die entsprechende URL in der HTML-Code geändert, so dass der Browser des Benutzers einen neuen Antrag für die Datei. Wenn die Anfrage Nginx erreicht hat, wurde sie in den einfachen Namen der Datei umgeschrieben.

2

Die meisten modernen Browser überprüfen den Header if-modified-since, wenn sich eine cachefähige Ressource in einer HTTP-Anforderung befindet. Allerdings unterstützen nicht alle Browser den if-modified-since-Header.

Es gibt drei Möglichkeiten, den Browser zum Laden einer zwischengespeicherten Ressource zu zwingen.

Option 1 Erstellen Sie eine Abfragezeichenfolge mit einer Versionsnummer. src="script.js?ver=21". Der Nachteil ist, dass viele Proxy-Server eine Ressource mit Abfragezeichenfolgen nicht zwischenspeichern. Es erfordert auch eine standortweite Aktualisierung für Änderungen.

Option 2 Erstellen Sie ein Benennungssystem für Ihre Dateien src="script083010.js". Der Nachteil von Option 1 besteht jedoch darin, dass auch Änderungen an der Site bei jeder Dateiänderung erforderlich sind.

Option 3 Vielleicht die eleganteste Lösung, richten Sie einfach die Caching-Header: zuletzt geändert und läuft in Ihrem Server ab.Der Hauptnachteil dabei ist, dass Benutzer möglicherweise Ressourcen neu cachen müssen, weil sie abgelaufen sind, sich aber nie geändert haben. Darüber hinaus funktioniert der zuletzt geänderte Header nicht gut, wenn der Inhalt von mehreren Servern bereitgestellt wird.

Hier ein paar Ressourcen, um zu überprüfen: YahooGoogleAskApache.com

+0

Lol las deine Antwort nach meiner, fast genau so. +1 für hivemind. – Incognito

3

Ich würde von ETags in dieser Situation unter Verwendung von Caching vorschlagen, siehe http://en.wikipedia.org/wiki/HTTP_ETag. Sie können dann den Hash als Etag verwenden. Für jede Ressource wird weiterhin eine Anforderung gesendet, aber der Browser lädt nur die Elemente herunter, die sich seit dem letzten Download geändert haben.

Lesen Sie auf Ihrem Web-Server/Plattform Dokumente auf, wie man Etags richtig verwendet, haben die meisten anständigen Plattformen eingebaute Unterstützung.

2

Dies ist wirklich nur ein Problem, wenn Ihr Web-Server eine weit entfernte "Expires" -Kopfzeile setzt (so etwas wie ExpiresDefault "access plus 10 years" in Ihrer Apache-Konfiguration). Andernfalls erstellt ein Browser basierend auf der modifizierten Zeit und/oder dem Etag ein bedingtes GET. Sie können überprüfen, was auf Ihrer Site passiert, indem Sie einen Web-Proxy oder eine Erweiterung wie Firebug (im Net-Panel) verwenden. In Ihrer Frage wird nicht erwähnt, wie Ihr Webserver konfiguriert ist und welche Header mit statischen Dateien gesendet werden.

Wenn Sie keine ferne Zukunft festlegen, müssen Sie nichts Besonderes tun. Normalerweise verarbeitet Ihr Webserver bedingte GETs für statische Dateien basierend auf der letzten modifizierten Zeit. Wenn Sie einen Expires-Header für ferne Zukunft festlegen, dann müssen Sie dem Dateinamen eine Art Version hinzufügen, wie Ihre Frage und die anderen Antworten bereits erwähnt haben.

1

Ich glaube, dass eine Kombination von Lösungen am besten funktioniert:

  1. Einstellung Cache-Ablaufdaten für jede Art von Ressource (Bild, Seite, usw.) appropreatly für diese Ressource, zum Beispiel:

    • Ihre statischen "About", "Contact", usw. Seiten werden sich wahrscheinlich nur wenige Male im Jahr ändern, so dass Sie einfach einen Cache von einem Monat auf diese Seiten setzen können.
    • Bilder, die auf diesen Seiten verwendet werden, können eine ewige Cache-Zeit haben, da Sie eher ein Bild ersetzen und dann ein Bild ändern möchten.
    • Avatarbilder können eine Ablaufzeit von einem Tag haben.
  2. Einige Ressourcen benötigen in ihren Namen modifizierte Daten. Zum Beispiel Avatare, erzeugte Bilder und dergleichen.

  3. Einige Dinge sollten nie Caches, neue Seiten, Benutzerinhalte usw. sein. In diesen Fällen sollten Sie auf dem Server cachen, aber niemals auf der Client-Seite.

Am Ende müssen Sie carfully, um jede Art von Ressource zu prüfen, welche Cache-Zeit zu bestimmen, um den Browser zu instruieren, zu verwenden und immer conservitive sein, wenn Sie sich nicht sicher sind. Sie können die Zeit später erhöhen, aber es ist viel schmerzhafter, etwas zu cachen.

1

Sie können sich die Vorgehensweise des Grails "uiperformance" -Plugins ansehen, das Sie unter here finden. Es macht eine Menge der Dinge, die Sie erwähnen, aber automatisiert sie (setzen Ablaufzeit auf eine lange Zeit, dann erhöht Versionsnummern, wenn Dateien ändern).

Also, wenn Sie Grails verwenden, erhalten Sie dieses Zeug kostenlos.Wenn Sie nicht sind - vielleicht können Sie die angewendeten Techniken ausleihen.

Auch - geliehen von der UI-Performance-Seite, - lesen Sie die folgenden 14 rules.

1

ETags bieten scheinbar eine Lösung für dieses ...

Per http://httpd.apache.org/docs/2.0/mod/core.html#fileetag wir den Browser so einstellen können ETags auf Datei-Größe (statt Zeit/inode/etc) zu erzeugen. Diese Generierung sollte für mehrere Serverbereitstellungen konstant sein.

ermöglichen es einfach in (/etc/apache2/apache2.conf)

FileETag Size 

& Sie sollte gut sein!

Auf diese Weise können Sie Ihre Bilder einfach als <img src='/path/to/foo.png' /> referenzieren und trotzdem alle Vorteile des HTTP-Caching nutzen.