在Web开发中,浏览器缓存是一把双刃剑。一方面,它可以显著提高网站的加载速度,减少服务器的负载;另一方面,当开发者更新了网站的静态资源(如JavaScript、CSS文件)后,用户可能因为缓存而无法及时获取到最新的文件。为了解决这个问题,开发者需要采取一些策略来强制浏览器和代理服务器下载最新的文件。
一种常见的方法是通过改变文件的URL来实现。开发者可以通过添加一个唯一的查询字符串(如版本号)来使浏览器和代理服务器将这些URL视为新文件,从而绕过缓存。例如:
<script src="someJs.js?v=1001"></script>
<link href="someCss.css?v=2001"></link>
这种方法虽然有效,但需要开发者手动更新HTML、ASPX、ASCX、母版页等文件中所有静态文件的引用,并增加版本号。如果遗漏了某些页面,可能会导致浏览器使用旧的缓存脚本,从而破坏页面功能。因此,这需要大量的回归测试来确保更改CSS或JS不会影响整个网站的其他部分。
另一种方法是运行构建脚本,扫描所有文件并更新网站上每个页面中的JavaScript和CSS文件引用。但这种方法不适用于动态页面,因为JavaScript和CSS引用是在运行时添加的,例如使用ASP.NET的ScriptManager。
如果无法预知运行时将添加哪些JavaScript和CSS到页面中,唯一的选择是分析页面输出,并在运行时动态更改JavaScript、CSS引用。这里有一个HttpFilter可以帮助实现这一点。这个过滤器可以拦截任何ASPX页面的请求,并自动将JavaScript和CSS文件的最后修改日期时间附加到生成的HTML中。它直接使用字符缓冲区和响应流进行操作,以确保尽可能快地处理,即使在高负载下也不会增加超过50ms的延迟。
首先,需要在Global.asax文件的Application_BeginRequest事件处理程序中设置名为StaticContentFilter的过滤器:
Response.Filter = new Dropthings.Web.Util.StaticContentFilter(
Response,
relativePath => {
if (Context.Cache[physicalPath] == null) {
var physicalPath = Server.MapPath(relativePath);
var version = "?v=" + new System.IO.FileInfo(physicalPath).LastWriteTime.ToString("yyyyMMddhhmmss");
Context.Cache.Add(physicalPath, version, null, DateTime.Now.AddMinutes(1), TimeSpan.Zero, CacheItemPriority.Normal, null);
Context.Cache[physicalPath] = version;
return version;
} else {
return Context.Cache[physicalPath] as string;
}
},
"http://images.mydomain.com/",
"http://scripts.mydomain.com/",
"http://styles.mydomain.com/",
baseUrl,
applicationPath,
folderPath);
这里的关键部分是委托,每当过滤器检测到脚本或CSS链接时,它会触发委托并要求返回文件的版本。返回的任何内容都会附加在脚本或CSS的原始URL之后。在这个例子中,委托使用文件的最后修改日期时间生成版本号,并缓存该版本,以确保不会在每次页面视图时都进行文件I/O请求以获取文件的最后修改日期时间。
例如,HTML片段中的以下脚本和CSS:
<script type="text/javascript" src="scripts/jquery-1.4.1.min.js"></script>
<script type="text/javascript" src="scripts/TestScript.js"></script>
<link href="Styles/Stylesheet.css" rel="stylesheet" type="text/css" />
将被输出为:
<script type="text/javascript" src="scripts/jquery-1.4.1.min.js?v=20100319021342"></script>
<script type="text/javascript" src="scripts/TestScript.js?v=20110522074353"></script>
<link href="Styles/Stylesheet.css?v=20110522074829" rel="stylesheet" type="text/css" />
如所见,每个文件的最后修改日期时间都生成了一个查询字符串。好处是,不需要在更改文件后生成一个连续的版本号。它将取最后修改日期,只有在文件更改时才会更改。
这里展示的HttpFilter不仅可以附加版本后缀,还可以在图像、CSS和链接URL上添加想要的任何内容。可以利用这个特性从不同的域加载图像,或者从不同的域加载脚本,并从中受益于浏览器的并行加载特性,从而提高页面加载性能。例如,以下标签可以有任何URL附加:
<script src="some.js"></script>
<link href="some.css" />
<img src="some.png" />
它们可以被输出为:
<script src="http://javascripts.mydomain.com/some.js"></script>
<link href="http://styles.mydomain.com/some.css" />
<img src="http://images.mydomain.com/some.png" />
从不同的域加载JavaScript、CSS和图像可以显著提高页面加载时间,因为浏览器一次只能从一个域加载两个文件。如果从不同的子域加载JavaScript、CSS和图像,以及页面本身在www子域上,可以并行加载8个文件,而不是只有2个文件。