在php编程中使用header()函数发送文件头,设置浏览器缓存,加快站点的访问速度

阅读此文,最好先阅读下 HTTP协议 和 HTTP协议中的缓存机制

页面缓存的原理

页面缓存状态是由http header决定的,一个浏览器请求信息,一个是服务器响应信息。主要包括Pragma: no-cache、Cache-Control、 Expires、 Last-Modified、If-Modified-Since。其中Pragma: no-cache由HTTP/1.0规定,Cache-Control由HTTP/1.1规定。

自己画的工作原理图:

 

image

从图中我们可以看到原理主要分三步:

  • 第一次请求:浏览器通过http的header报头,附带Expires,Cache-Control,Last-Modified/Etag向服务器请求,此时服务器记录第一次请求的Last-Modified/Etag
  • 再次请求:当浏览器再次请求的时候,附带Expires,Cache-Control,If-Modified-Since/Etag向服务器请求
  • 服务器根据第一次记录的Last-Modified/Etag和再次请求的If-Modified-Since/Etag做对比,判断是否需要更新,然后响应请求

 

HTTP协议中的时间,都必须是格林威治时间(即GMT)。
GMT 是“Greenwich Mean Time”的缩写,中文叫“格林威治标准时间”,是英国的标准时间,也是世界各地时间的参考标准。中英两国的标准时差为8个小时,即英国的当地时间比中国的北京时间晚8小时。

 

1.Pragma:只能取固定值no-cache,用于http/1.0里面,禁止浏览器缓存

2. Expires:用于指定当前缓存的文档在什么时候被认为过期,GMT格式,超过了这个日期浏览器就不使用本地缓存,而是像服务器发出新的请求。

3.Cache-Control
no-cache:禁止浏览器和代理服务器缓存,可以单独指定不缓存某些信息头,例如:no-cache=Set-Cookie不缓存cookie信息
no-store:专指不能缓存到硬盘
max-age:xx秒以后文档过时,可以代替Expires,如果同时出现,max-age优先
s-max-age:代理服务器中缓存的文档的有效期
must-reva lidate:对于客户机的请求,代理服务器必须向服务器验证缓存的文档是否过时,总是获取最新的文档给客户机

4.if-modified-since
当客户机访问一个已经缓存的页面时,只有该页面自某个指定的时间以来发生过更改,才需要重新获取该文档。
if-modified-since用于指定这个时间,GMT格式。

5.last-modified:服务端设置的文档的最后的更新日期。

6.if-match:定义判断网页过期的条件
服务器给客户机传送网页的时候,可以传递代表实体内容特征的头字段(ETag),这种头字段被叫做实体标签。当客户机再次向服务端发请求的时候,
会使用if-match携带实体标签信息。

7.ETag:用于服务器向客户端传送的代表实体内容特征的标记信息。

 

用户访问到服务器响应的流程

当资源第一次被访问的时候,HTTP头部如下

(Request-Line) GET /a.html HTTP/1.1
Host
127.0.0.1
User-Agent Mozilla/
5.0 (X11; U; Linux i686; zh-CN; rv:1.9.0.15) Gecko/2009102815 Ubuntu/9.04 (jaunty) Firefox/3.0.15
Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language zh-cn,zh;q=0.5
Accept-Encoding gzip,deflate
Accept-Charset gb2312
,utf-8;q=0.7,;q=0.7
Keep-Alive 300
Connection keep-alive

HTTP返回头部如下

(Status-Line) HTTP/1.1 200 OK
Date Thu
, 26 Nov 2009 13:50:54 GMT
Server Apache/
2.2.11 (Unix) PHP/5.2.9
Last-Modified Thu
, 26 Nov 2009 13:50:19 GMT
Etag
"8fb8b-14-4794674acdcc0"
Accept-Ranges bytes
Content-Length
20
Keep-Alive timeout
=5, max=100
Connection Keep-Alive
Content-Type text/html

当资源第一次被访问的时候,http返回200的状态码,并在头部携带上当前资源的一些描述信息,如

Last-Modified // 指示最后修改的时间
Etag // 指示资源的状态唯一标识
Expires // 指示资源在浏览器缓存中的过期时间

接着浏览器会将文件缓存到Cache目录下,并同时保存文件的上述信息

当第二次请求该文件时,浏览器会先检查Cache目录下是否含有该文件,如果有,并且还没到Expires设置的时间,即文件还没有过期,那么此时浏览器将直接从Cache目录中读取文件,而不再发送请求

如果文件此时已经过期,则浏览器会发送一次HTTP请求到WebServer,并在头部携带上当前文件的如下信息

If-Modified-Since Thu, 26 Nov 2009 13:50:19 GMT
If-None-Match
"8fb8b-14-4794674acdcc0"

即把上一次修改的时间,以及上一次请求返回的Etag值一起发送给服务器。服务器在接收到这个请求的时候,先解析Header里头的信息,然后校验该头部信息。

如果该文件从上次时间到现在都没有过修改或者Etag信息没有变化,则服务端将直接返回一个304的状态,而不再返回文件资源,状态头部如下

(Status-Line) HTTP/1.1 304 Not Modified
Date Thu
, 26 Nov 2009 14:09:07 GMT
Server Apache/
2.2.11 (Unix) PHP/5.2.9
Connection Keep-Alive
Keep-Alive timeout
=5, max=100
Etag
"8fb8b-14-4794674acdcc0"

这样,就能够很大程度上减少网络带宽以及提升用户的浏览器体验。

当然,如果服务器经过匹配发现文件修改过了,就会将文件资源返回,并带上新文件状态信息。

 

上面描述的是一个普通的浏览器缓存状态,在实际应用中,如页面跳转(点击页面链接跳转,window.open,在地址栏敲回车,刷新页面)等操作,会有一些区别

普通页面跳转

普通页面跳转包括链接点击跳转,用js脚本打开新页面(window.open)

无缓存情况下,请求会返回所有资源结果

设置Expires并且未过期时,浏览器将不会发出http请求

如果Expires过期,则会发送相应请求,并附带上Last-Modifed等信息,供服务器校验

页面刷新(F5)

这种情况一下,一般会看到很多304的请求,就是说即便资源设置了Expires且未过期,浏览器也会发送相应请求

IE和FF稍有区别

IE:

If-Modified-Since Wed, 18 Nov 2009 15:54:52 GMT
If-None-Match
"2360492659"
Pragma: no-cache // 禁止缓存

FF:

If-Modified-Since Wed, 18 Nov 2009 15:54:52 GMT
If-None-Match
"2360492659"
Cache-Control max-age
=0 // 文件立即过期

强制刷新(Ctrl+F5)

效果和无缓存时候一致,返回200的结果

 

使用php语言中的header()函数设置浏览器缓存

PHP根据HTTP协议将HTML文档的标头送到浏览器,告诉浏览器具体怎么处理这个页面。在php语言中,header()这个函数很有用的,尤其在用到ajax时候。

HTTP的日期时间必须是格林威治时间(GMT),而不是本地时间

<?php 
 
//定义一个合理缓存时间。合理值屈居于页面本身、访问者的数量和页面的更新频率,此处为3600秒(1小时)。 
$time = 60 * 60;
  
//发送Last-Modified头标,设置文档的最后的更新日期。 
header ("Last-Modified: " .gmdate("D, d M Y H:i:s", time() )." GMT"); 
 
//发送Expires头标,设置当前缓存的文档过期时间,GMT格式。 
header ("Expires: " .gmdate("D, d M Y H:i:s", time()+$time )." GMT"); 
 
//发送Cache_Control头标,设置xx秒以后文档过时,可以代替Expires,如果同时出现,max-age优先。 
header ("Cache-Control: max-age=$time"); 
  
?>

 

RFC822格式的日期时间格式: 英文星期缩写,日期 英文月份缩写 年份 小时:分钟:秒钟 时区。

<?php
date_default_timezone_set('PRC');//设置时区为中华人民共和国,东八区
echo gmdate ('r'); //输出:格林威治标准时(GMT),RFC822格式的日期时间,跟php系统设置的时区无关。样式如:Sun, 20 Oct 2013 10:17:29 +0000
echo date('r'); //输出:RFC822格式的日期时间,由php系统设置的时区决定。此处php设置东八区时间,样式如:Sun, 20 Oct 2013 18:17:29 +0800
?>

 

要使用者每次都能得到最新的资料,而不是 Proxy 或 cache 中的资料,可以使用下列的标头

<php 
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); 
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . "GMT"); 
header("Cache-Control: no-cache, must-reva lidate"); 
header("Pragma: no-cache"); 
?>

 

测试缓存的案例

当我们使用抓包工具查看http状态时,我们可以看到200,304,from cache之类的标识,也许你也早已注意到了他们,并明白他们的意思。但是,你是否想过在你的程序中加入代码主动告诉浏览器,更好地利用浏览器缓存。 
备注:本章所有案例都是使用chrome 17.0.963.79 m浏览器,测试方式为每次打开浏览器新窗口的方式测试,而不是F5刷新。 
我们先做一个没有缓存的页面,叫做nocache.php
代码如下: 

 <?php 
echo time(); 
?>

用浏览器打开,我们可以看到每次打开页面,都是打印新的时间,返回的状态码是200,表明在默认情况下,我们的浏览器没有使用缓存。
我们再做一个页面,叫做last_modified.php
代码如下: 

<?php 
$cache_time = 3600; 
$modified_time = @$_SERVER['HTTP_IF_MODIFIED_SINCE']; 
if( strtotime($modified_time)+$cache_time > time() ){ 
    header("HTTP/1.1 304"); 
    exit; 
} 
header("Last-Modified: ".gmdate("D, d M Y H:i:s", time() )." GMT");  
echo time(); 
?>

用浏览器打开,我们可以看到第一次打开,返回的状态码为200,打印时间为最新时间。然后我们第二次打开,可以看到状态码为304,时间和刚才的时间一样,表明我们是使用到缓存了。我们删除last_modified.php文件,然后第三次打开页面,浏览器返回404错误,可见Last-Modified虽然使用了缓存,但是每次打开页面依然需要向服务器发起http请求,浏览器根据用户的$_SERVER['HTTP_IF_MODIFIED_SINCE']来判断浏览器的内容是否过期,没过期的话返回304状态,浏览器内容从缓存中读取。
我们再做一个页面,叫做 expires.php
代码如下: 

<?php 
$cache_time = 3600; 
header("Expires: ".gmdate("D, d M Y H:i:s", time()+$cache_time )." GMT");    
echo time(); 
?>

用浏览器打开,我们可以看到第一打开,返回的状态为200,时间为最新的时间。然后我们第二次打开,可以看到状态码依然是200,时间依然是旧的时间,Size栏目显示为from cache,表示内容是直接从浏览器读取。我们删除expires.php文件,然后第三次在新窗口中打开,可以看到返回200状态码,打印时间依然是旧的,Size依然提示为from cache,由此可见,设置了Expires,就算删除页面,浏览器端依然可以显示,表明浏览器根本就没有向服务器发起http请求。 
      到这里,也许你会感觉Expires比Last-Modified缓存效果更好是吧,因为本地有缓存数据时,不需要向服务器发起http请求,服务器的并发数会明显的减少,可以少处理很多http请求。但是Expires也有缺点,那就是设置的过期时间是服务器的时间,而不是你本地的时间,这样如果服务器时间跟你本地时间不一致时,可能并没有起到缓存的效果。HTTP/1.1为了弥补Expirse的不足,引入了Cache-Control标记。格式如下Cache-Control: max-age=<second>,这个时间是相对浏览器本地时间,所以更加准确。
我们再做一个页面,叫做cached_control.php
代码如下: 

 <?php 
$cache_time = 3600; 
header("Cache-Control: max-age=".$cache_time); 
echo time(); 
?>

我们测试可以得到设置Expires一样的效果,也就是缓存后,删除文件,依然在浏览器缓存有效期内可以正常访问页面。 
      好了,over了,下次当你服务器并发太高,服务器资源和带宽资源不足时,请记住伟大的浏览器缓存吧!也许你依然不以为然,觉得提高服务器的吞吐量有很多办法,比如在服务器端做缓存,把页面静态化,等等。但是我相信有一个东西你应该会在意,没错,就是带宽,在意吧?如果能利用好浏览器缓存,将可以降低你的宽带资源,这样不是很好吗。