代码改变世界

一个由browser静态页面cache导致的问题

2013-05-14 08:05  swing_zhou  阅读(1098)  评论(0编辑  收藏  举报

最近我们的产品新版本上线部署了,一切都很顺利,却很快从用户那里报了一个奇怪的问题:一个之前就有的创建功能不能正常使用了。更为奇怪的是,按照用户提供的操作方式访问同样的页面,看到的却是完全不同的页面布局,并且无论如何也无法再现用户的那个问题。这是怎么回事呢?

背景介绍

为了描述清楚问题,先对我们的产品做一个简单介绍。我们产品前端实现为一个部署在IIS中的SPA(Single Page Application)站点,用户成功登陆后将会访问到一个唯一的页面index.htm,页面的大概样子是这样的:

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<link href="merged435678.css" media="screen" rel="stylesheet" type="text/css"/>
<script src="merged435678.js" type="text/javascript"></script>
</head>
<body>
<!-- 页面的实际内容 -->
</body>
</html>

用户在登陆后首先会在浏览器里加载index页面及其所引用的css和js文件,然后用户所有的后续操作都不会再做任何的页面跳转,而只需要通过在index页面里加载不同的子页面模板来显示不同的内容。或者说这个index页面就是我们站点里的唯一页面。注意到代码中head里面的css和js元素了吗?为了减少网络传输提高效率,我们把所有的css内容和js内容都分别做了合并和压缩处理,并且在生成的文件名里加上了一个与发布版本相关的版本号信息。为生成的文件名加上版本号,想必原因大家都很清楚:每当发布产品的一个新版本,由于文件名发生变化,所引用的js和css文件都可以做到重新加载而不至于在浏览器端拿到它们的旧版本。看起来很完美的一个解决方案,并且已经考虑了浏览器cache的问题,那用户遇到的问题又是怎么回事呢? 

问题分析

经过简单的分析,发现问题就出在index.htm页面本身。由于该页面是静态页面,因此也是可以被浏览器cache的。而与js和css文件不同的是,该文件名中并没有也不合适加上版本信息,因此极有可能client端没有办法识别文件是否被更新过以及是否需要被重新下载,因此一直拿到的就是browser里cache的那个旧版本。而旧版本的index里使用的一定是旧版本的js和css,自然用户看到的也就是旧版本的创建页面了。结论是,除非手动清空browser端cache,否则客户端没有办法得到新版本的index.htm。

找到了问题所在,solution也就顺理成章了。可能采取的方案有:

  1. 采取某种过期策略,使得一旦发布了产品的新版本,立即使旧的index.htm过期。
  2. 禁用index.htm的browser端cache。

显然方案1更加“智能”,但我们最终还是决定使用“简单粗暴”的方案2,原因是我们是SPA,理论上来说index.htm只会在登陆成功后加载一次,之后的所有操作都不需要去拿index,因此由于多次加载带来的性能上的损耗必然很小。

方案实施

经过查阅微软的相关文档,发现在IIS的7.0和7.5的web.config里专门有一个clientCache的配置段用来解决这样的问题。根据官方文档的说法,clientCache中的配置项会影响发送到客户端的response中的cache相关的http header。例如,在我们的这个问题里,web.config里就加上了如下的配置段:

1 <configuration>
2    <system.webServer>
3       <staticContent>
4          <clientCache cacheControlMode="DisableCache" />
5       </staticContent>
6    </system.webServer>
7 </configuration>

这里最主要的就是cacheControlMode参数,它可以选择设置为如下的值:

  1. NoControl:不添加Cache-Control或者Expire头。
  2. DisableCache:在response中添加Cache-Control: no-cache的头。
  3. UseMaxAge:添加Cache-Control: max-age=<nnn>的头,其中max-age的值由另一个参数CacheControlMaxAge指定。
  4. UseExpires:添加Expires: <date>的头来指定具体的过期时间。其中的过期日期由httpExpires参数指定。 

我们这里使用了“简单粗暴”的DisableCache,因此浏览器任何一次访问index页面都不会再cache并且从browser cache中去取得结果了。

问题到这里还没有结束。上面的配置方式是针对整个站点生效的,因此有些应该被cache的内容,如merge之后的js和css文件,现在也被“简单粗暴”的不能cache了。好在微软还给我们提供了<location>配置,用于帮助我们指定配置项希望对哪个路径生效。因此,把上面的配置改成这样:

1 <configuration>
2    <location path="index.htm">
3    <system.webServer>
4       <staticContent>
5          <clientCache cacheControlMode="DisableCache" />
6       </staticContent>
7    </system.webServer>
8    </location>
9 </configuration>

path参数指定到index.htm,就把我们的配置限定在只对index.htm这一个页面生效了。