深入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 相关文章推荐
php chr() ord()中文截取乱码问题解决方法
Sep 08 PHP
一个php Mysql类 可以参考学习熟悉下
Jun 21 PHP
『PHP』PHP截断函数mb_substr()使用介绍
Apr 22 PHP
深入php 正则表达式的学习探讨
Jun 06 PHP
windows环境下php配置memcache的具体操作步骤
Jun 09 PHP
关于php操作mysql执行数据库查询的一些常用操作汇总
Jun 24 PHP
解析php如何将日志写进syslog
Jun 28 PHP
php中出现空白页的原因及解决方法汇总
Jul 08 PHP
php天翼开放平台短信发送接口实现方法
Dec 22 PHP
php下pdo的mysql事务处理用法实例
Dec 27 PHP
php类的扩展和继承用法实例
Jun 20 PHP
如何在centos8自定义目录安装php7.3
Nov 28 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
PHP函数in_array()使用详解
2014/08/20 PHP
Thinkphp框架开发移动端接口(2)
2016/08/18 PHP
PHP实现将base64编码字符串转换成图片示例
2018/06/22 PHP
php使用gearman进行任务分发操作实例详解
2020/02/26 PHP
对象无length属性时IE6/IE7中无法将其转换成伪数组(ArrayLike)
2011/07/31 Javascript
js实现图片从左往右渐变切换效果的方法
2015/02/06 Javascript
JQuery实现鼠标移动图片显示描述层的方法
2015/06/25 Javascript
javascript insertAfter()定义与用法示例
2016/07/25 Javascript
Bootstrap CSS组件之导航条(navbar)
2016/12/17 Javascript
简单实现js选项卡切换效果
2017/02/09 Javascript
手机注册发送验证码倒计时的简单实例
2017/11/15 Javascript
vue使用ElementUI时导航栏默认展开功能的实现
2018/07/04 Javascript
vue项目上传Github预览的实现示例
2018/11/06 Javascript
详解Vue中watch的详细用法
2018/11/28 Javascript
微信小程序scroll-view隐藏滚动条的方法详解
2020/03/25 Javascript
ant design vue 表格table 默认勾选几项的操作
2020/10/31 Javascript
[00:44]华丽开场!DOTA2勇士令状带来全新对阵画面
2019/05/15 DOTA
Python中的包和模块实例
2014/11/22 Python
全面了解python字符串和字典
2016/07/07 Python
简介Python的collections模块中defaultdict类型的用法
2016/07/07 Python
Python算法输出1-9数组形成的结果为100的所有运算式
2017/11/03 Python
VSCode Python开发环境配置的详细步骤
2019/02/22 Python
使用python 将图片复制到系统剪贴中
2019/12/13 Python
Java byte数组操纵方式代码实例解析
2020/07/22 Python
Python压缩模块zipfile实现原理及用法解析
2020/08/14 Python
Python面向对象多态实现原理及代码实例
2020/09/16 Python
Anaconda使用IDLE的实现示例
2020/09/23 Python
有影响力的品牌之家:Our Social Collective
2019/06/08 全球购物
Python面试题:Python里面如何生成随机数
2015/03/12 面试题
函授毕业生自我鉴定
2013/11/06 职场文书
婚纱摄影师求职信范文
2014/04/17 职场文书
优秀团干部个人事迹
2014/05/29 职场文书
生物科学专业自荐书
2014/06/20 职场文书
小学体育组工作总结
2015/08/13 职场文书
小学中队长竞选稿
2015/11/20 职场文书
婚礼必备主持词范本!
2019/07/23 职场文书