我們往往在服務器上對緩存設置進行各種優化方案,但是我們卻很少注意到客戶端緩存,准確的說是浏覽器的緩存機制。
其實每種浏覽器都有緩存策略,會暫時將每一個浏覽過的文件緩存在一個特殊的文件夾裡。我們就可以在用戶重復提交頁面請求的時候,告訴用戶這個頁 面沒有改變,可以調用緩存。 那我們怎麼知道用戶有沒有這個頁面的緩存數據呢? 其實浏覽器在發送請求的時候會先發送http頭,一般象這樣:
Date: Sun, 30 Jul 2006 09:18:11 GMT
Content-Type: image/gif
Last-Modified: Wed, 19 Jul 2006 07:40:06 GMT
ETag: "8c55da8d6abc61:2327"
Content-Length: 14757
其中
Last-Modified: Wed, 19 Jul 2006 07:40:06 GMT
ETag: "8c55da8d6abc61:2327"
就是有關頁面的緩存信息的。然後如果服務器返回的響應代碼不是HTTP 200 (OK),而是 304的話,浏覽器就會從緩存中讀取數據。
//告訴客戶端浏覽器不使用緩存,HTTP 1.1 協議
header("Cache-Control: no-cache, must-revalidate");
//告訴客戶端浏覽器不使用緩存,兼容HTTP 1.0 協議
header("Pragma: no-cache");
根據這個原理,可以用在不經常更新或者需要經常刷新的頁面,可以大大減輕服務器的負擔,因為它如果發現客戶端有緩存,就向客戶端發送一個304響應,然後停止程序的執行。
浏覽器發出的請求中包含If-Modified-Since和If-None-Match 兩個參數,第一個表示詢問數據的最後修改時間是否是Thu,19 Jun 2008 16:24:01 GMT 然後服務器就會檢查數據的最後修改時間,如果是該時間則返回狀態碼304(表示沒有修改),此時當浏覽器收到狀態碼是304時就不會下載數據而是從本地緩 存中調用。然而只有本地緩存中存在著該請求資源的數據時浏覽器才會發送If-Modified-Since參數並且其值為上一次服務器所返回的Last- Modified的值(並不是所有的服務器都支持If-Modified-Since和If-None-Match );If-None-Match的功能也類似,它是由服務器返回的Etag的值生成的,可以是任意值,因為其作用僅僅是使服務器檢查數據的修改時間然後返 回而已,只要不為none(默認值)或不為空其它的都可以。
所以我們可以在代碼的最前部分設置返回給浏覽的Etag為某個值,然後在這個資源被第二次請求的時候就會附帶著一個If-None-Match 參 數,通過核實其值確實為所發出的Etag值時就可以指定服務器返回為304然後強行退出程序就行了,If-Modified-Since也是一樣的做法這 裡就只給出etag方法的php版(Last-Modified版的太常見了如設置緩存超時等等):
PHP 代碼復制到剪貼板
復制代碼 代碼如下:
if ($_SERVER["HTTP_IF_NONE_MATCH"] == "claymorephp.com")
{
header('Etag:'.'zhaiyun.com',true,304);
exit();
}
else {
header('Etag:'."claymorephp.com");
}
你還可以稍微改一下:
$expires=date("Ymd"); //一天後緩存過期
if ($_SERVER["HTTP_IF_NONE_MATCH"] == $expires)
{
header('Etag:'.$expires,true,304);
exit();
}
else {
header('Etag:'.$expires);
}
if ($_SERVER["HTTP_IF_NONE_MATCH"] == "claymorephp.com") { header('Etag:'.'zhaiyun.com',true,304); exit(); } else { header('Etag:'."claymorephp.com"); } 你還可以稍微改一下: $expires=date("Ymd"); //一天後緩存過期 if ($_SERVER["HTTP_IF_NONE_MATCH"] == $expires) { header('Etag:'.$expires,true,304); exit(); } else { header('Etag:'.$expires); }
另外,當GZIP和ETAG同時使用時有時會出問題,就是ETAG沒有值,這個問題是普遍存在的,我暫時沒有找到相關的原因,網上搜了一會,普遍的人稱之為BUG。
基於以上原因,關於PHPBLOG的客戶端緩存是以下來處理的(同時對HTTP_IF_NONE_MATCH和HTTP_IF_MODIFIED_SINCE進行判斷):
PHP 代碼復制到剪貼板
復制代碼 代碼如下:
if($_SERVER['HTTP_IF_NONE_MATCH'])
{
if($_SERVER['HTTP_IF_NONE_MATCH'] == 'phpblog')
{
header('Etag:phpblog',true,304);//控制浏覽器緩存
$_SESSION['time_end']=microtime(true);
exit();
}
}
else if($_SERVER['HTTP_IF_MODIFIED_SINCE'])//eg:Sun, 02 Nov 2008 07:08:25 GMT; length=35849
{
$array=explode(' ',$_SERVER['HTTP_IF_MODIFIED_SINCE']);
$gmday=$array[1];
$month_array=array(
"Jan"=>"01",
"Feb"=>"02",
"Mar"=>"03",
"Apr"=>"04",
"May"=>"05",
"Jun"=>"06",
"Jul"=>"07",
"Aug"=>"08",
"Sep"=>"09",
"Oct"=>"10",
"Nov"=>"11",
"Dec"=>"12");
$gmmonth=$month_array[$array[2]];
$gmyear=$array[3];
$array=explode(':',$array[4]);
$gmtimestamp=gmmktime($array[0],$array[1],$array[2],$gmmonth,$gmday,$gmyear);
if(gmmktime()-$gmtimestamp<$config_client_cache_time*60*60)
{
header('Etag:phpblog',true,304);//控制浏覽器緩存
$_SESSION['time_end']=microtime(true);
exit();
}
}
if($_SERVER['HTTP_IF_NONE_MATCH']) { if($_SERVER['HTTP_IF_NONE_MATCH'] == 'phpblog') { header('Etag:phpblog',true,304);//控制浏覽器緩存 $_SESSION['time_end']=microtime(true); exit(); } } else if($_SERVER['HTTP_IF_MODIFIED_SINCE'])//eg:Sun, 02 Nov 2008 07:08:25 GMT; length=35849 { $array=explode(' ',$_SERVER['HTTP_IF_MODIFIED_SINCE']); $gmday=$array[1]; $month_array=array( "Jan"=>"01", "Feb"=>"02", "Mar"=>"03", "Apr"=>"04", "May"=>"05", "Jun"=>"06", "Jul"=>"07", "Aug"=>"08", "Sep"=>"09", "Oct"=>"10", "Nov"=>"11", "Dec"=>"12"); $gmmonth=$month_array[$array[2]]; $gmyear=$array[3]; $array=explode(':',$array[4]); $gmtimestamp=gmmktime($array[0],$array[1],$array[2],$gmmonth,$gmday,$gmyear); if(gmmktime()-$gmtimestamp<$config_client_cache_time*60*60) { header('Etag:phpblog',true,304);//控制浏覽器緩存 $_SESSION['time_end']=microtime(true); exit(); } }
緩存的HEADER是這樣來發送的:
PHP 代碼復制到剪貼板
復制代碼 代碼如下:
$client_cache_time=$config_client_cache_time*60*60;//單位 - 秒
header('Cache-Control: public, max-age='.$client_cache_time);
header('Expires: '.gmdate('D, d M Y H:i:s',time()+$client_cache_time).' GMT');//設置頁面緩存時間
header('Last-Modified: '.gmdate('D, d M Y H:i:s',time()).' GMT');//返回最後修改時間
header('Pragma: public');
header('Etag:phpblog');//返回標識,用於標識上次的確訪問過(浏覽器中存在緩存)
$client_cache_time=$config_client_cache_time*60*60;//單位 - 秒 header('Cache-Control: public, max-age='.$client_cache_time); header('Expires: '.gmdate('D, d M Y H:i:s',time()+$client_cache_time).' GMT');//設置頁面緩存時間 header('Last-Modified: '.gmdate('D, d M Y H:i:s',time()).' GMT');//返回最後修改時間 header('Pragma: public'); header('Etag:phpblog');//返回標識,用於標識上次的確訪問過(浏覽器中存在緩存)