客户端文件缓存

时间:2010-08-11 14:17:31

标签: caching

如果我理解正确,broswer会根据文件名缓存图像,JS文件等。因此,如果更新了一个此类文件(在服务器上),则浏览器将使用缓存副本。

此问题的解决方法是重命名所有文件(作为构建的一部分),以便文件名包含其内容的MD5哈希值,例如

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

但是,除了重命名文件本身之外,还必须更改对这些文件的所有引用。例如,<img src="me.png"/>等标记应更改为<img src="me_32126A88BC3456BB.png"/>

显然,这可能变得非常复杂,特别是当您考虑可以在服务器端代码中动态创建对这些文件的引用时。

当然,一种解决方案是使用HTTP标头完全禁用浏览器(以及服务器和浏览器之间的任何缓存)的缓存。但是,没有缓存会产生一系列问题。

有更好的解决方案吗?

谢谢, 唐

11 个答案:

答案 0 :(得分:16)

最佳解决方案似乎是通过附加上次修改时间来版本文件名。

您可以这样做:向Apache配置添加重写规则,如下所示:

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

这会将任何“版本化”网址重定向到“正常”网址。我们的想法是保持您的文件名相同,但要从缓存中受益。对于某些不使用参数缓存URL的代理,将参数附加到URL的解决方案将不是最佳的。

然后,而不是写:

<img src="image.png" />

只需调用PHP函数:

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

使用如下所示的versionFile():

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

就是这样!浏览器将要求image.123456789.png,Apache会将其重定向到image.png,因此您将在所有情况下从缓存中受益,并且不会有任何过时的问题,而不必费心去文件名版本控制

您可以在此处查看此技术的详细说明:http://particletree.com/notebook/automatically-version-your-css-and-javascript-files/

答案 1 :(得分:9)

为什么不添加查询字符串“版本”号并每次更新版本?

foo.js - &gt; foo.js?版本= 5

在构建期间还有一些工作要更新版本号,但文件名不需要更改。

答案 2 :(得分:6)

重命名资源是可行的方法,尽管我们使用内部版本号并将其嵌入到文件名而不是MD5哈希

foo.js -> foo.123.js

因为这意味着您可以以确定的方式重命名所有资源并在运行时解决。

然后,我们使用自定义控件根据存储在应用设置中的内部版本号生成页面加载资源的链接。

答案 3 :(得分:3)

我们使用Rails和Nginx对PJP采用了类似的模式。

我们希望用户头像图像被浏览器缓存,但是在头像的更改中,我们需要尽快使缓存无效。

我们在头像模型中添加了一个方法,将时间戳附加到文件名:

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

在代码中使用头像的所有位置,我们引用了此方法而不是URL。在Nginx配置中,我们添加了这个重写:

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

这意味着如果文件发生更改,其HTML中的URL也会更改,因此用户的浏览器会对该文件发出新请求。当请求到达Nginx时,它被重写为文件的简单名称。

答案 4 :(得分:3)

我建议在这种情况下使用ETags进行缓存,请参阅http://en.wikipedia.org/wiki/HTTP_ETag。然后,您可以将哈希用作etag。仍将为每个资源提交请求,但浏览器将仅下载自上次下载以来已更改的项目。

阅读关于如何正确使用etags的网络服务器/平台文档,大多数体面的平台都有内置的支持。

答案 5 :(得分:2)

大多数现代浏览器会在可缓存资源位于HTTP请求中时检查if-modified-since标头。但是,并非所有浏览器都支持if-modified-since标头。

有三种方法可以“强制”浏览器加载缓存资源。

选项1 使用版本#创建查询字符串。 src="script.js?ver=21"。缺点是许多代理服务器不会使用查询字符串缓存资源。它还需要在站点范围内更新更改。

选项2 为您的文件src="script083010.js"创建命名系统。然而,选项1的缺点是,只要文件发生变化,这也需要站点范围的更新。

选项3 也许是最优雅的解决方案,只需设置缓存标头:最后修改并在服务器中过期。这样做的主要缺点是用户可能必须重新缓存资源,因为它们已过期但从未更改过。此外,当从多个服务器提供内容时,最后修改的标头不能正常工作。

以下是一些要查看的资源:Yahoo Google AskApache.com

答案 6 :(得分:2)

如果您的Web服务器设置了一个远期的“Expires”标头(在Apache配置中设置类似ExpiresDefault "access plus 10 years"),那么这只是一个问题。否则,浏览器将根据修改的时间和/或Etag进行条件GET。您可以使用Web代理或Firebug等扩展程序(在Net面板上)验证您网站上发生的情况。您的问题没有提到您的Web服务器是如何配置的,以及它使用静态文件发送的标头。

如果您没有设置远期的Expires标头,那么您无需做任何特别的事情。您的Web服务器通常会根据上次修改时间处理静态文件的条件GET。如果您要设置远期Expires标头然后是,则需要在文件名中添加某种版本,例如您的问题,其他答案已经提到过。

答案 7 :(得分:1)

对于我支持的网站,我一直在考虑这个问题,改变所有引用都是一项重要工作。我有两个想法:

1。 设置远程缓存到期标头并应用您建议的最常下载文件的更改。对于其他文件设置标题,以便它们在很短的时间后过期 - 例如。 10分钟。然后,如果您在更新应用程序时有10分钟的停机时间,则用户访问该站点时将刷新缓存。应该改进常规站点导航,因为文件只需要每10分钟下载一次而不是每次点击。

2。 每次将新版本的应用程序部署到包含版本号的其他上下文时。例如。 www.site.com/app_2_6_0/我不太确定这一点,因为每次更新都会破坏用户书签。

答案 8 :(得分:1)

我相信解决方案的组合最有效:

  1. 为该资源适当地设置每种资源类型(图像,页面等)的缓存到期日期,例如:

    • 您的静态“关于”,“联系”等页面可能不会每年更改一次,因此您可以轻松地在这些页面上放置一个月的缓存时间。
    • 这些页面中使用的图像可能具有永久缓存时间,因为您更喜欢替换图像然后更改图像。
    • 头像图片可能有一天的到期时间。
  2. 某些资源需要在其名称中修改日期。例如,头像,生成的图像等。

  3. 有些东西永远不应该是缓存,新页面,用户内容等。在这些情况下,您应该缓存在服务器上,但绝不应该在客户端缓存。

  4. 最后,您需要仔细考虑每种类型的资源,以确定指示浏览器使用的缓存时间,如果您不确定,则始终保持一致。你可以稍后增加时间,但解开某些东西会更加痛苦。

答案 9 :(得分:1)

您可能想查看grails“uiperformance”插件所采用的方法,您可以找到here。它提到了很多你提到的东西,但它们会自动化(设置到期时间很长,然后在文件更改时增加版本号)。

因此,如果你使用grails,你可以免费获得这些东西。如果你不是 - 也许你可以借用所用的技术。

此外 - 从ui-performance页面借用, - 阅读以下14 rules

答案 10 :(得分:1)

ETags似乎为此提供了解决方案......

根据http://httpd.apache.org/docs/2.0/mod/core.html#fileetag,我们可以设置浏览器以文件大小生成ETag(而不是time / inode / etc)。这一代应该在多个服务器部署中保持不变。

只需在(/etc/apache2/apache2.conf)

中启用它
FileETag Size

&安培;你应该好!

这样,你可以简单地将你的图像引用为<img src='/path/to/foo.png' />,并仍然使用HTTP缓存的所有优点。