开发者

php flush not working

开发者 https://www.devze.com 2023-02-04 19:41 出处:网络
<?php for($i=0;$i<20;$i++) { echo \'printing...<br />\'; ob_flush(); flush(); usleep(300000); } ?>
<?php
for($i=0;$i<20;$i++)
{
    echo 'printing...<br />';
    ob_flush();
    flush();

    usleep(300000);
}

?>
开发者_如何转开发

Url that contains the code: http://domainsoutlook.com/sandbox/delayed.php

I have a dedicated server so I can make the changes. I am running apache and nginx as the proxy server.


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);
}


You're using ob_flush without ob_start, so there is nothing to flush for it.

It also depends on the webserver and proxy and its settings.

You should disable buffering for Nginx (add proxy_buffering off; to the config file and restart Nginx)

Also, check if your php.ini contains output_buffering = Off and zlib.output_compression = Off.


Main php file;

<?php
header('Content-Type: text/HTML; charset=utf-8');
header( 'Content-Encoding: none; ' );//disable apache compressed
session_start();
ob_end_flush();
ob_start();
set_time_limit(0);
error_reporting(0);

..... bla bla

for(each)........
{
   bla bla..
    echo "<br>>>>".$i."<<<br>";
    ob_flush();
    flush(); //ie working must

}
?>

it's working..


You have to fill the buffer, so that it can be flushed to browser. Use this after echo

echo str_pad('',4096)."\n";

Complete code:

<?php
     if (ob_get_level() == 0) ob_start();

     for( $i=0 ; $i<20 ; $i++) {
        echo 'printing...<br />';
        echo str_pad('',4096)."\n";

        ob_flush();
        flush();

        usleep(300000);
     }
     ob_end_flush();
?>


In php.ini:

output_buffering = Off
zlib.output_compression = Off

In nginx.conf:

fastcgi_keep_conn on; # < solution
proxy_buffering off;
gzip off;


As I read, it seems a really hard problem to solve, and the only (dirty) way I found is writing something useless to output to fill the ≠ buffers.

  • Without SSL
    • Without output_buffering, flush is needed.
      nginx buffers can be lowered until the PHP header size
    • With output_buffering, ob_flush need to be added to have the same behavior as above
  • With SSL
    • There is another buffer for SSL and NGX_SSL_BUFSIZE is fixed in nginx compilation

Here is my test.php file (call it with ?size=... to change space writing in the loop)

<!DOCTYPE html>
<html>
<head></head>
<body>
<?php
$vars = array('output_buffering', 'zlib.output_compression');
print('<p>');
foreach ($vars as $var) {
  print("$var : ");
  var_dump(ini_get($var));
  print('<br />');
}
print("ob_get_level() : " .ob_get_level());
print('</p>');
if (ob_get_level()) {
  $bytes = ob_get_length();
  ob_flush();
}

$nb_iterations = !empty($_GET['nb']) ? max(2, (int) $_GET['nb']) : 5;
$size = !empty($_GET['size']) ? $_GET['size'] : 0;

for ($i = 1; $i < $nb_iterations; $i++) {
  sleep(1);
  print(str_repeat(' ', 1024 * $size ));
  print("<p>wait $i s</p>");
  if (ob_get_level()) {
    $bytes += ob_get_length();
    print($bytes + strlen($bytes));
    ob_flush(); // this is working, results aren't depending on output_buffering value
  }
  flush(); // this is needed  
}
?>
</body>
</html>

And the lower conf I can set is

location ~ ^/test.php$ {
  gzip off;
  fastcgi_pass   unix:/var/run/php5-fpm/ssl.socket;
  fastcgi_param   QUERY_STRING            $query_string;  
  fastcgi_param   REQUEST_METHOD          $request_method;
  fastcgi_param   SCRIPT_FILENAME         $request_filename;   
  # if too low => upstream sent too big header while reading response header from upstream
  fastcgi_buffer_size 128; 
  fastcgi_buffers 2 128;  
  fastcgi_busy_buffers_size 128;
}


Another possible cause is mod_security. It looks like it has it's own buffers. So if you are using it you will have to set :

SecResponseBodyAccess Off

Kind of a dirty workaround but so far that it the only way I got it to work.


Just wanted to add to the Roger's answer.

If you are using FastCGI php5-fpm module within Apache2 you must also make sure you are adding

-flush

argument in your Apache2 configuration, i.e.

<IfModule mod_fastcgi.c>
...
        FastCgiExternalServer /usr/lib/cgi-bin/php5-fcgi -flush -socket /tmp/php5-fpm.sock -idle-timeout 480 -pass-header Authorization
</IfModule>


Check your server api with

echo phpinfo();

If you found your server api

Server API :  CGI/FastCGI

in CentOS then add this line in "/etc/httpd/conf.d/fcgid.conf"

OutputBufferSize 0

To test, restart your Apache server and try below code

ob_start();
for($i = 0; $i < 10; $i ++) {
    echo $i;
    echo '<br />';
    flush();
    ob_flush();
    sleep(1);
}


I have noticed that browsers react differently. Chrome, for example, holds on to the input forever, and doesn't seem to care about displaying it any earlier. Unsurprisingly, Firefox will display the input earlier, if the above tips (contributed by other stackoverflowers) are applied, so try with Firefox.


I was able to flush only this way - adding session_write_close();

if (ob_get_level() == 0)
{
    if(!ob_start("ob_gzhandler"))ob_start();
}               
echo ('bla bla bla');

$ans=ob_get_contents();
ob_end_clean();

header('Connection: close');
header('Content-Length: '.strlen($ans));
header('Status: 200');

echo $ans;

session_write_close();
ob_flush();
flush();


if(!ob_get_level()) ob_start();
echo json_encode(array('valid'=>true,'msg'=>'Flush occured.'));
$size = ob_get_length();
header("Content-Type: application/json");
// Set the content length of the response.
header("Content-Length: {$size}");
//Close the connection if you want to.
header("Connection: close");
// Flush all output.
ob_end_flush();
ob_flush();
flush();
// Close current session (if it exists).
if(session_id()) session_write_close();


I have tried many times when using php-fpm with nginx. Many answers just instruct:

In php.ini:

output_buffering = Off

zlib.output_compression = Off

In nginx.conf:

gzip off;

proxy_buffering off;

BUT they forgot the very important setting in nginx.conf:

fastcgi_keep_conn on;
0

精彩评论

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

关注公众号