关于javascript:缓存通过params破坏

Cache busting via params

我们希望在生产部署上缓存故障,但不要浪费大量的时间来寻找一个这样做的系统。我的想法是在当前版本号的css和js文件的末尾应用一个参数:

1
<link rel="stylesheet" href="base_url.com/file.css?v=1.123"/>

两个问题:这会有效地破坏缓存吗?该参数是否会导致浏览器从此不再缓存来自该URL的响应,因为该参数指示这是动态内容?


参数?v=1.123表示一个查询字符串,因此浏览器会认为这是来自,比如?v=1.0的新路径。从而导致它从文件而不是从缓存中加载。如你所愿。

而且,浏览器会假设下次调用?v=1.123时源代码将保持不变,并且应该用该字符串缓存它。因此,无论服务器如何设置,它都将保持缓存状态,直到您移动到?v=1.124或其他位置。


Two questions: Will this effectively break the cache?

对。即使堆栈溢出也使用这个方法,尽管我记得他们(每天有数百万的访问者,以及无数不同的客户端和代理版本和配置)有一些异常的边缘情况,即使这样也不足以破坏缓存。但一般的假设是,这将起作用,并且是打破客户机缓存的合适方法。

Will the param cause the browser to then never cache the response from that url since the param indicates that this is dynamic content?

不。该参数不会更改缓存策略;服务器发送的缓存头仍将应用,如果不发送,则为浏览器的默认值。


将版本号放在实际文件名中比较安全。这允许同时存在多个版本,以便您可以推出新版本,如果仍存在任何请求旧版本的缓存HTML页,则它们将获得与HTML一起使用的版本。

注意,在Internet上最大的版本化部署之一中,jquery使用实际文件名中的版本号,它可以安全地允许多个版本共存,而无需任何特殊的服务器端逻辑(每个版本只是一个不同的文件)。

在部署新页面和新链接文件(这是您想要的)时,这会占用缓存一次,从那时起,这些版本就可以有效地缓存(您也需要)。


正如其他人所说,使用查询参数进行缓存崩溃通常被认为是一个坏主意(tm),而且已经有很长时间了。最好在文件名中反映版本。HTML5样板文件建议不要使用查询字符串等。

这就是说,在我所看到的引用了一个消息来源的建议中,似乎所有人都从2008年史蒂夫·苏德斯的一篇文章中汲取了智慧。他的结论是基于当时代理人的行为,他们可能是或不可能是相关的。但是,在缺少更多当前信息的情况下,更改文件名是安全的选项。


一旦客户端下载了资源,它就会破坏缓存一次,除非:

  • 更新v参数。
  • 客户端清除其缓存

  • 一般来说,这应该是可以的,但是如果有一个中间缓存(代理)被配置为忽略请求参数,那么这可能不起作用。

    例如,如果您通过Akamai cdn提供静态内容,则可以将其配置为忽略请求参数,以防止使用此方法进行缓存总线。


    在此处找到两种技术(查询字符串与文件名)的比较:

    作为查询字符串的版本有两个问题。

    First, it may not always be a browser that implements caching through which we need to bust. It is said that certain (possibly older) proxies do ignore the querystring with respect to their caching behavior.

    Second, in certain more complex deployment scenarios, where you have multiple frontend and/or multiple backend servers, an upgrade is anything but instantaneous. You need to be able to serve both the old and the new version of your assets at the same time. See for example how this affects you when using Google App Engine.


    这在很大程度上取决于您希望缓存有多强大。例如,Squid代理服务器(可能还有其他服务器)默认为不缓存与查询字符串一起提供服务的URL——至少在编写该文章时是这样。如果您不介意某些用例导致不必要的缓存未命中,那么继续查询参数。但是,很容易建立一个基于文件名的缓存总线方案来避免这个问题。


    另一种类似的方法是在服务文件时使用htaccess mod_rewrite忽略部分路径。从不缓存的索引页引用文件的最新路径。

    从开发的角度来看,它就像在版本号中使用参数一样简单,但是它和文件名方法一样强大。

    使用路径中被忽略的部分作为版本号,服务器只会忽略它并为未缓存的文件提供服务。

    1.2.3/css/styles.csscss/styles.css使用相同的文件,因为htaccess文件将除去并忽略第一个目录。

    包括版本化文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <?php
      $version ="1.2.3";
    ?>

    <html>
      <head>
        <meta http-equiv="cache-control" content="max-age=0" />
        <meta http-equiv="cache-control" content="no-cache" />
        <meta http-equiv="expires" content="0" />
        <meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
        <meta http-equiv="pragma" content="no-cache" />
        <link rel="stylesheet" type="text/css" href="<?php echo $version ?>/css/styles.css">
      </head>
      <body>
        <script src="<?php echo $version ?>/js/main.js">
      </body>
    </html>

    注意,这种方法意味着您需要禁用索引页的缓存-使用标记来关闭所有浏览器中的缓存?

    HTAccess文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    RewriteEngine On

    # if you're requesting a file that exists, do nothing
    RewriteCond %{REQUEST_FILENAME} !-f
    # likewise if a directory that exists, do nothing
    RewriteCond %{REQUEST_FILENAME} !-d

    # otherwise, rewrite foo/bar/baz to bar/baz - ignore the first directory
    RewriteRule ^[^/]+/(.+)$ $1 [L]

    您可以在任何允许URL重写的服务器平台上使用相同的方法。

    (重写条件改编自mod_rewrite-rewrite directory to query string except/!)

    …如果您的索引页/站点入口点需要缓存总线,您可以始终使用javascript来刷新它。


    1
    2
    3
    4
    5
    6
    7
    8
    9
    <script type="text/javascript">
    // front end cache bust

    var cacheBust = ['js/StrUtil.js', 'js/protos.common.js', 'js/conf.js', 'bootstrap_ECP/js/init.js'];  
    for (i=0; i < cacheBust.length; i++){
         var el = document.createElement('script');
         el.src = cacheBust[i]+"?v=" + Math.random();
         document.getElementsByTagName('head')[0].appendChild(el);
    }