一个由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也就顺理成章了。可能采取的方案有:
- 采取某种过期策略,使得一旦发布了产品的新版本,立即使旧的index.htm过期。
- 禁用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参数,它可以选择设置为如下的值:
- NoControl:不添加Cache-Control或者Expire头。
- DisableCache:在response中添加Cache-Control: no-cache的头。
- UseMaxAge:添加Cache-Control: max-age=<nnn>的头,其中max-age的值由另一个参数CacheControlMaxAge指定。
- 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这一个页面生效了。