Problems with PHP Output Buffer Flush - php

I have a PHP script which takes a while to process and I wanted to get the site to display it's progress while it is running.
I have been having problems displaying the contents of the output buffer while a PHP code is running. I have been trying the use of ob_implicit_flush(true), ob_end_flush() and ob_flush() with no success.
After looking for a fair bit at the PHP manual on php.net I have come across a comment by Lee saying
As of August 2012, all browsers seem to show an all-or-nothing
approach to buffering. In other words, while php is operating, no
content can be shown.
In particular this means that the following workarounds listed further
down here are ineffective:
1) ob_flush (), flush () in any combination with other output
buffering functions;
2) changes to php.ini involving setting output_buffer and/or
zlib.output_compression to 0 or Off;
3) setting Apache variables such as "no-gzip" either through
apache_setenv () or through entries in .htaccess.
So, until browsers begin to show buffered content again, the tips
listed here are moot.
see here
If he is correct, is there an alternative I can use to display the progress of the script running?
[edit]
Looks like Lee could be right. I have been doing some testing and here is an example of the problem
<?php
ob_implicit_flush(true);
ob_end_flush();
for ($i=0; $i<5; $i++) {
echo $i.'<br>';
sleep(1);
}
?>
Should display 0 to 4 with a second in between. What actually happens is that the site waits 5 seconds then displays 0 to 4 immediately. It is waiting for the script to complete then displays everything in one go.
I have had output_buffering = Off in my php.ini file and tried setting it to 1 and 4096 (4096 being 4KB) and it still does the same.

Check the buffer size and make an "empty" dummy of that size, for example...
$flushdummy="";
for ($i=0; $i<700; $i++) { $flushdummy=$flushdummy." "; }
$flushdummy=$flushdummy."\n";
then, when output is desired, add the dummy to the output, plus the flush:
echo "here I am<br />", $flushdummy;
flush();
#ob_flush();

Related

using flush() to implement a heartbeat in php

I want to implement a PDF-exportfunction with PHP on a virtual LAMP-server. The user clicks on a link to a PHP-script that produces the PDF with wkhtmltopdf. This PDF can be more than a thousand sites long and needs very many SQL-queries and images. So it can take several minutes to produce this PDF.
The thing is, that most browsers will timeout after a certain time when the server dont respond anything. In firefox this will happen after 300 seconds by default. In about:config it is defined by network.http.response.timeout. When I change the value to 100 , the browser will timeout after 100 seconds when using the pdf-export. The solution would be some kind of heartbeat to avoid a timeout in the browser. I want to realize this heartbeat with the PHP flush() functions but I just cant get it to work.
Here is a testcode. It is from http://manzzup.blogspot.de/2013/11/real-time-updating-of-php-output-using.html:
// Turn off output buffering
ini_set('output_buffering', 'off');
// Turn off PHP output compression
ini_set('zlib.output_compression', false);
//Flush (send) the output buffer and turn off output buffering
while (#ob_end_flush());
// Implicitly flush the buffer(s)
ini_set('implicit_flush', true);
ob_implicit_flush(true);
echo "add this";
echo str_pad("padding: ",8000,"_");
echo "<br />";
ob_flush();
flush();
sleep(5);
echo "Program Output";
ob_flush();
flush();
This is just the last example I have tested, but I am new on stackoverflow and cant post more then 2 Links. here is another related site:
http://www.stevesouders.com/blog/2013/01/31/http-archive-adding-flush/
Is it possible to use the flush() functions in all browsers? Does someone have a running site that uses this functions? What server configurations can stop the flush() functions from working?
Thank you for all upcoming answers!
As you can read here: http://nl1.php.net/flush , avoid using flush() inside a <table> and give a newline within each flush. Also you need to disable output buffering and compression, but you already did this.
Don't forget to increase or disable the time allowed for the script to run. This needs to be done in PHP.ini (sometimes this can even be done runtime), but the web server also has settings for this.
edit
Try also adding header( 'Content-type: text/html; charset=utf-8' );.
Increase script execution time limit
ini_set('max_execution_time', time in seconds);

Can't get flush() working in Windows

We have a ubuntu test server that this simple script works fine on where it outputs a number each second to the browser
<?
for ($i=0; $i<3; $i++)
{
echo $i.'<br>';
flush();
sleep(1);
}
However on my local development box (windows environment) I cannot get it to work, it outputs it all after 3 seconds.
I am using the same php version (5.3.13) as well as apache (2.2.22) version on my local box as the test server.
I have mirrored most of the settings in both php.ini as well as the httpd.conf
Here are settings I have set (which are identical to the test server)
output_buffering 0
zlib.output_compression = Off
zlib.output_compression_level = -1
I don't have mod_gzip installed.
I have even tried doing a wamp install on my box, and it didn't work with that either.
I can't imagine it being a client (browser -- firefox 13) issue as I use the exact same browser to view the script on the test server and it works fine
I have tried enabling implicit flush, doing ob_flush, etc. No luck.
Anyone have any ideas on how to get this working?
Just keep in mind what #Wrikken said, this is not recommended.
So I was thinking, everything you're doing seems to be right. I copied your setup and tried. I faced same problem. I executed the script from command line, it worked.
Then I had to do the last test, Wireshark. Following the first couple of packets it was clear that the server is sending everything correctly, so it had to be the browser’s buffer.
So I tried send some data before the loop, well guess what? It worked!
Here you go:
<?php
ini_set('output_buffering','off');
ini_set('zlib.output_compression', 0);
echo str_repeat(" ", 1024), "\n";
for($i=0;$i<6;$i++) {
echo $i."<br />\n";
ob_flush();
flush();
sleep(1);
}
?>
I'm not sure about the application you have in mind, but this is clearly not a good idea, I highly recommend that you look into other options.
Update:
After a lengthy Google search I found this and this
Because a browser cannot correctly render a page without knowing how
to construct the page's characters, most browsers buffer a certain
number of bytes before executing any JavaScript or drawing the page
And
To avoid these delays, you should always specify the character
encoding as early as possible, for any HTML document larger than 1 KB
(1024 bytes, to be precise, which is the maximum buffer limit used by
any of the browsers we have tested).
So I tried to send the charset first instead of filling the buffer: (and it worked)
<?php
ini_set('output_buffering','off');
ini_set('zlib.output_compression', 0);
//echo str_repeat(" ", 1024), "\n";
header('Content-Type: text/html; charset=iso-8859-1');
//Note that it shoudn't matter which charset you send
for($i=0;$i<6;$i++) {
echo $i."<br />\n";
ob_flush();
flush();
sleep(1);
}
?>
So why was it working with the first server but not the second?
Most likely it's because your first server is sending the charset with the header while the second one isn't.
In your case, however, I'd make the following changes
<?php
ini_set('output_buffering','off');
ini_set('zlib.output_compression', 0);
//Plain text MIME type since you'll use for logging purposes
//and if you run it from CLI, you can ignore the whole header line
header('Content-Type: text/plain; charset=iso-8859-1');
for($i=0;$i<6;$i++) {
//No need to echo <br /> once you'll run it from CLI
echo $i."\n";
ob_flush();
flush();
sleep(1);
}
?>
I have found a solution for my problem.
FOr some reason adding the line
default_charset = "utf-8"
To my php.ini makes it work.
I confirmed my removing the line.. flush didn't work.
Added it, and it worked. Maybe because it adds extra headers giving enough for the browser buffer.

PHP flush and WAMP server

I can't for the life of me get the PHP flush function to work properly, using WAMP. Here is some sample code, commented out are all of the different things I've tried:
//apache_setenv('no-gzip', 1); // returns error that apache_setenv does not exist
//ini_set('zlib.output_compression',0);
//ini_set('implicit_flush',1);
//ob_end_clean();
//for ($i = 0; $i < ob_get_level(); $i++) { ob_end_flush(); }
//ob_implicit_flush(1);
set_time_limit(0);
echo "<pre>";
for ($i = 0; $i < 100; ++$i) {
echo $i.' '.time().str_repeat(' ',256)."\n";
//ob_flush(); // returns error without output buffering enabled
flush();
usleep(100000);
}
It seems no matter what I do, I always get the results all together in one giant chunk.
Edit:
I've uploaded the same exact code to a server running on cPanel/linux, and it works perfect in all browsers. Why can't I get it to work properly on a localhost WAMP server??
flush() may not be able to override the buffering scheme of your web server and it has no effect on any client-side buffering in the browser. It also doesn't affect PHP's userspace output buffering mechanism. This means you will have to call both ob_flush() and flush() to flush the ob output buffers if you are using those.
Several servers, especially on Win32, will still buffer the output from your script until it terminates before transmitting the results to the browser.
Server modules for Apache like mod_gzip may do buffering of their own that will cause flush() to not result in data being sent immediately to the client.
Even the browser may buffer its input before displaying it. Netscape, for example, buffers text until it receives an end-of-line or the beginning of a tag, and it won't render tables until the tag of the outermost table is seen.
Some versions of Microsoft Internet Explorer will only start to display the page after they have received 256 bytes of output, so you may need to send extra whitespace before flushing to get those browsers to display the page.
php.net
Try using ob_flush() before flush();

Output buffer in PHP won't work

For some time (serveral nights..) I've been trying to get a time-expensive script to output simple dots so I know it's still processing the script. Basically it's a cronjob which is going to run nightly to update cache-keys in a memcache server.
No matter what I try I can't get PHP to output the current buffer. What I want is to send the echo'd dots while processing the script. What am I missing to get it to work? I've also tried the flush() function... and also to use ini_set("output_buffering", 1024);
At the moment this is my set up:
# clean all open buffers
while(ob_get_level() != 0)
{
ob_end_clean();
}
ob_start();
// Several loops, taking some minutes...
for( ..loopconditions ..){
echo ".";
ob_flush();
}
ob_end_clean()
Are you debugging this in a browser? If so, it may not be a problem with the output buffering at all.
Every browser has a buffer of it's own and ultimately it decides when to start flushing it.
Mentioned in the manual page for flush():
flush() ... has no effect on any client-side buffering in the browser.
Even the browser may buffer its input before displaying it. Netscape, for example, buffers text until it receives an end-of-line or the beginning of a tag, and it won't render tables until the tag of the outermost table is seen.
Some versions of Microsoft Internet Explorer will only start to display the page after they have received 256 bytes of output, so you may need to send extra whitespace before flushing to get those browsers to display the page.
IIRC, if you're using php-cgi, output buffering doesn't work.
In console, it is easier to use PEAR Console_ProgressBar. It will do it for you.
Example from the docs:
<?php
require_once 'Console/ProgressBar.php';
$bar = new Console_ProgressBar('[%bar%] %percent%', '=>', ' ', 80, 7);
//do some processing here
for ($i = 0; $i <= 7; $i++) {
$bar->update($i);
sleep(1);
}
echo "\n";
?>
[=======================================> ] 57.14%
More examples in:
http://pear.php.net/manual/en/package.console.console-progressbar.php#example-124
http://pear.php.net/manual/en/package.console.console-progressbar.php#example-125

PHP immediate echo

I have quite a long data mining script, and in parts of it I echo some information to the page (during a foreach loop, actually.)
However I am noticing that the information is being sent to the browse not immediately as I had hoped, but in 'segments'.
Is there some function I can use after my echo to send all the data to the browser immediately?
Thanks.
You probably want flush(). However, PHP may be using output buffering. There are a few ways that this can change things, but in a nutshell, you can flush(), then ob_flush().
You can try using flush() after each echo, but even that won't guarantee a write to the client depending on the web server you're running.
Yes, padding your output to 1024 bytes will cause most browsers to start displaying the content.
But we also learn from #nobody's answer to question "How to flush output after each `echo` call?" that the 1024 bytes browser buffering effect only happens when the browser has to guess the character encoding of the page, which can be prevented by sending the proper Content-Type header (eg. "Content-Type: text/html; charset=utf-8"), or by specifying the content charset through appropriate html meta tags. And it worked as well for me in all browsers.
So basically, all one need to do is:
header('Content-Type: text/html; charset=utf-8');
ob_implicit_flush(true);
With no requirement for extra padding or flushing, which is of great cosmetic benefit for the code! Of course, headers have to be sent before any content, and one also has to make sure no output buffering is going on.
Problem definitely solved for me! Please (+1) #nobody's answer on the other question as well if it works for you. If, although, one still encounters problems, I suggest checking out the answers to that other question for other specific situations that might presumely prevent implicit flushing from working correctly.
Note also that some browsers won't start displaying anything until the body of the response contains a certain amount of data - like 256 or 1024 bytes. I have seen applications before that pad data with a 1024 character long comment near the top of the page, before they do a flush. It's a bit of a hack, but necessary.
This applies to Internet Explorer and Safari IIRC.
So,
If it is the first flush, make sure you have output at least 1024 bytes sofar (not including HTTP headers).
Call flush()
If you can determine that there is output buffering in place, issue ob_flush()
I like to just use
while (ob_get_level()) ob_end_flush();
near the start of my script somewhere, and then just
flush();
whenever I want to flush. This assumes that you don't want any output buffering at all, even if it was set up before your script (such as in a PHP.ini or htaccess configuration).
You should be able to use something like this to force output to be sent immeadiately. Put it at the part of the code you want the output to be sent.
flush();
ob_flush();
Phew! I finally found the answer to Google Chrome's buffer issue! Thanks to boysmakesh for the push in the right direction. Here's the function I use:
function buffer_flush(){
echo str_pad('', 512);
echo '<!-- -->';
if(ob_get_length()){
#ob_flush();
#flush();
#ob_end_flush();
}
#ob_start();
}
And this is how I call it:
show_view('global', 'header'); // Echos the <html><head>... tags and
// includes JS and CSS.
show_view('global', 'splash_screen'); // Shows a loading image telling
// the user that everything's okay.
buffer_flush(); // Pretty obvious. At this point the loading view shows
// up on every browser i've tested (chrome, firefox,
// IE 7 & 8)
show_view('global', 'main'); // Has a loop that echos "Test $i<br>" 5
// times and calls buffer_flush() each time.
show_view('global', 'footer'); // End the html page and use JQuery to
// fade out the loading view.
To perfectly work this out in Google chrome,
try this:
$i = 0;
$padstr = str_pad("",512," ");
echo $padstr;
while ($i <= 4){
$padstr = str_pad("",512," ");
echo $padstr;
echo "boysmakesh <BR> ";
flush();
sleep(2);
$i = $i + 1;
}
Ee are sending 512 bytes before sending EACH echo. Don't forget to put <BR> at the end of content before you flush. Else it won't work in Chrome but works in IE.
The data we padding is browser dependent. For some browsers it's enough to have 256 bytes but some need 1024 bytes. For chrome it is 512.
ignore_user_abort(TRUE); // run script in background
set_time_limit(0); // run script forever
$interval=150000;
$i = 0;
if(
strpos($_SERVER["HTTP_USER_AGENT"], "Gecko") or
strpos($_SERVER["HTTP_USER_AGENT"], "WebKit")
){
# important to change browser into quirks mode
echo '<?xml version="1.0" encoding="iso-8859-1"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
}
function buffer_flush(){
echo "\n\n<!-- Deal with browser-related buffering by sending some incompressible strings -->\n\n";
for ( $i = 0; $i < 5; $i++ )
echo "<!-- abcdefghijklmnopqrstuvwxyz1234567890aabbccddeeffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz11223344556677889900abacbcbdcdcededfefegfgfhghgihihjijikjkjlklkmlmlnmnmononpopoqpqprqrqsrsrtstsubcbcdcdedefefgfabcadefbghicjkldmnoepqrfstugvwxhyz1i234j567k890laabmbccnddeoeffpgghqhiirjjksklltmmnunoovppqwqrrxsstytuuzvvw0wxx1yyz2z113223434455666777889890091abc2def3ghi4jkl5mno6pqr7stu8vwx9yz11aab2bcc3dd4ee5ff6gg7hh8ii9j0jk1kl2lmm3nnoo4p5pq6qrr7ss8tt9uuvv0wwx1x2yyzz13aba4cbcb5dcdc6dedfef8egf9gfh0ghg1ihi2hji3jik4jkj5lkl6kml7mln8mnm9ono -->\n\n";
while ( ob_get_level() )
ob_end_flush();
if(ob_get_length()){
#ob_flush();
#flush();
#ob_end_flush();
}
#ob_start();
}
ob_start();
do{
if($i<10){
buffer_flush();
echo ". ";
buffer_flush();
usleep($interval);
} else {
echo sprintf("<pre>%s</pre>", print_r($_SERVER,true));
break;
}
$i++;
}while(true);
Running php 5.5 on IIS 7, IE 11 (win server) I found this worked as the opening lines of the file. Note putting the while statement before the header caused a header already written error.
header('Content-Type: text/html; charset=utf-8');
while (ob_get_level()) ob_end_flush();
ob_implicit_flush(true);
Further references to ob_flush() in the script caused a buffer does not exist error.
This worked fine when I was processing a file and sending sql statements to the browser, however when I hooked up the db (ms server 2008) I had no input returned till the script had completed.
this combination finally worked for me, based on thomasrutter's answer
while (ob_get_level()) ob_end_flush();
ob_implicit_flush(true);

Categories