How to flush data to browser but continue executing
Asked Answered
O

7

22

I have a ob_start() and a corresponding ob_flush(). I would like to flush a portion of data and continue executing the rest. Using ob_flush() didn't help. Also if possible rest needs to happen without showing loading in browser.

EDIT:

I don't want to use ajax

Organize answered 14/5, 2012 at 7:28 Comment(2)
After you do the first ob_flush, do you want other output to be shown? Or do you want the request to be completely finished as far as the client can tell and then some kind of processing continue in the background?Domingadomingo
All you need is flush() if your output buffering is already turned off in php.ini, however to make this work you need to configure Apache, NginX, etc.Fragrance
D
16

ob_flush writes the buffer. In other words, ob_flush tells PHP to give Apache (or nginx/lighttpd/whatever) the output and then for PHP to forget about it. Once Apache has the output, it does whatever it wants with it. (In other words, after ob_flush it's out of your control whether or not it gets immediately written to the browser).

So, short answer: There's no guaranteed way to do that.

Just a guess, you're likely looking for AJAX. Whenever people are trying to manipulate when page content loads as you're doing, AJAX is almost always the correct path.

If you want to continue a task in the background, you can use ignore_user_abort, as detailed here, however, that is often not the optimal approach. You essentially lose control over that thread, and in my opinion, a web server thread is not where heavy processing belongs.

I would try to extract it out of the web facing stuff. This could mean a cron entry or just spawning a background process from inside of PHP (a process that though started from inside of script execution will not die with the script, and the script will not wait for it to finish before dying).

If you do go that route, it will mean that you can even make some kind of status system if necessary. Then you could monitor the execution and give the user periodic updates on the progress. (Technically you could make a status system with a ignore_user_abort-ed script too, but it doesn't seem as clean to me.)

Domingadomingo answered 14/5, 2012 at 7:32 Comment(1)
when a new user logs in i want to save a lot a data. This data should get saved immediately but not interrupt the user i.e show loading on browserOrganize
M
18

I have done this in the past and this is how I solved it:

ob_start();

/*
 * Generate your output here
 */ 

// Ignore connection-closing by the client/user
ignore_user_abort(true);

// Set your timelimit to a length long enough for your script to run, 
// but not so long it will bog down your server in case multiple versions run 
// or this script get's in an endless loop.
if ( 
     !ini_get('safe_mode') 
     && strpos(ini_get('disable_functions'), 'set_time_limit') === FALSE 
){
    set_time_limit(60);
}

// Get your output and send it to the client
$content = ob_get_contents();         // Get the content of the output buffer
ob_end_clean();                      // Close current output buffer
$len = strlen($content);             // Get the length
header('Connection: close');         // Tell the client to close connection
header("Content-Length: $len");     // Close connection after $len characters
echo $content;                       // Output content
flush();                             // Force php-output-cache to flush to browser.
                                     // See caveats below.

// Optional: kill all other output buffering
while (ob_get_level() > 0) {
    ob_end_clean();
}

As I said in a couple of comments before, you should watch out for gzipping your content, since that will alter the length of your content, but not change the header about it. It also can buffer your output, so it won't get send to the client instantly.
You could try letting apache know to not gzip your content by using apache_setenv('no-gzip', '1');. But this will not work if you use rewrite-rules to go to your page, since then it will also modify those environment variables. At least, it did so for me.

See more caveats about flushing your content to the user in the manual.

Melody answered 14/5, 2012 at 9:15 Comment(1)
Just an FYI the PHP function is ob_get_contentSExpert
D
16

ob_flush writes the buffer. In other words, ob_flush tells PHP to give Apache (or nginx/lighttpd/whatever) the output and then for PHP to forget about it. Once Apache has the output, it does whatever it wants with it. (In other words, after ob_flush it's out of your control whether or not it gets immediately written to the browser).

So, short answer: There's no guaranteed way to do that.

Just a guess, you're likely looking for AJAX. Whenever people are trying to manipulate when page content loads as you're doing, AJAX is almost always the correct path.

If you want to continue a task in the background, you can use ignore_user_abort, as detailed here, however, that is often not the optimal approach. You essentially lose control over that thread, and in my opinion, a web server thread is not where heavy processing belongs.

I would try to extract it out of the web facing stuff. This could mean a cron entry or just spawning a background process from inside of PHP (a process that though started from inside of script execution will not die with the script, and the script will not wait for it to finish before dying).

If you do go that route, it will mean that you can even make some kind of status system if necessary. Then you could monitor the execution and give the user periodic updates on the progress. (Technically you could make a status system with a ignore_user_abort-ed script too, but it doesn't seem as clean to me.)

Domingadomingo answered 14/5, 2012 at 7:32 Comment(1)
when a new user logs in i want to save a lot a data. This data should get saved immediately but not interrupt the user i.e show loading on browserOrganize
I
5

this is my function

function bg_process($fn, $arr) {
    $call = function($fn, $arr){
        header('Connection: close');
        header('Content-length: '.ob_get_length());
        ob_flush();
        flush();
        call_user_func_array($fn, $arr);
        };
    register_shutdown_function($call, $fn, $arr);
    }

wrap the function to be executed in the end, after php close the connection. and of course the browser will stop buffering.

function test() {
    while (true) {
        echo 'this text will never seen by user';
        }
    }

this is how to call the function

bg_process('test'); 

first argument is callable, second argument is an array to be passed to 'test' function with an indexed array

Note : I don't use ob_start() at the beginning of the script.

Instrumentation answered 17/5, 2012 at 6:37 Comment(0)
G
2

I have an article explaining how this can be achieved using apache/mod_php on my blog here: http://codehackit.blogspot.com/2011/07/how-to-kill-http-connection-and.html Hope this helps, cheers

Gropius answered 14/5, 2012 at 7:50 Comment(1)
It would be helpful if you added a little snippet of how it works here, so in case your site ever goes down, we still have the answer here. The code you wrote in your blogpost works. However, if you told apache before to gzip your content, it will no longer work (since length won't be correct anymore)Melody
Q
1

If you are using PHP-FPM:

ignore_user_abort(true);
fastcgi_finish_request();

Above two functions are the key factors which ignore_user_abort prevents error and fastcgi_finish_request closes client connection.

Quaternity answered 28/7, 2018 at 8:37 Comment(0)
F
0

fastcgi_finish_request

This function flushes all response data to the client and finishes the request. This allows for time consuming tasks to be performed without leaving the connection to the client open.

not working on Apache.(PHP 5 >= 5.3.3, PHP 7)

Factious answered 25/5, 2018 at 0:31 Comment(0)
E
-1

Use:

header("Content-Length: $len");

..where $len is the length of the data to be flushed to the client.

I don't have the background to know when and where this is going to work, but I tried on a few browsers, and all returned instantly with:

<?PHP 
    header("Content-length:5");
    echo "this is more than 5";
    sleep(5);
?>

edit: Chrome, IE, and Opera showed this, while FireFox showed this is more than 5. All of them closed the request after that though.

Earflap answered 14/5, 2012 at 7:53 Comment(6)
That's why the content-length needs to be of equal length as the actual content. And beware of gzip, since that will modify the length of your response.Melody
@Zombaya, I don't see how that's relevant though. So long as he's not trying to cut off his output after a certain amount, this should work to end the request after sending the necessary data.Earflap
Well, when I tried doing it and the length did not match the actual length, the connection would not always close and keep on loading.Melody
@Zombaya, Why wouldn't the length match the actual length? Why couldn't he just do header("Content-Length: ".strlen($content));Earflap
That works normally, but if you configured apache to use gzip-compression on your outputted webpages, it modifies the content of your response, but does not modify the content-length-header. I had a bit of trouble with that in the past.Melody
@Zombaya, Oh I see, that makes sense. I imagine there's some sort of http magic that would have a similar effect though.Earflap

© 2022 - 2024 — McMap. All rights reserved.