再次揭露text/vnd.wap.wml引起的ASP.NET OutputCache Bug及解决方法

在前一篇文章“一个伴随ASP.NET从1.0到4.0的OutputCache Bug”中,揭露了ASP.NET OutputCache的一个浏览器缓存的Bug。

在这篇文章中,我们将揭露ASP.NET OutputCache的另一个Bug,这个Bug在去年2月份的时候揭露过一次,但是当时揭露不够彻底,解决方法也不够完美。这里再揭露一次。

背景介绍

为了解决非电信用户访问博客园的网速问题(尤其是北方网通用户),我们准备采用CDN加速。要让CDN加速发挥作用,就要建立有效的页面缓存机制(OutputCache Location要设置为"Any")。为了解决这个实际中的应用问题,必须要好好研究ASP.NET OutputCache,所以才会揭露它的Bug。

重要提示

如果你在ASP.NET应用程序(非ASP.NET MVC)中使用了OutputCache,一定要关注这个Bug,一定要动手去解决它!

问题现象

如果你见过上面的身影,你就曾经与这个Bug相遇过或者重逢过。(不经意的相遇或者重逢,也许只是擦肩而过,也许就是天长地久;不要期待偶然,但也不要忽视偶然。)

当初我们与它相遇时,它“忽隐忽现”(出现在不多见的特定的情况下),让人“神魂颠倒”(面对这个问题无从下手)。

浏览器为什么会给出这个窗口呢?

只是因为Web服务器和浏览器说了一句话(Response Headers):"Content-Type:text/vnd.wap.wml; charset=utf-8"。为什么要说这样的话?请看下面的分解。

问题发生过程

1)当你在一个ASP.NET页面中增加了OutputCache设置,比如:

<%@ OutputCache Duration="300" VaryByParam="*"%>

2)然后,在缓存没有建立(或者已经过期)时,一部手机通过浏览器以WAP方式第一个请求了这个页面(Request headers中包含"Accept:text/vnd.wap.wml",这样的请求可能通过代码进行模拟,详见这里)。

3)接着,ASP.NET将这个请求的响应缓存了起来。这个操作是在System.Web.Caching.OutputCacheModule.OnLeave中进行的,缓存内容来自System.Web.HttpResponse.GetSnapshot(),不仅缓存了Response body,而且缓存了Response headers。

*【关键地方】在缓存Response headers时,ASP.NET根据"C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\Browsers\Default.browser"中的配置进行了特殊处理:

<defaultBrowser id="Wml" parentID="Default">
<identification>
<header name="Accept" match="text/vnd\.wap\.wml|text/hdml"/>
<header name="Accept" nonMatch="application/xhtml\+xml; profile|application/vnd\.wap\.xhtml\+xml"/>
</identification>
<capabilities>
<capability name="preferredRenderingMime" value="text/vnd.wap.wml"/>
<capability name="preferredRenderingType" value="wml11"/>
</capabilities>
</defaultBrowser>

由于发起请求的Accept为text/vnd.wap.wml,所以上面的正好匹配,然后ASP.NET根据这里的配置,将Response headers中Content-Type的修改为"text/vnd.wap.wml; charset=utf-8",并将之缓存。

4)接下来的在缓存周期内的请求都是由这个缓存来迎接(操作在System.Web.Caching.OutputCacheModule.OnEnter中进行,最终由System.Web.HttpResponse.UseSnapshot返回缓存内容),只要不是WAP方式的请求,就会出现上面图中的下载文件对话框。

在解决这个问题时,我们试图捕捉这个响应,通过FireFox的Firebug, Chrome的Developer tools, Fiddler都没捕捉到,后来才试了试IE9的Developer tools,竟然捕捉到了(第一次享受到IE9带来的惊喜)。见下图:

问题的原因就在这里,正确的Content-Type应该是"text/html; charset=utf-8",ASP.NET却对WAP请求进行了特殊对待,由Accept决定Content-Type(难道是“屁股决定脑袋”)。可能是WAP的应用场景需要这样的设计,但在设计时却没有考虑对OutputCache的影响。

(注:我们测试了,将客户端请求的Accept改为其他类型,比如text/plain,都不存在这个问题)

从前面描述的过程中,我们可以知道,解决问题要在第3步提到的Default.browser文件下手。

推荐解决方法

前提条件:当前Web服务器上没有WAP站点

优点:一处更改,对当前Web服务器上的所有站点都有效。

操作步骤:

  • 进入C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\Browsers
  • 将Default.browser文件复制到桌面(另外再复制一份到另外一个文件夹进行备份)
  • 用你喜欢的编辑打开桌面上的Default.browser文件,找到<defaultBrowser id="Wml" parentID="Default" >部分,注释或者删除以下配置并保存:
<capabilities>
<capability name="preferredRenderingMime" value="text/vnd.wap.wml"/>
<capability name="preferredRenderingType" value="wml11"/>
</capabilities>
  • 将修改过的Default.browser文件复制至"C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\Browsers",覆盖同名文件。
  • 以管理员身份运行命令行,cd进入"C:\Windows\Microsoft.NET\Framework\v4.0.30319",运行命令"aspnet_regbrowsers -i":
    3
  • 大功造成,然后就可以尽情地进行OutputCache。

注:该解决方法基于之前的经验 —— ASP.NET4中不要相信Request.Browser.Cookies,Form验证要用UseCookies,如果当时不写博客,找到这个解决方法就没这么容易了。

知道了原因,解决方法有多种,我们这里只列出我们将采用的解决方法。

ASP.NET MVC呢?

在ASP.NET MVC中没有这个Bug,前一文章中提到的“浏览器缓存的Bug”在ASP.NET MVC中也不存在。

小结

1)为什么ASP.NET有这些Bug而ASP.NET MVC没有?

我想其中一个重要的原因就是ASP.NET的封闭,ASP.NET MVC的开放(开源)—— 有了源代码,开发者发现问题时可以更快地找到真正的原因,然后反馈给微软。即使微软不能立即解决问题,至少我可以修改源代码自己解决问题,有了这个条件,开发者遇到问题才愿意追要究底。不然,即使找到了问题的原因,由于不能修改源代码,只能望问题心叹(比如,我之前遇到的Entity Framework问题)。ASP.NET MVC的开放是它成功的关键,Entity Framework是否成功也将取决于它是否开放。

2)ASP.NET WebForms与ASP.NET MVC会并存吗?

以前我觉得会并存,但现在ASP.NET WebForms的Bug微软自己都懒得去解决,你还相信会并存吗?

posted @ 2011-11-07 14:53  dudu  阅读(12440)  评论(16编辑  收藏  举报