开发者

PHP Flush that works... even in Nginx

开发者 https://www.devze.com 2023-02-08 04:54 出处:网络
Is it possible to echo each time the loop is executed? For example: foreach(range(1,9) as $n){ echo $n.\"\\n\";

Is it possible to echo each time the loop is executed? For example:

foreach(range(1,9) as $n){
    echo $n."\n";
    开发者_运维知识库sleep(1);
}

Instead of printing everything when the loop is finished, I'd like to see it printing each result per time.


The easiest way to eliminate nginx's buffering is by emitting a header:

header('X-Accel-Buffering: no');

This eliminates both proxy_buffering and (if you have nginx >= 1.5.6), fastcgi_buffering. The fastcgi bit is crucial if you're using php-fpm. The header is also far more convenient to do on an as-needed basis.

Docs on X-Accel-Buffering Docs on fastcgi_buffering


FINAL SOLUTION

So that's what I found out:

Flush would not work under Apache's mod_gzip or Nginx's gzip because, logically, it is gzipping the content, and to do that it must buffer content to gzip it. Any sort of web server gzipping would affect this. In short, at the server side, we need to disable gzip and decrease the fastcgi buffer size. So:

  • In php.ini:

    . output_buffering = Off

    . zlib.output_compression = Off

  • In nginx.conf:

    . gzip off;

    . proxy_buffering off;

Also have this lines at hand, specially if you don't have acces to php.ini:

  • @ini_set('zlib.output_compression',0);

  • @ini_set('implicit_flush',1);

  • @ob_end_clean();

  • set_time_limit(0);

Last, if you have it, coment the code bellow:

  • ob_start('ob_gzhandler');

  • ob_flush();

PHP test code:

ob_implicit_flush(1);

for($i=0; $i<10; $i++){
    echo $i;

    //this is for the buffer achieve the minimum size in order to flush data
    echo str_repeat(' ',1024*64);

    sleep(1);
}

Related:

  • php flush not working

  • How to flush output after each `echo` call?

  • PHP flushing output as soon as you call echo


Easy solution on nginx server:

fastcgi_keep_conn on; # < solution

proxy_buffering off;
gzip off;


I didn't want to have to turn off gzip for the whole server or a whole directory, just for a few scripts, in a few specific cases.

All you need is this before anything is echo'ed:

header('Content-Encoding: none;');

Then do the flush as normal:

ob_end_flush();
flush();

Nginx seems to pick up on the encoding having been turned off and doesn't gzip.


You need to flush the php's buffer to the browser

foreach(range(1,9) as $n){
    echo $n."\n";
    flush();
    sleep(1);
}

See: http://php.net/manual/en/function.flush.php


I found that you can set:

header("Content-Encoding:identity");

in your php script to disable nginx gzipping without having to modify the nginx.conf


You can accomplish this by flushing the output buffer in the middle of the loop.

Example:

ob_start();
foreach(range(1,9) as $n){
    echo $n."\n";
    ob_flush();
    flush();
    sleep(1);
}

Note that your php.ini settings can affect whether this will work or not if you have zlib compression turned on


I had a gzip problem comming from my php-fpm engine. this code is the only one working for me :

function myEchoFlush_init() {
    ini_set('zlib.output_compression', 0);
    ini_set('output_buffering', 'Off');
    ini_set('output_handler', '');
    ini_set('implicit_flush', 1);
    ob_implicit_flush(1);
    ob_end_clean();
    header('Content-Encoding: none;');

}

function myEchoFlush($str) {
    echo $str . str_repeat(' ', ini_get('output_buffering') * 4) . "<br>\n";
}

This is my test function : it checks max_execution_time :

public function timeOut($time = 1, $max = 0) {
    myEchoFlush_init();
    if ($max) ini_set('max_execution_time', $max);
    myEchoFlush("Starting infinite loop for $time seconds. It shouldn't exceed : " . (ini_get('max_execution_time')));
    $start = microtime(true);
    $lastTick = 1;
    while (true) {
        $tick = ceil(microtime(true) - $start);
        if ($tick > $lastTick) {
            myEchoFlush(microtime(true) - $start);
            $lastTick = $tick;
        }
        if ($tick > $time) break;
    }
    echo "OK";
}


Combining PHP Flush/Streaming with gzip (AWS ALB, nginx only)

My interest in PHP streaming support was to enable the browsers to fetch early/important assets early as to minimize the critical render path. Having to choose between either PHP streaming or gzip wasn't really an alternative. This used to be possible with Apache 2.2.x in the past, however it doesn't look like this is something that's being worked on for current versions.

I was able to get it to work with nginx behind an AWS Application Load Balancer using PHP 7.4 and Amazon Linux 2 (v3.3.x). Not saying it only works with the AWS stack, however the ALB sitting in front of nginx changes things and I didn't have a chance to test it with a directly exposed instance. So keep this in mind.

nginx

location ~ \.(php|phar)(/.*)?$ {
  
    # php-fpm config
    # [...]

    gzip            on;
    gzip_comp_level 4;
    gzip_proxied    any;
    gzip_vary       on;

    tcp_nodelay     on;
    tcp_nopush      off;
}

gzip_proxies & gzip_vary are the key parameters for gzipped streaming, the tcp_ parameters are to disable nginx buffering/waiting for 200ms (not sure if that's still a default nginx setting).

In my case I only needed to enable it for the php files, you should be able to move it higher into your server config.

php.ini

  output_buffering = Off
  zlib.output_compression = Off
  implicit_flush = Off

If you want to manually control when the buffer is sent to the server/browser, set implicit_flush = Off and use ob_flush(); flush() and finally ob_end_flush().

0

精彩评论

暂无评论...
验证码 换一张
取 消