使用 CRC32 进行 JavaScript 和 CSS 缓存
一种确保新 .JS 和 .CSS 文件始终被下载,而缓存文件永不被重新下载的简单方法
引言
每次我做一个 Web 项目时,我总是在处理 Web 内容缓存方面“重复造轮子”。有时,我尝试通过版本号来做,比如添加一个?v=01.04.23,但然后一次发布会失败,因为有人忘了更新它。或者我尝试通过日期来做?v=20120101,认为每天下载一次还可以接受,并且它能确保客户看到任何更改(如果部署是在晚上进行的话),但那样开发起来就很痛苦,并且你的服务器在每天早上仍然承受着巨大的压力。
有没有办法用 8 个字节唯一地标识任何文件,这样只有当文件实际更改时我们才需要重新下载它?嗯,PKZIP 几十年来一直在这样做,通过计算 .zip 存档中每个文件的 CRC32 字符串。我发现 PKZIP 及其变体可以非常快速地压缩大量文件,这意味着计算 CRC32 一定非常快。
背景
如果您不熟悉使用虚假查询字符串来为 Web 内容添加版本号,请在此处查看快速介绍。
如果您不熟悉 CRC32,您可以在此处了解它。
Using the Code
由于 CRC32 是对给定文件的简短哈希,因此添加或删除单个字节都会更改 CRC 哈希。这意味着此方法在文件更改时下载,在文件相同且已缓存时不下载,这是 100% 可靠的。虽然这可能不是有史以来最快的方法(尽管它确实非常快),但它是万无一失的,因为没有人可以因为忘记版本号而绕过它。使用此方法的文件将保持缓存状态,即使多年过去,直到文件更改,届时所有用户将立即获得新文件。
那么我们如何使用这种神奇的新方法呢?只需在您的 `Url.Content` 调用中添加一个`, true`。 (您*确实*在使用 `Url.Content` 将您的*.js* 和 *.css* 文件添加到您的 MVC 项目中,对吧?)
所以,`<%=Url.Content("~/Scripts/MyScript.js")%>` 变成 `<%=Url.Content("~/Scripts/MyScript.js", true)%>`,而在 Razor 中,`@Url.Content("~/Scripts/MyScript.js")` 变成 `@Url.Content("~/Scripts/MyScript.js", true)`。就是这样!
现在,您的文件将作为http://www.example.com/Scripts/MyScript.js?crc=2D1BA10F 而不是http://www.example.com/Scripts/MyScript.js 被下载。
那么,首先,让我们看看相关的代码
using System.IO;
using System.Web;
namespace WebCRC
{
public static class CRC
{
public static string Content(this System.Web.Mvc.UrlHelper Url, string Path, bool CRC)
{
if (!CRC)
return Url.Content(Path);
string serverPath = HttpContext.Current.Server.MapPath(Path);
byte[] fileContents = File.ReadAllBytes(serverPath);
string result = CRC32.Compute(fileContents).ToString("X");
return Url.Content(Path) + "?crc=" + result;
}
}
}
- 首先,您可以看到我们正在重载 `Url.Content`,因为有了 `this System.Web.Mvc.UrlHelper Url`。这给了我们一个扩展方法,一个就像是随类一起出现的类的方法。
- 接下来,我们使用 `HttpContext.Current.Server.MapPath(Path)` 获取文件在服务器上的位置。
- 然后我们使用 `File.ReadAllBytes(serverPath)` 将文件的一个副本读入内存。JavaScript 和 CSS 文件通常非常小,因此这里不需要缓冲。
- 最后,我们计算 CRC32 校验和 `CRC32.Compute(fileContents)` 并将其转换为十六进制字符串 `.ToString("X")`。
- 最后但同样重要的是,我们将 `?crc={checksum}` 添加到文件名中。
那么使用此功能有哪些步骤呢?再简单不过了。
- 在您项目的“引用”部分引用 `WebCRC.dll` 文件,方法是选择“添加引用...”
- 在您想要添加 CRC 引用的网页(*.aspx* 或 *.cshtml* 文件)中,为 ASPX 文件添加 `<%@Import Namespace="WebCRC" %>`,或者为 Razor 添加 `@using WebCRC`。
- 在您的 `Url.Content` 调用中添加 `, true`。
就是这么简单!
这难道不会因为每次生成 HTML 页面时都计算这些校验和而“杀死”您的服务器吗?
不,其实不会。您看,由于您的 Web 服务器一直在提供这些文件,它们已经缓存在内存中了。CRC 算法已经存在很长时间了,在现代计算机上速度非常快。在我的桌面机器上,此过程(每个文件引用)仅需 2ms(在我的桌面机器上),在不错的服务器上可能只需 1ms。而且,由于大多数页面渲染至少需要 1.5 秒(1500 毫秒),我们可以花 10-20 毫秒的时间来确保我们不必花费另外几百毫秒来频繁下载东西,或者支持电话告诉用户清除缓存,因为我们忘记了什么导致页面无法工作。
您不担心 CRC 冲突吗?即新文件生成了与旧文件相同的哈希?
不,因为一个会生成与 JavaScript 文件相同哈希的文件根本不会像 JavaScript(它看起来会像二进制垃圾)。而且它*肯定*不会像在其中添加了一个新函数的同一个文件。
这仅限于 JavaScript 和 CSS 文件吗?
不。您可以将其用于任何您喜欢的类型的文件,例如图像。但如果您在处理大文件时使用它,我建议您进行测试,因为如果文件更大,它可能会变得很重。
这能与精简文件和 gzip 一起使用吗?
当然。只要使用相同的精简器,并且在每次部署时都能给出相同的精简结果,CRC 就会每次计算出相同的校验和。如果文件发生更改,精简后的版本也必须不同,因此它会获得新的 CRC。
Gzip 由服务器在页面渲染*之后*处理,因此它对此过程没有影响。
历史
- 2012 年 7 月 16 日:首次上传