我注意到一些浏览器(特别是Firefox和Opera)非常热衷于使用.css和.js文件的缓存副本,甚至在浏览器会话之间。当您更新其中一个文件时,这会导致一个问题,但用户的浏览器会继续使用缓存的副本。

当文件发生更改时,强迫用户浏览器重新加载文件的最优雅的方法是什么?

理想情况下,该解决方案不会强制浏览器在每次访问页面时重新加载文件。


我发现John Millikin和da5id的建议很有用。这有一个专门的术语:自动版本控制。

我在下面发布了一个新的答案,这是我最初的解决方案和约翰的建议的结合。

SCdF建议的另一个想法是将伪查询字符串附加到文件中。(一些自动使用时间戳作为伪查询字符串的Python代码是由pi..提交的)

然而,关于浏览器是否缓存带有查询字符串的文件还存在一些讨论。(请记住,我们希望浏览器缓存该文件并在以后的访问中使用它。我们只希望它在文件更改时再次获取该文件。)


当前回答

一个特定于silverstripe的答案是:http://api.silverstripe.org/3.0/source-class-SS_Datetime.html#98-110:

希望这将帮助使用SilverStripe模板的人,并试图在每次页面访问/刷新时强制重新加载缓存图像。在我的情况下,它是一个GIF动画,只播放一次,因此没有重放后,它被缓存。在我的模板中,我简单地添加了:

?$Now.Format(dmYHis)

添加到文件路径的末尾,以创建唯一的时间戳并强制浏览器将其视为新文件。

其他回答

感谢Kip的完美解决方案!

我对它进行了扩展,将其用作Zend_view_Helper。因为我的客户端在虚拟主机上运行他的页面,我也为此扩展了它。

/**
 * Extend filepath with timestamp to force browser to
 * automatically refresh them if they are updated
 *
 * This is based on Kip's version, but now
 * also works on virtual hosts
 * @link http://stackoverflow.com/questions/118884/what-is-an-elegant-way-to-force-browsers-to-reload-cached-css-js-files
 *
 * Usage:
 * - extend your .htaccess file with
 * # Route for My_View_Helper_AutoRefreshRewriter
 * # which extends files with there timestamp so if these
 * # are updated a automatic refresh should occur
 * # RewriteRule ^(.*)\.[^.][\d]+\.(css|js)$ $1.$2 [L]
 * - then use it in your view script like
 * $this->headLink()->appendStylesheet( $this->autoRefreshRewriter($this->cssPath . 'default.css'));
 *
 */
class My_View_Helper_AutoRefreshRewriter extends Zend_View_Helper_Abstract {

    public function autoRefreshRewriter($filePath) {

        if (strpos($filePath, '/') !== 0) {

            // Path has no leading '/'
            return $filePath;
        } elseif (file_exists($_SERVER['DOCUMENT_ROOT'] . $filePath)) {

            // File exists under normal path
            // so build path based on this
            $mtime = filemtime($_SERVER['DOCUMENT_ROOT'] . $filePath);
            return preg_replace('{\\.([^./]+)$}', ".$mtime.\$1", $filePath);
        } else {

            // Fetch directory of index.php file (file from all others are included)
            // and get only the directory
            $indexFilePath = dirname(current(get_included_files()));

            // Check if file exist relativ to index file
            if (file_exists($indexFilePath . $filePath)) {

                // Get timestamp based on this relativ path
                $mtime = filemtime($indexFilePath . $filePath);

                // Write generated timestamp to path
                // but use old path not the relativ one
                return preg_replace('{\\.([^./]+)$}', ".$mtime.\$1", $filePath);
            } else {
                return $filePath;
            }
        }
    }
}

更改文件名也可以。但这通常不是最简单的解决方案。

正如你所注意到的,HTTP缓存控制标头为“no-cache”并不总是有效。HTTP 1.1规范为用户代理提供了决定是否请求新副本的回旋余地。(如果只看指令的名称,这是不直观的。去阅读实际的HTTP 1.1缓存规范…这在上下文中更有意义。)

简而言之,如果你想要严格的缓存控制使用

Cache-Control: no-cache, no-store, must-revalidate

在响应头中。

简单的客户端技术

一般来说,缓存是好的…所以有一些技术,取决于你是在开发网站时为自己解决问题,还是在生产环境中控制缓存。

一般访问者不会有相同的体验,当你在开发网站时。由于一般访问者访问站点的频率较低(可能每个月只有几次,除非您是谷歌或hi5 Networks),那么他们不太可能将您的文件保存在缓存中,这可能就足够了。

如果你想强制一个新版本进入浏览器,你可以在请求中添加一个查询字符串,并在你进行重大更改时增加版本号:

<script src="/myJavascript.js?version=4"></script>

这将确保每个人都获得新文件。它可以工作,因为浏览器会查看文件的URL,以确定缓存中是否有副本。如果您的服务器没有设置为对查询字符串执行任何操作,那么它将被忽略,但是对于浏览器来说,该名称将看起来像一个新文件。

另一方面,如果您正在开发一个网站,您不希望每次保存对开发版本的更改时都更改版本号。那太乏味了。

所以当你在开发你的网站时,一个很好的技巧是自动生成一个查询字符串参数:

<!-- Development version: -->
<script>document.write('<script src="/myJavascript.js?dev=' + Math.floor(Math.random() * 100) + '"\><\/script>');</script>

在请求中添加一个查询字符串是一个资源版本化的好方法,但对于一个简单的网站来说,这可能是不必要的。记住,缓存是一件好事。

It's also worth noting that the browser isn't necessarily stingy about keeping files in cache. Browsers have policies for this sort of thing, and they are usually playing by the rules laid down in the HTTP specification. When a browser makes a request to a server, part of the response is an Expires header... a date which tells the browser how long it should be kept in cache. The next time the browser comes across a request for the same file, it sees that it has a copy in cache and looks to the Expires date to decide whether it should be used.

信不信由你,实际上是你的服务器使浏览器缓存如此持久。您可以调整服务器设置并更改Expires报头,但我上面写的小技巧可能是一种更简单的方法。由于缓存很好,所以您通常希望将日期设置为遥远的将来(“far -future Expires Header”),并使用上面描述的技术强制更改。

如果你想了解更多关于HTTP的信息或者这些请求是如何发出的,一本很好的书是Steve Souders的《高性能网站》。这是对这门学科很好的介绍。

如果你添加session-id作为JavaScript/CSS文件的伪参数,你可以强制“会话范围缓存”:

<link rel="stylesheet" src="myStyles.css?ABCDEF12345sessionID" />
<script language="javascript" src="myCode.js?ABCDEF12345sessionID"></script>

如果需要版本级缓存,可以添加一些代码来打印文件日期或类似内容。如果您正在使用Java,您可以使用自定义标记以一种优雅的方式生成链接。

<link rel="stylesheet" src="myStyles.css?20080922_1020" />
<script language="javascript" src="myCode.js?20080922_1120"></script>

有趣的文章。阅读了这里所有的答案,并结合我从未遇到过任何“伪”查询字符串问题的事实(我不确定为什么每个人都不愿意使用这个),我猜解决方案(它消除了Apache重写规则的需要,因为在接受的答案)是计算CSS文件内容的短哈希(而不是文件日期时间)作为伪查询字符串。

这将导致以下结果:

<link rel="stylesheet" href="/css/base.css?[hash-here]" type="text/css" />

当然,datetime解决方案也可以在编辑CSS文件的情况下完成工作,但我认为这是关于CSS文件的内容,而不是关于文件datetime,所以为什么要把这些混为一谈呢?