巧用htaccess客户端网页缓存实现网站加速

网站加快访问速度的一个基本原则就是减少HTTP请求数,在我们实现了合并CSS和脚本,使用了图片CSS Sprites后,发现YSlowPageSpeed的评分并没有提高(当然淘宝UED团队给了我们一个新的优化思路,不必拘泥于这两个工具,详细请见这里),仔细研究后才发现原来我们没有很好的利用客户端缓存,仔细分析了淘宝等大网站的HTTP反馈信息如下。

淘宝网

1
2
3
Date: Tue, 22 Feb 2011 14:02:56 GMT
Expires: Tue, 22 Feb 2011 15:02:56 GMT
Cache-Control: max-age=3600

腾讯QQ

1
2
3
Date: Tue, 22 Feb 2011 14:04:26 GMT
Expires: Tue, 22 Feb 2011 14:19:26 GMT
Cache-Control: max-age=900

网易163

1
2
3
Date: Tue, 22 Feb 2011 14:07:35 GMT
Expires: Tue, 22 Feb 2011 14:08:21 GMT
Cache-Control: max-age=80

都可以看到这些网站有个共同点就是使用了Cache-Control控制了客户端的网页缓存,这样,客户端在频繁访问网站时不必每次都向服务器发出请求,大大提高访问的速度和减轻了服务器的负担。

Cache-Control的关键部分就在于max-age,其值代表缓存的时间,单位为秒,对于较快更新的网站这个时间应该短一些,对于更新较慢的网站,这个时间应该长一些,对于个人博客,我认为这个值可以设定为600(10分钟),当然如果时间过长的话,可能评论文章需要刷新或等待指定时间过后才能看见。

对于客户端缓存控制,还有Pragma和ETag值得注意,一般情况下Param: no-cache,代表不缓存文件,当然我们要缓存就必须去掉这项,有关ETag,在查询HTTP反馈后发现是一个标识符,也就是说客户端在收到这个页面的时候会记下这个ETag标识,当再次访问时,客户端将把这个ETag发回给服务器,服务器在收到这个ETag后,会与当前的ETag进行对比,如果一致,代表页面没有改动,直接反馈304 Not Modified的消息,这样浏览器就直接在本地取出缓存页面。实际考察下来发现大多数Web应用均没有实现对ETag的管理控制,所以保留这个信息对于这些应用来说纯属浪费,完全可以去掉,去掉Pragma和ETag头信息可以在.htaccess输入下面两行。

1
2
3
4
5
<FilesMatch ".(html|htm|php|txt)$">
Header unset Pragma
Header unset ETag
FileETag None
</FilesMatch>

其中html|htm|php|txt对应的是要处理的文件类型扩展名。再解决了这两项后,下面可以添加Cache-Control信息了,这里我们设定为10分钟。原来的.htaccess配置如下。

1
2
3
4
5
6
<FilesMatch ".(html|htm|php|txt)$">
Header unset Pragma
Header unset ETag
FileETag None
Header set Cache-Control "max-age=600"
</FilesMatch>

同样的道理,我们知道一般网站上图片、样式表、脚本文件等等都是基本上不改动的,我们可以通过下面的代码将其缓存时间设定长一些。

1
2
3
4
5
6
<FilesMatch ".(gif|jpg|jpeg|png|ico|css|js)$">
Header unset Pragma
Header unset ETag
FileETag None
Header set Cache-Control "max-age=315360000"
</FilesMatch>

怎么样,是不是觉得时间太长了,假如我要改个样式,客户端可能需要刷新才能看见,那怎么办?我们知道,前面对于网页设置了个比较短的时间,也就是说网页在那个时间过后就需要重新到服务器上获取最新版。我们可以在引用的样式表style.css的后面加上style.css?t=20110222这样的时间戳,当然为了照顾一些特殊的浏览器,一般建议这样写style.css?t=20110222.css,当你改动了样式表后,你只需要对网页里的link引用的样式表时间戳进行修改即可。

1
<link rel="stylesheet" href="style.css?t=20110222.css" type="text/css" />

好吧,做完这些,我们可以歇口气喝杯咖啡了?等等,你会发现把配置好的.htaccess文件上传到网站根目录下,然后你登录博客后台时一些后台操作变得不正常了,比如我们删掉一条记录,系统提示删除成功,但是当你返回的时候,发现那条记录仍然存在,真的是这样吗?当你再刷新下该页,才发现那条记录已经没了,聪明的你应该晓得是怎么回事了,对!就是缓存在作怪。

所以我们需要去掉后台管理页面的缓存,假设后台管理路径是/admin/,我们可以针对该文件夹单独配置个.htaccess,内容如下。

1
2
3
4
5
6
<FilesMatch ".(html|htm|php|txt)$">
Header unset ETag
FileETag None
Header set Cache-Control "no-cache, no-store, max-age=0, must-revalidate"
Header set Pragma "no-cache"
</FilesMatch>

好了,就是这么简单,做完这一切,现在访问你的网站看看,是不是感觉不错:-)

另外还有种设置缓存的方法可以参考《How to Set an Expires Header in Apache》这篇文章。

主机域名www的自适应301重定向方法

一般来说Web网站都是喜欢带www的,但是我觉得我的网站带www前缀有些略显不雅,所以我决定把www转向到不带www的域名上来,通常情况下使用301重定向

以前自己用ASP写过个转向的代码片段,供大家参考:

1
2
3
4
5
6
7
8
9
10
11
12
<%
Dim g_ReqServer
g_ReqServer = Request.ServerVariables("SERVER_NAME")
If InStr(1, g_ReqServer, "www.", 1)<>0 Then
    Response.Status="301 Moved Permanently" 
    Response.AddHeader "Location", "http://" &_
        Replace(LCase(g_ReqServer), "www.", "") &_
             Request.ServerVariables("PATH_INFO") & "?" &_
                    Request.QueryString()
    Response.End
End If
%>

这里解释下,首先Request.ServerVariables(“SERVER_NAME”)获取访问所使用的主机域名,然后通过InStr判断是不是包含www.,如果包含则返回301状态。紧接着拼接出新的网址,新的网址必定由http:// + 去掉www.的主机域名(这里调用Replace函数替换) + 所请求文件的路径信息PATH_INFO + ‘?’ + 所请求的字符串,之所以称为“自适应”是因为对于任何的URL,都只是单纯的去掉www.,原路径不变,所以问号和请求字符串就尤显重要了,如果省掉就可能导致查询字符串的丢失。

后来初学PHP,也照样子写过一段代码,如下:

1
2
3
4
5
6
7
8
9
<?php
$server_name = $_SERVER['SERVER_NAME'];
if(!stristr($server_name, 'www.')) {
 $pathinfo = parse_url($_SERVER['REQUEST_URI']);
 header("HTTP/1.1 301 Moved Permanently");
 header("Location: http://www." . $server_name . 
    $_SERVER['PHP_SELF']."?".$pathinfo['query']);
}
?>

基本原理是一样的,只不过要获得?后面的一大串查询字符串需要调用parse_url。

值得注意的是上述两段代码片段必须加在访问的页面代码顶端,也就是在任何Response或者echo输出之前,否则就失效了。

后来通过网上查询获得了个更好的方法,那就是.htaccess,在.htaccess的 里面添加下面的代码:

1
2
3
RewriteEngine on
RewriteCond %{HTTP_HOST} ^www\.wangye\.org$
RewriteRule ^(.*)$   http://wangye.org/$1 [L,R=301]

初学.htaccess,感觉有点正则表达式的味道,首先匹配主机域名,如果是www.wangye.org就进行R=301的重定向。

2011年3月10日更新

Aaron Saray的博客上看到了个比较好的写法,包含了两种情况,分别是有带www的转向不带www的和不带www的转向带www,特别记录下来。

1
2
3
4
5
6
7
8
9
10
11
12
RewriteEngine On
RewriteBase /
 
#require WWW
RewriteCond %{HTTP_HOST} !^www\.(.+)$ [NC]
RewriteRule ^(.*)$ http://www.%1/$1 [R=301,L] 
 
#OR
 
#do not require www
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^(.*)$ http://%1/$1 [R=301,L]

2011年7月3日更新

根据未来往事所反映的关于PHP代码实现跳转,在处理URL重写(URL Rewrite)的情况下会丢失重写,回到原始带查询参数的链接地址,为此我将相关代码修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
// 定义你自己的URL路由表
// 比如下面的路由表将会把
// read.php?paramA=1&paramB=2&paramC=3
// 变成
// /post/1/2/3/
$router = array(
  'read.php'=>'/post/'
);
$server_name = $_SERVER['SERVER_NAME'];
if(!stristr($server_name, 'www.')) {
  $fn = explode('/', strtolower($_SERVER['PHP_SELF']));
  if (array_key_exists($fn[0], $router)) {
    $path = $router[$fn[0]] .
      implode('/',
        array_filter($_REQUEST,
           create_function('$v','return !empty($v);')));
  } else {
    $pathinfo = parse_url($_SERVER['REQUEST_URI']);
    $path = $_SERVER['PHP_SELF']."?".$pathinfo['query'];
  }
  header("HTTP/1.1 301 Moved Permanently");
  header("Location: http://www." . $server_name . $path);
}
?>

总结最后,还是建议大家在条件允许的情况下尝试采用301的DNS转向,目前有些DNS服务商已经免费提供这项功能。

  1. 301重定向 : 301代表永久性转移(Permanently Moved),301重定向是网页更改地址后对搜索引擎友好的最好方法,只要不是暂时搬移的情况,都建议使用301来做转址。[更多]
  2. .htaccess : Apache服务器中的一个配置文件,它负责相关目录下的网页配置。通过.htaccess文件,可以帮我们实现:网页301重定向、自定义404错误页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能。 [更多]