• php输出控制output controll(header, ob_xxx)


    关于php的output controll参考文档:

    http://gywbd.github.io/posts/2015/1/php-output-buffer-in-deep.html

    http://www.cnblogs.com/liuzhang/p/4161213.html

    众所周知,调用header()函数之前不能有任何输出到浏览器,否则会报错。

    如何复现这种情况:

    1、php.ini设置output_buffering OFF

    2、测试代码:

    echo 2;
    header("HTTP/1.1 503 Service Unavailable");

    3、这样程序就会报错:

    Warning: Cannot modify header information - headers already sent by

    如何修复这种错误:

    方法1:

    php.ini中output_buffering 4096设置一个4K的大小,这样程序的echo就会一直缓存到php(或者缓存到web server ?)直到缓存大小为4K或者调用ob_系列函数才会输出。

    这也是下面代码直到sleep 2s后才能显示所有结果的原因:

    echo 2;
    sleep(2);
    echo 3;

    方法2:

    方法1中output_buffering 4096表示默认php程序开启输出(针对echo printf等函数)缓存(效果等同于ob_start()函数),此时调用ob_get_level()得到的结果是1。

    所以可以调用关闭缓存函数来关闭输出缓存

    ob_end_clean()或者ob_get_clean()

    ob_end_clean();
    echo 2;
    header("HTTP/1.1 503 Service Unavailable");

    这样就会报错了。

    但是如下代码也并不会立即输出结果:

    ob_get_clean();
    echo 2;
    sleep(2);
    echo 3;

    原因应该是ob_函数只关闭了php的输出缓存,而echo的东西依旧存在web server里面被缓存着,也只有等待程序完毕web server才把输出完全发送给浏览器。

    如果想要程序echo一个东西浏览器就显示一个,可以如下操作:

    echo str_pad('',4096)."
    ";  //这一行很重要,强制使缓冲溢出,4096等于output_buffering 的值
    echo 1;
    
    ob_flush();
    flush();
    
    sleep(2);
    echo 2;

    //@todo 未完成,需要重新理一理,还未完全搞懂。flush和ob系列函数有些是控制php缓存的,有些是控制web server缓存的。

     更新:

    这个文档比较易懂 http://www.hackingwithphp.com/13/0/0/output-buffering

    下面的栗子都是把php.ini中output buffing OFF掉来测试的。

    栗子1:

    ob_start();            //开启一个buffer
        print "Hello First!
    ";  //往buffer里面写入东西
        ob_end_flush();        //关闭掉buffer, 并且把buffer里面的东西输出出来  此时输出"Hello First
    "
    
        ob_start();        //开启一个buffer
        print "Hello Second!
    ";  //往buffer里面写入东西
        ob_end_clean();        //关闭掉buffer,并且清空掉这个buffer里面的东西
    
        ob_start();          //开启一个buffer
        print "Hello Third!
    ";    //往这个buffer里面写入东西,

    //程序结束时,会自动输出所有buffer里面的东西,此时输出"Hello Third "

    说明:

    该栗子会输出hello first 和 hello third

    栗子2:

     ob_start();        //开启
     print "Hello First!
    ";  //写入东西
     ob_flush();        //输出该buffer的东西,和ob_end_flush唯一不同的是少了end,也就是说该buffer还没有关闭      此时输出“helle first”
     print "Hello Second!
    "; //继续往这个buffer写入
     ob_clean();        //清除这个buffer所有内容
     print "Hello Third!
    ";  //继续往这个buffer写入

    //程序结束时,自动输出buffer所有内容,此时输出"hello third"

    说明:

    这个栗子输出hello first和hello third

    和栗子1唯一不同的是这个栗子只使用了一个buffer,省去了buffer close/open 的操作,效率要高一点。

    栗子3:

    ob_start();                    //开启
    print "Hello first!
    ";        //写入
    
    ob_start();                    //开启第二个buffer
    print "Hello second!
    ";    //写入到第二个buffer
    
    ob_clean();                    //清空第二个buffer,因为buffer的创建是栈的形式,所以满足后进先出,一个ob_函数只能影响到最近的一个buffer
    
    //程序结束后输出所有buffer内容,此时输出"hello first"

    输出hello first

    栗子4:

    ob_start();                        //开启
    print "In first buffer
    ";        //写入
    
    ob_start();                        //开启第二个buffer
    print "In second buffer
    ";        //写入到第二个buffer
    ob_end_flush();                    //关闭第二个buffer并且输出第二个buffer的内容,此时因为第一个buffer没有关闭,所以第二个buffer的输出会当做内容写入到第一个buffer里面去,这三行等同于 print "In second buffer
    ";
    
    print "In first buffer
    ";        //继续写入到第一个buffer
    ob_end_flush();                 //关闭第一个buffer并且输出所有内容,此时输出所有三行数据

    输出所有

    注意这个栗子4和栗子3对比。

    栗子5:

    ob_start('ob_gzhandler');    //使用zip压缩内容后发送给浏览器,浏览器会自动解析,
    print "My content
    ";      //压缩后的内容能节约60%左右的带宽
    ob_end_flush();

    有一个坑儿就是要确定浏览器是否支持压缩,可以查看浏览器的request header中的

    Accept-Encoding:gzip, deflate, sdch 头部。如果有gzip应该就是支持的,未验证。
     
     
    栗子6 flush():
    ob_start();        //开启
    echo 'test';    //写入
    ob_end_flush(); //关闭php的buffer并且输出buffer里面的东西
    
    flush();        //立即将php的输出(这个输出指的是已经从buffer里面flush出来的数据,比如这个栗子把上面的ob_end_flush函数注释或者放在flush后面,这个栗子的结果就是要等脚本结束才能看到结果)呈现到浏览器,而不用等待脚本结束
              //这个flush()函数应该是把web server的东西直接输出到浏览器,而上面的ob_flush,ob_end_flush应该是php进程把输出丢给web server
    sleep(2);
    
    echo 444;

    栗子6等同于下面这个栗子(前提是下面这个栗子的php.ini中的output buffering是OFF状态)

    //ob_start();    //这一行是不能加滴,加了的话,echo 'test'就会被放在buffer里面,而紧接着flush()函数是拉不到数据的,因为flush()函数只接受buffer里面flush出来的数据(ob_end_flush,ob_flush)或者非buffer里面的数据。
    echo 'test'; //写入 flush(); sleep(2); echo 444;

    说明:

    栗子6的结果是分段式输出结果。

    强调一点,CLI模式下的output buffering默认是OFF,implicit_flush参数默认是1,

    也就是说CLI模式下,echo 东西时,不会被缓存在buffer里面,而且implicit_flush=1,相当于手动执行了flush()函数,所以,在CLI模式下,echo数据会立马看到效果。

    而web模式下的php.ini,outpu buffering默认是4096, implicit_flush默认是OFF, 相当于默认开启了一个ob_start,而且没有执行flush(),如果要web server把数据立马推到web brower,必须手动调用flush().

    以下情况会导致使用header等需要发送头部信息的函数报如下错误:

    Warning: Cannot modify header information - headers already sent by

    情况1:

    header等函数放在有输出的代码的后面,这个输出指的是非buffer的输出 或者 buffer里面有输出并且调用了ob_flush/ob_end_flush函数的。

    栗子:

    //php.ini中output buffering OFF
    echo "test"; header("HTTP/1.1 503 Service Unavailable");

    或者

    //php.ini中output buffering OFF
    ob_start();
    echo "test";
    ob_end_flush();
    header("HTTP/1.1 503 Service Unavailable");

    或者

    //php.ini中的output buffering 4096 可以用ob_start(NULL, 4096);代替
     echo str_pad(' ', 4097);//如果echo的大小小于4096则不会报错。 
    header(
    "HTTP/1.1 503 Service Unavailable");

    这三种情况都会报错header,

    其中第三个栗子,header和echo都在同一个buffer

    如果第三个栗子echo的内容<4096字节,那echo和header属于同一个buffer,php底层会自动先把header的内容写入header buffer,再把echo的内容写入body buffer,然后先发送header再发送body分别给SAPI,所以并不会报错

    如果第三个栗子echo的内容>=4096字节,那么php会自动调用类似ob_flush/ob_end_flush的函数(也就是等同于第二个例子),把echo的内容装入body buffer传递给sapi(只会装入4096个字节),自动组装一个默认的header buffer传递给sapi,

      然后代码继续运行,如果后面的代码没有header等函数,则会把剩下的echo内容第二次装入body buffer传递给sapi并且正常运行下去,如果遇到header函数,此时因为已经传递过一个默认的header buffer给SAPI了,所以这个时候再修改header要报错。

    所以导致header函数报错的最终原因是看php有没有传递header buffer给sapi,也就是看有没有手动ob_flush()/ob_end_flush()或者自动flush(echo超过output buffering设置或者ob_start(NULL, buffer size)设置时会自动触发flush)

  • 相关阅读:
    树状数组
    hibernate hql where语句拼接工具类
    Unit Testing of Spring MVC
    Unit Testing of Spring MVC Controllers1
    查询时间不能超过90天
    实现日期比较
    Criteria查询
    楼房重建 线段树
    [SDOI2009]HH去散步 矩阵乘法
    [SDOI2014]数表 莫比乌斯反演
  • 原文地址:https://www.cnblogs.com/alazalazalaz/p/6257303.html
走看看 - 开发者的网上家园