深入PHP与浏览器缓存的分析


Posted in PHP onJune 03, 2013

我们往往在服务器上对缓存设置进行各种优化方案,但是我们却很少注意到客户端缓存,准确的说是浏览器的缓存机制。
其实每种浏览器都有缓存策略,会暂时将每一个浏览过的文件缓存在一个特殊的文件夹里。我们就可以在用户重复提交页面请求的时候,告诉用户这个页 面没有改变,可以调用缓存。 那我们怎么知道用户有没有这个页面的缓存数据呢? 其实浏览器在发送请求的时候会先发送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');//返回标识,用于标识上次的确访问过(浏览器中存在缓存)

PHP 相关文章推荐
ADODB类使用
Nov 25 PHP
用PHP将网址字符串转换成超链接(网址或email)
May 25 PHP
php Hex RGB颜色值互换的使用
May 10 PHP
zf框架的db类select查询器join链表使用示例(zend框架)
Mar 14 PHP
ThinkPHP中公共函数路径和配置项路径的映射分析
Nov 22 PHP
php显示指定目录下子目录的方法
Mar 20 PHP
php自动给网址加上链接的方法
Jun 02 PHP
FleaPHP框架数据库查询条件($conditions)写法总结
Mar 19 PHP
php简单创建zip压缩文件的方法
Apr 30 PHP
PHP中字符与字节的区别及字符串与字节转换示例
Oct 15 PHP
yii2简单使用less代替css示例
Mar 10 PHP
ThinkPHP 3.2.2实现事务操作的方法
May 05 PHP
PHP判断图片格式的七种方法小结
Jun 03 #PHP
基于session_unset与session_destroy的区别详解
Jun 03 #PHP
PHP批量采集下载美女图片的实现代码
Jun 03 #PHP
基于PHP CURL获取邮箱地址的详解
Jun 03 #PHP
解析CI即CodeIgniter框架在Nginx下的重写规则
Jun 03 #PHP
深入php函数file_get_contents超时处理的方法详解
Jun 03 #PHP
详解PHP内置访问资源的超时时间 time_out file_get_contents read_file
Jun 03 #PHP
You might like
基于mysql的bbs设计(五)
2006/10/09 PHP
php获取汉字首字母的函数
2013/11/07 PHP
编译PHP报错configure error Cannot find libmysqlclient under usr的解决方法
2014/06/27 PHP
php实现将wav文件转换成图像文件并在页面中显示的方法
2015/04/21 PHP
示例详解Laravel重置密码代码重构
2016/08/10 PHP
如何用JavaScript动态呼叫函数(两种方式)
2013/05/03 Javascript
推荐5 个常用的JavaScript调试技巧
2015/01/08 Javascript
angular中使用路由和$location切换视图
2015/01/23 Javascript
JS弹出对话框实现方法(三种方式)
2015/12/18 Javascript
JavaScript隐式类型转换
2016/03/15 Javascript
很棒的Bootstrap选项卡切换效果
2016/07/01 Javascript
微信小程序登录态控制深入分析
2017/04/12 Javascript
整理一些最近经常遇到的前端面试题
2017/04/25 Javascript
js插件实现图片滑动验证码
2020/09/29 Javascript
vue中rem的配置的方法示例
2018/08/30 Javascript
vuex实现及简略解析(小结)
2019/03/01 Javascript
微信小程序封装自定义弹窗的实现代码
2019/05/08 Javascript
nodejs中各种加密算法的实现详解
2019/07/11 NodeJs
详解Python中的文本处理
2015/04/11 Python
基于python的七种经典排序算法(推荐)
2016/12/08 Python
Python上下文管理器和with块详解
2017/09/09 Python
使用python获取电脑的磁盘信息方法
2018/11/01 Python
python实现停车管理系统
2018/11/30 Python
Python 读取串口数据,动态绘图的示例
2019/07/02 Python
在Python中append以及extend返回None的例子
2019/07/20 Python
python中调试或排错的五种方法示例
2019/09/12 Python
python隐藏类中属性的3种实现方法
2019/12/19 Python
PythonPC客户端自动化实现原理(pywinauto)
2020/05/28 Python
STAUD官方网站:洛杉矶独有的闲适风格
2019/04/11 全球购物
Nike意大利官网:Nike.com IT
2020/01/19 全球购物
向全球直邮输送天然健康产品:iHerb.com
2020/05/03 全球购物
FragranceNet中文网:北美健康美容线上零售商
2020/08/26 全球购物
学前班教师的自我鉴定
2013/12/05 职场文书
数控专业大学毕业生职业规划范文
2014/02/06 职场文书
向领导表决心的话
2014/03/11 职场文书
HTML+CSS 实现顶部导航栏菜单制作
2021/06/03 HTML / CSS