JavaScript/CSS 部署 – 从石器时代到工业时代

随着前端开发在 Web 开发中的地位日渐重要,前端相关的资源部署也成了各大网站开始重视的一步。

对于一个访问量相当大的网站来说,从前端方面考虑,除了使用 CDN 向客户端分发 JavaScript/CSS 资源,充分利用客户端浏览器缓存也是减轻服务器压力的一个重要途径。

首先按照我们的“常识”,认为 JavaScript/CSS 默认就是在浏览器中缓存“很长”一段时间的,作为最简单的策略,我们就依赖这种缓存机制来降低服务器的压力。然而,对于一个不断改进、更新的网站来说,JavaScript/CSS 的变动是相当频繁的。

如何让浏览器知道这些静态资源的改变呢?让我们从最简单的解决方案开始摸索和学习,慢慢改进。

刀耕火种的方式

把这件事扔给开发者吧。以 JSP 页面为例,首先在一个统一的 jsp 文件(例如 version.jsp)中定义所有静态资源的版本号:

[code lang=”html”]
<%
String dialogVersion = “20090101”;

%>
[/code]

然后在每个用到 JavaScript/CSS 的页面中包含该文件并且给静态资源加上版本号:

[code lang=”html”]
<%@ include file="/WEB-INF/include/common.jspf" %>



[/code]

开发者负责在每次 commit 代码前检查自己修改过的静态资源,去修改相应的版本号。这给本来就不堪重负的开发者又增加了一项艰巨任务,他们难免会有疏忽的时候,等到有客户反馈问题,才想起来——哦,忘记改版本号了!

自动化

一个成熟的开发团队应该使用版本控制工具来管理代码,一般最常用的是 Subversion. 除此之外,还应该有一套完善的部署脚本,执行一两个命令即可完成整个部署过程,而不是手动地去复制、修改代码。这个过程可能包含从 svn 检出代码、编译(解释型语言则省去了这一步)、选择性的复制(避免覆盖生产环境中的特定配置)等等。

有了这些基础,把上面手工修改版本号的工作交给自动脚本去做就可以了。”svn info” 命令可以列出文件最后被修改的版本号,我们只要得到这个版本号,把它写在 version.jsp 中即可。写这个脚本可能稍微麻烦一点,不过 python, perl, bash, ruby, 随便什么语言都可以,或者是一个 ant task. 这是一劳永逸的事情。开发者们可以把精力集中在真正的前端开发商了。

不要用 query string!

我本来以为到此为止就很完美了,但查阅了一些资料之后发现我的许多观点都是完全错误的。

首先,在上述的方法中,版本号是作为 query string 附在静态资源的 URL 上的,然而:

According the letter of the HTTP caching specification, user agents should never cache URLs with query strings. While Internet Explorer and Firefox ignore this, Opera and Safari don’t – to make sure all user agents can cache your resources, we need to keep query strings out of their URLs.

以上摘自文章 “Serving JavaScript Fast

正确的办法是将版本号插在文件名之中,后缀之前,然后利用 Web Server 的 URL Rewrite 功能指向不带版本号的真实文件,实际上就是欺骗一下客户端。具体做法请参考上文,或者 “Automatically Version Your CSS and JavaScript Files“.

仅仅 304 还不行!

假设浏览器的缓存中什么都没有,这时用户去访问一个网站,引用到的 JavaScript/CSS 都要到服务器上去下载。服务器将这些静态资源返回给客户端的同时,会附加一个 Last-Modified 时间值。下次客户端再请求该资源,会把这个时间值附加在 If-Modified-Since 这个 header 字段中传给服务器,意思就是如果在这个时间之后修改过才有必要重新传输。如果没有修改,响应的状态码就是 304.

下面用 fiddler 抓取 IE 浏览器请求我的 blog 首页的过程,做个测试。第一张图,蓝色虚线上面是缓存为空时访问页面的请求,虚线下面是重启浏览器之后再次访问的请求情况。为了说明问题,我只保留了与问题密切相关的一些请求,可以点击查看大图。

ie-fiddler

请求 #0, #28 都是请求页面本身,两次都是全部传输,状态码是200. 请求 #1, #2 分别和 #29, #30 对应,一个是 JavaScript,一个是 CSS. 从 body 那一列可以看出消息体长度都是 0,而状态码是 304. 再看请求 #3,在第二次访问时,浏览器根本没有再次请求该资源,关键就在于 Caching 那一列的属性。

尽管减少了数据传输,请求数却是影响服务器承载能力的最重要因素。理想的情况自然是像 #3 请求的 widget02.css 那样,告诉浏览器在一年以后该资源才会过期。

如果使用的 Web server 是 Apache httpd,那么可以利用它的 mod_expires 来实现 JavaScript/CSS 自动添加 Expires 头信息。具体方法在前面提到的文章里都有。

最后再次列一下参考的宝贵资源:

  1. Automatically Version Your CSS and JavaScript Files
  2. Serving JavaScript Fast
  3. Best Practices for Speeding Up Your Web Site
  4. What Is Caching and How Does It Apply to the Web?

事实上本人的实际经验还停留在刀耕火种的程度,只是把最近学到和想到的东西分享一下而已……在未来的项目中我肯定会实践这些想法,并在可能的情况下把实际的代码分享出来。文中如有错误,欢迎指正。

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

Time limit is exhausted. Please reload CAPTCHA.