31

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.

hakre
  • 193,403
  • 52
  • 435
  • 836
Roger
  • 8,286
  • 17
  • 59
  • 77

9 Answers9

49

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

Andy Fowler
  • 741
  • 6
  • 6
  • Great, this worked excellent for me. Using nginx 1.6.2 on Ubuntu 14.04. Thanks for this comment. – Bunkai.Satori Feb 21 '15 at 19:18
  • Thank you SO much! Wish I'd discovered it an hour ago. Simple, elegant single line solution :) – digitaltoast Feb 12 '16 at 19:46
  • This is what did it for me. – logic Dec 15 '16 at 15:44
  • I like this simple in-script solution, which overrides the conf files. I got this to work by using `set_time_limit(0)`, sending only this header, no content type header, followed by my echo'd HTML (or require_once()), followed by `ob_flush()`, and then `flush()`. I could then do my long task (an eCommerce task) and then output the rest of the HTML when done to close the page, which adds some Javascript that redirects the page immediately to their order receipt. – Volomike Apr 24 '17 at 05:23
  • This did work for me, but it broke th webpagetest.org tests. What could go wrong here? – Aftab Naveed Nov 06 '17 at 08:50
  • as opposed to the accepted solution above which didn't work, this one did. Thanks! – ExternalUse Feb 07 '18 at 09:27
40

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:

Community
  • 1
  • 1
Roger
  • 8,286
  • 17
  • 59
  • 77
29

Easy solution on nginx server:

fastcgi_keep_conn on; # < solution

proxy_buffering off;
gzip off;
Ondrej Prochazka
  • 740
  • 8
  • 10
9

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.

Redzarf
  • 2,578
  • 4
  • 30
  • 40
  • +1 for a PHP only solution (which can be easily enabled ad hoc). Note that to make it work `header('X-Accel-Buffering: no');` was needed too. – cvsguimaraes Aug 24 '15 at 11:50
  • 1
    This `header('Content-Encoding: none;');` worked for me. No need for `ob_end_flush()` or `flush()`, just make sure the output is big enough by using `echo str_repeat(' ', 1024*64);` –  Feb 14 '17 at 08:55
5

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

Petah
  • 45,477
  • 28
  • 157
  • 213
  • You may want to call @ob_flush() as well. – RC. Feb 02 '11 at 04:17
  • @RC: just curious, why do you need an '@' before ob_flush? I'd use: `if(ob_get_level() > 0) ob_flush();` – meze Feb 02 '11 at 04:50
  • @Roger, do you use output buffering or have output compression enabled? – Petah Feb 02 '11 at 10:29
  • @meze, it suppresses an error that you will get if output buffering is not enabled. – Petah Feb 02 '11 at 10:29
  • @Petah: then `ob_get_level` will return 0, there's no need to do @-hacks. – meze Feb 02 '11 at 10:31
  • @meze, personal preferences. Me I would turn all output buffering off in such a case `while (ob_get_level() > 0) ob_end_clean()` – Petah Feb 02 '11 at 10:37
  • 1
    @Roger, it sounds like you have output buffering issues. Are you on shared hosting? Do you have a demo page I can look at, and/or a phpinfo output. – Petah Feb 02 '11 at 21:07
  • @Petah, i have: output_buffering = Off AND zlib.output_compression = Off AND here you can see the phpini data: http://pastie.org/1536717 – Roger Feb 07 '11 at 12:17
3

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

ttk
  • 407
  • 3
  • 4
2

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

Andy Baird
  • 6,088
  • 4
  • 43
  • 63
  • Not working here. It prints everything at the end of the loop execution. – Roger Feb 02 '11 at 04:31
  • Check your php.ini for the following: output_buffering = Off zlib.output_compression = Off (Make sure those are both set this way) – Andy Baird Feb 02 '11 at 04:37
1

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";
}
Nicolas Thery
  • 2,319
  • 4
  • 26
  • 36
0

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().

knrdk
  • 536
  • 5
  • 13