一、什么是”Last-Modified”?
在浏览器第一次请求某一个URL时,服务器端的返回状态会是200,内容是你请求的资源,同时有一个Last-Modified的属性标记此文件在服务期端最后被修改的时间,格式类似这样:
Last-Modified: Fri, 12 May 2006 18:53:33 GMT
客户端第二次请求此URL时,根据 HTTP 协议的规定,浏览器会向服务器传送 If-Modified-Since 报头,询问该时间之后文件是否有被修改过:
If-Modified-Since: Fri, 12 May 2006 18:53:33 GMT
如果服务器端的资源没有变化,则自动返回 HTTP 304 (Not Changed.)状态码,内容为空,这样就节省了传输数据量。当服务器端代码发生改变或者重启服务器时,则重新发出资源,返回和第一次请求时类似。从而保证不向客户端重复发出资源,也保证当服务器有变化时,客户端能够得到最新的资源。
注意:
1、对于一些图像,css,js等静态文件资源,配置好了的apache服务器会理解这些If-Modified-Since请求头标,将头标里的时间和文件的最后修改时间进行比较并作出响应,如果二者相等则发送一个304 Not Modfied来告诉客户端所请求资源并未修改让客户端放心使用缓存中的资源,否则的话会重新发送一个新的资源和新的Last-Modified的头标。
2、但是对于一个动态的PHP脚本,我们即使在脚本加入了header('Last Modified: '.$time)来发送一个Last Modified响应头标,当客户端附带'If-Modified-Since'在次请求时apache服务器不会进行处理,这需要我们自己用$_SERVER['HTTP_IF_MODIFIED_SINCE']来获取'If-Modified-Since'的值自己来进行判断处理。
<?php
//设置 Last-Modified $mktime为所设定的时间
header( 'Last-Modified: ' . gmdate ( 'r' , $mktime ) . ' GMT' );
//再次请求时获取Last-Modified值
$again = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
echo "".$again;
?>
二、什么是”Etag”?
HTTP 协议规格说明定义ETag为“被请求变量的实体值” 。另一种说法是,ETag是一个可以与Web资源关联的记号(token)。典型的Web资源可以一个Web页,但也可能是JSON或XML文档。服务器单独负责判断记号是什么及其含义,并在HTTP响应头中将其传送到客户端,以下是服务器端返回的格式:
ETag: "50b1c1d4f775c61:df3"
客户端的查询更新格式是这样的:
If-None-Match: W/"50b1c1d4f775c61:df3"
如果ETag没改变,则返回状态304然后不返回,这也和Last-Modified一样。本人测试Etag主要在断点下载时比较有用。
<?php
//设置 ETag $etab为所设定的标志
$etab = "yuanyanbing";
header( 'ETag: '.$etab );
//再次请求时获取ETag:值
$againb = $_SERVER['HTTP_IF_NONE_MATCH'];
echo "".$againb;
?>
Last-Modified和Etags如何帮助提高性能?
聪明的开发者会把Last-Modified 和ETags请求的http报头一起使用,这样可利用客户端(例如浏览器)的缓存。因为服务器首先产生 Last-Modified/Etag标记,服务器可在稍后使用它来判断页面是否已经被修改。本质上,客户端通过将该记号传回服务器要求服务器验证其(客户端)缓存。
过程如下:
1. 客户端请求一个页面(A)。
2. 服务器返回页面A,并在给A加上一个Last-Modified/ETag。
3. 客户端展现该页面,并将页面连同Last-Modified/ETag一起缓存。
4. 客户再次请求页面A,并将上次请求时服务器返回的Last-Modified/ETag一起传递给服务器。
5. 服务器检查该Last-Modified或ETag,并判断出该页面自上次客户端请求之后还未被修改,直接返回响应304和一个空的响应体。
三、Expires
Expires表明缓存何时因该过期(GMT时间),属于HTTP 1.0 标准,通常是用来对Cache-Control的max-age的一个补充,来兼容HTTP 1.0,不赞成单独使用Expires,因为客户端时间容易发生偏差。
四、Pragma
HTTP 1.0 标准,通常是在不缓存时使用,Pragma: no-cache。
五、关于Cache-Control
public | 可以在任何地方缓存 |
private | 内容只缓存到私有缓存中 |
no-cache | 不能在任何地方缓存 |
must-revalidate | 缓存必须检查跟新版本 |
proxy-revalidate | 代理缓存必须检查跟新版本 |
max-age | 内容能够被缓存的时间 |
s-maxage | 覆盖共享缓存的max-age设置 |
Cache-directive | 打开一个新的浏览器窗口 | 在原窗口中单击 Enter 按钮 | 刷新 | 单击 Back 按钮 |
---|---|---|---|---|
public | 浏览器呈现来自缓存的页面 | 浏览器呈现来自缓存的页面 | 浏览器重新发送请求到服务器 | 浏览器呈现来自缓存的页面 |
private | 浏览器重新发送请求到服务器 | 第一次,浏览器重新发送请求到服务器;此后,浏览器呈现来自缓存的页面 | 浏览器重新发送请求到服务器 | 浏览器呈现来自缓存的页面 |
no-cache/no-store | 浏览器重新发送请求到服务器 | 浏览器重新发送请求到服务器 | 浏览器重新发送请求到服务器 | 浏览器重新发送请求到服务器 |
must-revalidation/proxy-revalidation | 浏览器重新发送请求到服务器 | 第一次,浏览器重新发送请求到服务器;此后,浏览器呈现来自缓存的页面 | 浏览器重新发送请求到服务器 | 浏览器呈现来自缓存的页面 |
max-age=xxx (xxx is numeric) | 在 xxx 秒后,浏览器重新发送请求到服务器 | 在 xxx 秒后,浏览器重新发送请求到服务器 | 浏览器重新发送请求到服务器 | 在 xxx 秒后,浏览器重新发送请求到服务器 |
浏览器行为影响
在先前有效访问后,在以后对同一URI资源的请求中,浏览器只进行两种动作:
(1)直接在缓存中去获取内容。如果先前有效访问的响应头包含 Expires,max-age的话,'打开新窗口' '输入URI回车' '前一页' '后一页'这些浏览器行为不会使浏览器在Expires,max-age设置的有效期时间内去访问服务器,而是在缓存中去获取内容,但是' 刷新' 或 '重载'例外。
(2)访问服务器,根据服务器响应来获取内容。这种情况发生在设置no-cache等头标要求不缓存,或者是设置了 Expires,max-age但浏览器行为是 ' 刷新' 或 '重载'时候。'Last-Modified' 'ETag' 'must-revalidate' 等有些特殊,不直接受浏览器行为影响,它们也是访问服务器后,再由服务器判断是发送新的资源,还是发送一个304 Not Modfied让浏览器使用缓存中的资源。
<?php
/********************************
* 客户端缓存控制函数
* $type 缓存类型
* $interval 客户端缓存过期时间
* $mktime 设置Last-Modified
* $etag 设置ETag标志
******************************/
function http_cache_control( $type = 'nocache' , $interval =0, $mktime = '' , $etag = '' ){
if ( $type == 'nocache' )
{
header('Expires: -1' ); //设置 -1为立刻过期
header('Pragma: no-cache' );
header('Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0' );
}
else
{ //检查 ETag: 值 $_SERVER [ 'HTTP_IF_NONE_MATCH' ]
if (isset( $_SERVER [ 'HTTP_IF_NONE_MATCH' ]) && $etag && $_SERVER [ 'HTTP_IF_NONE_MATCH' ] == $etag )
{
header('HTTP/1.1 304 Not Modfied' );
}//检查 Last-Modified: 值 $_SERVER [ 'HTTP_IF_MODIFIED_SINCE' ]
elseif (isset( $_SERVER [ 'HTTP_IF_MODIFIED_SINCE' ]) && $mktime && $_SERVER [ 'HTTP_IF_MODIFIED_SINCE' ] == gmdate ( 'r' , $mktime ). ' GMT' )
{
header('HTTP/1.1 304 Not Modfied' );
}
else
{ //根据修改时间加过期时间,算出过期时间点
if ( $mktime )
{
$gmtime = gmdate ( 'r' , $mktime + $interval ). ' GMT' ;
header('Expires: ' . $gmtime );
}
if ( $type == 'public' )//设置缓存类型为public
{
header('Cache-Control: public,max-age=' . $interval );
}
elseif ( $type == 'private' )//设置缓存类型为 private
{
header('Cache-Control: private,max-age=' . $interval . ',s-maxage=0' );
}elseif ( $type == 'none' )
{
header('Cache-Control: must-revalidate,proxy-revalidate' );
}
}
$mktime && header( 'Last-Modified: ' . gmdate ( 'r' , $mktime ) . ' GMT' );
$etag && header( 'ETag: ' . $etag );
}
}
?>