74

I have a php script that only produces logs to the client.
When I echo something, I want it to be transferred to client on-the-fly.
(Because while the script is processing, the page is blank)
I had already played around with ob_start() and ob_flush(), but they didn't work.

What's the best solution?

PS: it is a little dirty to put a flush at the end of the echo call...

EDIT: Neither the Answers worked, PHP or Apache Fault?

Paramone
  • 2,634
  • 4
  • 31
  • 58
cusspvz
  • 5,143
  • 7
  • 30
  • 45
  • 3
    On the fly? You mean echo as it's being produced? Because output buffering functions like ob_start are there to do the exact opposite. – Manos Dilaverakis Jun 28 '10 at 14:29
  • 1
    @Manos Dilaverakis - I mean, when i do an echo, i wan't it to be directly transfered to the client, for now, the page is loading, and only at the end of the script, it transfer all at once. Cat it be apache is fault? – cusspvz Jun 28 '10 at 14:31
  • That is expected behaviour - output is sent once the script stops executing, unless you specify otherwise. Depending on how your application is designed, you may be able to flush the buffer at certain points in execution (for example when a class is instantiated or a given view function which is often called is run). – cam8001 Jun 28 '10 at 14:40

18 Answers18

70

I've gotten the same issue and one of the posted example in the manual worked. A character set must be specified as one of the posters here already mentioned. http://www.php.net/manual/en/function.ob-flush.php#109314

header( 'Content-type: text/html; charset=utf-8' );
echo 'Begin ...<br />';
for( $i = 0 ; $i < 10 ; $i++ )
{
    echo $i . '<br />';
    ob_flush();
    flush();
    sleep(1);
}
echo 'End ...<br />';
still_dreaming_1
  • 8,661
  • 6
  • 39
  • 56
Teno
  • 2,582
  • 4
  • 35
  • 57
  • 6
    I tried everything to get output-flushing working correctly. The thing that got it working in the end was "Content-type" header in your example. Thankyou fine sir!!! – Wireblue Feb 28 '13 at 02:31
  • Yep, solved it for me as well. Awesome. Although, I also had to enable implicit flushing, but whatever. :) – user966939 Aug 10 '14 at 05:05
  • 2
    None of the examples here and other pages worked for me.Linux with Apache and php 7 – Dmitry z Dec 02 '16 at 16:57
  • 1
    I found that if the first echo is a tag, it seems to fail. Like `echo '

    Begin...

    ';` as the first echo would cause the entire thing to wait until it was finished. Using the exact example here worked fine on centos, apache, and php 7.
    – Dustin Graham Jun 14 '18 at 23:47
  • 2
    **Code doesn't work.** tested on awardspace free hosting default settings, firefox. – Pacerier Jun 06 '20 at 11:56
  • Not working, tested with Firefox, Chrome, Edge, IE. – vee Jun 16 '21 at 07:32
  • I improved this code a little by ob_flush()ing any explicit programmer induced caching first before trying to fully flush() to the browser. It is still possible for the Apache/web server to do its own caching that cannot be controlled by php code, or for the browser to cache contents before displaying it to you. – still_dreaming_1 Jan 14 '23 at 14:43
  • It works without the `header` and `flush`. You just need the `ob_flush`. – Lawrence Feb 23 '23 at 14:49
45

Edit:

I was reading the comments on the manual page and came across a bug that states that ob_implicit_flush does not work and the following is a workaround for it:

ob_end_flush();

# CODE THAT NEEDS IMMEDIATE FLUSHING

ob_start();

If this does not work then what may even be happening is that the client does not receive the packet from the server until the server has built up enough characters to send what it considers a packet worth sending.


Old Answer:

You could use ob_implicit_flush which will tell output buffering to turn off buffering for a while:

ob_implicit_flush(true);

# CODE THAT NEEDS IMMEDIATE FLUSHING

ob_implicit_flush(false);
amphetamachine
  • 27,620
  • 12
  • 60
  • 72
  • Yes, definitely I'm looking for something like that (+1), but it didn't worked :s Can it be Apache's fault? – cusspvz Jun 28 '10 at 14:38
  • These two comments to that function may help: http://www.php.net/manual/en/function.ob-implicit-flush.php#35072 http://www.php.net/manual/en/function.ob-implicit-flush.php#33778 – cam8001 Jun 28 '10 at 14:44
  • If i stop the flush `ob_end_flush()`, my program skips the important step (don't know why ;s) I've added the line `flush` on my Class `DebugEcho` function, and it didn't workedtoo, besides, it's a little bad and time consuming doing a flush on the cache every `echo` call. – cusspvz Jun 28 '10 at 14:54
  • ob_end_flush will clear the buffer and stop output buffering completely, so don't use that until you're sure that you don't want to buffer anymore content! – cam8001 Jun 28 '10 at 15:11
  • You still need to `flush()` after each line of output as it is not guaranteed to be sent to the client immediately. – Justin Johnson Jun 28 '10 at 15:14
  • You said that the server may not send the data until it has enough data collected. I wonder how to configure this size. – Julian F. Weinert Jan 08 '13 at 17:01
  • @amphetamachine thanks! is it possible to flush on top of the previously flushed contents? – RafaSashi Nov 21 '14 at 14:57
  • The above are rather old and not working. The 2018 version below is the only one working in 2018 – Dimitrios Ververidis Jun 06 '18 at 12:54
  • **Code doesn't work.** tested on awardspace free hosting default settings, firefox. – Pacerier Jun 06 '20 at 11:55
  • The edit on this answer worked for me in 2020 combined with flush() and ob_flush(). – CMG Jul 16 '20 at 19:05
45

So here'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 these lines at hand, especially if you don't have access 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, comment 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 to make the buffer achieve the minimum size in order to flush data
    echo str_repeat(' ',1024*64);

    sleep(1);
}
DisgruntledGoat
  • 70,219
  • 68
  • 205
  • 290
Roger
  • 8,286
  • 17
  • 59
  • 77
  • 4
    The key to the answer for me was the `//this is for the buffer achieve the minimum size in order to flush data echo str_repeat(' ',1024*64);` – Matt The Ninja Jul 09 '14 at 13:57
  • Only this worked for me. +1 for the `str_repeat` bit, but perhaps a good idea to make it an invisible character instead of a whitespace? – Léo Lam Aug 19 '14 at 10:26
  • 1
    So how do we disable gzip for that page only? – Pacerier Mar 14 '15 at 21:49
  • 2
    `echo str_repeat(' ',1024*64);` made the `flush` work. But how come I couldn't find it anywhere else? The documentation doesn't mention it either.... – Shawn Aug 17 '16 at 01:05
  • The minimum buffer size is the critical missing piece. thanks! – Paulo Aug 23 '16 at 14:28
  • 1
    this is ultimate, god level steps to confirmed success. in "echo str_repeat(' ',1024*64);" i had to use 4096 instead of 1024 for better real time echo. cheers – Sunil Kumar Mar 07 '19 at 09:24
  • This sorta works.. but having to send so many bytes **defeats the purpose**. – Pacerier Jun 06 '20 at 10:58
19

For those coming in 2018:

The ONLY Solution worked for me:

<?php

    if (ob_get_level() == 0) ob_start();
    for ($i = 0; $i<10; $i++){

        echo "<br> Line to show.";
        echo str_pad('',4096)."\n";    

        ob_flush();
        flush();
        sleep(2);
    }

    echo "Done.";

    ob_end_flush();
?>

and its very important to keep de "4096" part because it seems that "fills" the buffer...

Ari Waisberg
  • 1,186
  • 1
  • 12
  • 23
  • Not working great in 2020; the first 4 lines are displayed all in once after like 4 seconds, then after this, each new line is displayed one per sec. – 4wk_ Jan 15 '20 at 19:58
  • 2
    BUT if I increase the str_pad value, it's working better! with 4096000 its displaying pretty well. It's definitly a value of content handled by the browser. – 4wk_ Jan 15 '20 at 20:08
  • 2
    Isn't sending 4MB of text data in each step a bit overkill? – M.i.X Feb 10 '20 at 23:21
  • 1
    @M.i.X yes it is! but it's the only way (I know) to do this using just php... the correct way should be ajax, but if you want use just php I don't know a better way – Ari Waisberg Feb 11 '20 at 18:47
  • I use 4096 but did not work, change to 1024*64 working fine. I don't know why. – vee Jun 16 '21 at 07:39
  • This worked for me without the sleep(2); line. – Jorge Mauricio Oct 28 '21 at 21:10
  • You can also change the `output_buffering = 4096` setting in `php.ini` to some lower value to avoid `str_pad`'ding to 4096. – pstryk Nov 18 '21 at 05:33
  • 1
    After a million tries, this is the ONLY code that worked for me with FastCGI. But using 4096 didn't work. I had to use `str_pad('',1024*64);` – Joe May 04 '22 at 05:42
12

Flushing seemingly failing to work is a side effect of automatic character set detection.

The browser will not display anything until it knows the character set to display it in, and if you don't specify the character set, it need tries to guess it. The problem being that it can't make a good guess without enough data, which is why browsers seem to have this 1024 byte (or similar) buffer they need filled before displaying anything.

The solution is therefore to make sure the browser doesn't have to guess the character set.

If you're sending text, add a '; charset=utf-8' to its content type, and if it's HTML, add the character set to the appropriate meta tag.

Thomas Tempelmann
  • 11,045
  • 8
  • 74
  • 149
nobody
  • 121
  • 1
  • 2
9

what you want is the flush method. example:

echo "log to client";
 flush();
GSto
  • 41,512
  • 37
  • 133
  • 184
  • @CuSS Regardless, this is the only way to satisfy the functionality that you want – Justin Johnson Jun 28 '10 at 15:14
  • 1
    what's dirty about it? That's what flush() was designed to do, and it gets the job done. – GSto Jun 28 '10 at 15:41
  • 7
    `ini_set('implicit_flush', true)` will basically emulate a flush() after ever output block, and it can get set pretty much anywhere (php.ini, .htaccess, per-script, etc...) – Marc B Jun 28 '10 at 20:47
  • 1
    **Code doesn't work.** tested on awardspace free hosting default settings, firefox. – Pacerier Jun 06 '20 at 11:25
5

Why not make a function to echo, like this:

function fecho($string) {
 echo $string;
 ob_flush();
}
cam8001
  • 1,581
  • 1
  • 11
  • 22
5

One thing that is not often mentionned is gzip compression that keeps turned ON because of details in various hosting environments.

Here is a modern approach, working with PHP-FPM as Fast CGI, which does not need .htaccess rewrite rule or environment variable :

In php.ini or .user.ini :

output_buffering = 0
zlib.output_compression = 0
implicit_flush = true
output_handler = 

In PHP script :

header('Content-Encoding: none'); // Disable gzip compression
ob_end_flush(); // Stop buffer
ob_implicit_flush(1); // Implicit flush at each output command

See this comment on official PHP doc for ob_end_flush() need.

ZalemCitizen
  • 474
  • 7
  • 13
  • 1
    This is the only solution that worked for me, and I tried all. Thanks! – Victor May 14 '19 at 08:59
  • Not working great in 2020; the first 4 lines are displayed all in once after like 4 seconds, then after this, each new line is displayed one per sec – 4wk_ Jan 15 '20 at 20:03
  • 1
    **Code doesn't work.** tested on awardspace free hosting default settings, firefox. – Pacerier Jun 06 '20 at 11:20
4

I had a similar thing to do. Using

// ini_set("output_buffering", 0);  // off 
ini_set("zlib.output_compression", 0);  // off
ini_set("implicit_flush", 1);  // on   

did make the output flushing frequent in my case.

But I had to flush the output right at a particular point(in a loop that I run), so using both

ob_flush();
flush();

together worked for me.

I wasn't able to turn off "output_buffering" with ini_set(...), had to turn it directly in php.ini, phpinfo() shows its setting as "no value" when turned off, is that normal? .

freedayum
  • 301
  • 2
  • 7
  • 17
  • Yes if phpinfo() shows "no value", it means that output_buffering is set to off. Otherwise it would say like 4096 – Tarik Aug 06 '17 at 13:56
  • 1
    **Code doesn't work.** tested on awardspace free hosting default settings, firefox. – Pacerier Jun 06 '20 at 11:23
4
header( 'X-Accel-Buffering: no' );
header( 'Content-Type: text/html; charset=utf-8' );

echo 'text to display';
echo '<span style="display: none;">' . str_repeat ( ' ', 4096 ) . '</span>';
flush();
usleep( 10 );
  • 1
    Baam! Thank you so much for pointing out this X-Accel-Buffering Header!! After having tried nearly everything on my ionos webspace this really saved my day!! :) – ingonieur Apr 22 '22 at 12:18
3

The correct function to use is flush().

<html>
<body>
<p>
Hello! I am waiting for the next message...<br />
<?php flush(); sleep(5); ?>
I am the next message!<br />
<?php flush(); sleep(5); ?>
And I am the last message. Good bye.
</p>
</body>
</html>

Please note that there is a "problem" with IE, which only outputs the flushed content when it is at least 256 byte, so your first part of the page needs to be at least 256 byte.

Dennis G
  • 21,405
  • 19
  • 96
  • 133
3

This works fine for me (Apache 2.4/PHP 7.0):

@ob_end_clean();
echo "lorem ipsum...";
flush();
sleep(5);
echo "<br>dolor...";
flush();
sleep(5);
echo "<br>sit amet";
Radacina
  • 431
  • 5
  • 10
1

Anti-virus software may also be interfering with output flushing. In my case, Kaspersky Anti-Virus 2013 was holding data chunks before sending it to the browser, even though I was using an accepted solution.

marcovtwout
  • 5,230
  • 4
  • 36
  • 45
0

Sometimes, the problem come from Apache settings. Apache can be set to gzip the output. In the file .htaccess you can add for instance :

SetEnv no-gzip 1
jlguenego
  • 1,192
  • 15
  • 23
0

Try this:

while (@ob_end_flush());      
ob_implicit_flush(true);

echo "first line visible to the browser";
echo "<br />";

sleep(5);

echo "second line visible to the browser after 5 secs";

Just notice that this way you're actually disabling the output buffer for your current script. I guess you can reenable it with ob_start() (i'm not sure).

Important thing is that by disabling your output buffer like above, you will not be able to redirect your php script anymore using the header() function, because php can sent only once per script execution http headers. You can however redirect using javascript. Just let your php script echo following lines when it comes to that:

        echo '<script type="text/javascript">';
        echo 'window.location.href="'.$url.'";';
        echo '</script>';
        echo '<noscript>';
        echo '<meta http-equiv="refresh" content="0;url='.$url.'" />';
        echo '</noscript>'; 
        exit;
Grigoreas P.
  • 2,452
  • 25
  • 19
0

Note if you are on certain shared hosting sites like Dreamhost you can't disable PHP output buffering at all without going through different routes:

Changing the output buffer cache If you are using PHP FastCGI, the PHP functions flush(), ob_flush(), and ob_implicit_flush() will not function as expected. By default, output is buffered at a higher level than PHP (specifically, by the Apache module mod_deflate which is similar in form/function to mod_gzip).

If you need unbuffered output, you must either use CGI (instead of FastCGI) or contact support to request that mod_deflate is disabled for your site.

https://help.dreamhost.com/hc/en-us/articles/214202188-PHP-overview

Matthew Lock
  • 13,144
  • 12
  • 92
  • 130
0

I'm late to the discussion but I read that many people are saying appending flush(); at the end of each code looks dirty, and they are right.

Best solution is to disable deflate, gzip and all buffering from Apache, intermediate handlers and PHP. Then in your php.ini you should have:

            output_buffering = Off
            zlib.output_compression = Off
            implicit_flush = Off

Temporary solution is to have this in your php.ini IF you can solve your problem with flush(); but you think it is dirty and ugly to put it everywhere.

implicit_flush = On

If you only put it above in your php.ini, you don't need to put flush(); in your code anymore.

Tarik
  • 4,270
  • 38
  • 35
0

This is my code: (work for PHP7)

private function closeConnection()
{
    @apache_setenv('no-gzip', 1);
    @ini_set('zlib.output_compression', 0);
    @ini_set('implicit_flush', 1);

    ignore_user_abort(true);
    set_time_limit(0);

    ob_start();
    // do initial processing here
    echo json_encode(['ans' => true]);

    header('Connection: close');
    header('Content-Length: ' . ob_get_length());
    ob_end_flush();
    ob_flush();
    flush();
}
Dmitry Matrosov
  • 412
  • 4
  • 5