continue processing php after sending http response

81,681

Solution 1

Yes. You can do this:

ignore_user_abort(true);//not required
set_time_limit(0);

ob_start();
// do initial processing here
echo $response; // send the response
header('Connection: close');
header('Content-Length: '.ob_get_length());
ob_end_flush();
@ob_flush();
flush();
fastcgi_finish_request();//required for PHP-FPM (PHP > 5.3.3)

// now the request is sent to the browser, but the script is still running
// so, you can continue...

die(); //a must especially if set_time_limit=0 is used and the task ends

Solution 2

I've seen a lot of responses on here that suggest using ignore_user_abort(true); but this code is not necessary. All this does is ensure your script continues executing before a response is sent in the event that the user aborts (by closing their browser or pressing escape to stop the request). But that's not what you're asking. You're asking to continue execution AFTER a response is sent. All you need is the following:

// Buffer all upcoming output...
ob_start();

// Send your response.
echo "Here be response";

// Get the size of the output.
$size = ob_get_length();

// Disable compression (in case content length is compressed).
header("Content-Encoding: none");

// Set the content length of the response.
header("Content-Length: {$size}");

// Close the connection.
header("Connection: close");

// Flush all output.
ob_end_flush();
@ob_flush();
flush();

// Close current session (if it exists).
if(session_id()) session_write_close();

// Start your background work here.
...

If you're concerned that your background work will take longer than PHP's default script execution time limit, then stick set_time_limit(0); at the top.

Solution 3

If you're using FastCGI processing or PHP-FPM, you can:

session_write_close(); //close the session
ignore_user_abort(true); //Prevent echo, print, and flush from killing the script
fastcgi_finish_request(); //this returns 200 to the user, and processing continues

// do desired processing ...
$expensiveCalulation = 1+1;
error_log($expensiveCalculation);

Source: https://www.php.net/manual/en/function.fastcgi-finish-request.php

PHP issue #68722: https://bugs.php.net/bug.php?id=68772

Solution 4

I spent a few hours on this issue and I have come with this function which works on Apache and Nginx:

/**
 * respondOK.
 */
protected function respondOK()
{
    // check if fastcgi_finish_request is callable
    if (is_callable('fastcgi_finish_request')) {
        /*
         * This works in Nginx but the next approach not
         */
        session_write_close();
        fastcgi_finish_request();

        return;
    }

    ignore_user_abort(true);

    ob_start();
    $serverProtocole = filter_input(INPUT_SERVER, 'SERVER_PROTOCOL', FILTER_SANITIZE_STRING);
    header($serverProtocole.' 200 OK');
    header('Content-Encoding: none');
    header('Content-Length: '.ob_get_length());
    header('Connection: close');

    ob_end_flush();
    ob_flush();
    flush();
}

You can call this function before your long processing.

Solution 5

Modified the answer by @vcampitelli a bit. Don't think you need the close header. I was seeing duplicate close headers in Chrome.

<?php

ignore_user_abort(true);

ob_start();
echo '{}';
header($_SERVER["SERVER_PROTOCOL"] . " 202 Accepted");
header("Status: 202 Accepted");
header("Content-Type: application/json");
header('Content-Length: '.ob_get_length());
ob_end_flush();
ob_flush();
flush();

sleep(10);
Share:
81,681
user1700214
Author by

user1700214

Updated on December 03, 2021

Comments

  • user1700214
    user1700214 over 2 years

    My script is called by server. From server I'll receive ID_OF_MESSAGE and TEXT_OF_MESSAGE.

    In my script I'll handle incoming text and generate response with params: ANSWER_TO_ID and RESPONSE_MESSAGE.

    The problem is that I'm sending response to incomming "ID_OF_MESSAGE", but server which send me message to handle will set his message as delivered to me (It means I can send him response to that ID), after receiving http response 200.

    One of solution is to save message to database and make some cron which will be running each minute, but I need to generate response message immediately.

    Is there some solution how to send to server http response 200 and than continue executing php script?

    Thank you a lot

  • Congelli501
    Congelli501 almost 10 years
    Is it possible to do it with a keep-alive connection ?
  • Martin_Lakes
    Martin_Lakes almost 10 years
    Excellent!! This is the only response to this question that actually works!!! 10p+
  • CJ Dennis
    CJ Dennis almost 10 years
    Awesome answer! The only thing I changed was set_time_limit(0);. You do probably want it to run for longer than the default 30 seconds, but indefinitely could cause problems if it goes into an infinite loop! I have a longer value set in my php.ini file.
  • ars265
    ars265 about 9 years
    is there a reason you use ob_flush after ob_end_flush? I understand the need for the flush function there at the end but I'm not sure why you would need ob_flush with ob_end_flush being called.
  • Brian
    Brian about 9 years
    Please note that if a content-encoding header is set to anything else than 'none' it could render this example useless as it would still let the user wait the full execution time (until timeout?). So to be absolutely sure it will work local ánd in the production environment, set the 'content-encoding' header to 'none': header("Content-Encoding: none")
  • vcampitelli
    vcampitelli almost 9 years
    Tip: I started using PHP-FPM, so I had to add fastcgi_finish_request() at the end
  • Nate Lampton
    Nate Lampton almost 9 years
    RE: Keep-alive connection: You do not necessarily need to have close the connection, but then what will happen is the next asset requested on the same connection will be forced to wait. So you could deliver the HTML fast but then one of your JS or CSS files might load slowly, as the connection has to finish getting the response from PHP before it can get the next asset. So for that reason, closing the connection is a good idea so the browser doesn't have to wait for it to be freed up.
  • Nate Lampton
    Nate Lampton almost 9 years
    I mentioned this in the original answer, but I'll say it here too. You do not necessarily need to have close the connection, but then what will happen is the next asset requested on the same connection will be forced to wait. So you could deliver the HTML fast but then one of your JS or CSS files might load slowly, as the connection has to finish getting the response from PHP before it can get the next asset. So for that reason, closing the connection is a good idea so the browser doesn't have to wait for it to be freed up.
  • fisk
    fisk over 8 years
    This is not what is being asked for - this function basically just extends the script termination event and is still part of the output buffer.
  • Martin_Lakes
    Martin_Lakes almost 8 years
    Tried a LOT of different combinations, THIS is the one that works!!! Thanks Kosta Kontos!!!
  • Delcon
    Delcon almost 8 years
    'ob_end_flush(); ob_flush();' produces a warning that there is nothing to flush. PHP documentation about 'ob_flush();' states: This function does not destroy the output buffer like ob_end_flush() does.
  • Trendfischer
    Trendfischer almost 8 years
    Found it worth an upvote, cause it shows what does not work.
  • Ehsan
    Ehsan over 7 years
    Thanks for this, after spending a few hours this worked for me in nginx
  • FBP
    FBP over 6 years
    why are both ob_end_flush and ob_flush needed. This is causing me issues when the same client calls another api. In the second api it only works if I add ob_start before initializing my variable initialization that is getting echoed
  • lhermann
    lhermann about 6 years
    After ob_end_flush();, ob_flush(); is not needed any more. In fact, it will cause an error. Just ob_end_flush(); is enough.
  • David Okwii
    David Okwii about 6 years
    Excellent answer. Works.
  • Sleepybear
    Sleepybear almost 6 years
    This was so helpful to me! Worked great for my Slack Bot slash command integration where I can send the response immediately to let them know it was received, then start working on calling other APIs and parsing data.
  • Meysam Valueian
    Meysam Valueian almost 6 years
    This code may return empty response. I have used that and sometimes a white screen appears. I have moved header('Connection: close'); after flush() and it is OK now.
  • Kevin Borders
    Kevin Borders over 5 years
    Note: this will not work if there are any other ob_start() calls in your code that have not been terminated with ob_end_flush(). To guarantee all buffers are flushed and ended, you can do: while (ob_get_level() > 0) { ob_end_flush(); }.
  • spice
    spice over 5 years
    This is bloody lovely! After trying everything else above this is the only thing that worked with nginx.
  • KyleBunga
    KyleBunga over 5 years
    Works perfectly on apache 2, php 7.0.32 and ubuntu 16.04! Thanks!
  • Accountant م
    Accountant م over 5 years
    Your code is almost exactly the same as the code here but your post is older :) +1
  • Accountant م
    Accountant م over 5 years
    be careful with filter_input function it returns NULL sometimes. see this user contribution for detail
  • Siniša
    Siniša over 4 years
    I've tried other solutions, and only this one worked for me. Order of lines is important as well.
  • HappyDog
    HappyDog over 4 years
    Ditto - upvoting because I was expecting to see this as an answer, and useful to see that it doesn't work.
  • Siniša
    Siniša over 4 years
    Thanks DarkNeuron! Great answer for us using php-fpm, just solved my problem!
  • marcvangend
    marcvangend about 4 years
    This only started working for me once the size of $response was sufficiently large, so I changed echo $response; to echo str_pad($response, 4096); to force a minimum size. (Background: I need to return an HTTP 200 to a web service within 3 seconds to acknowledge the POST request has been received. Technically the response doesn't have to contain any content.)
  • Russell G
    Russell G over 3 years
    ob_start() is only needed if you don't know the length of the output. If you do, you can skip all of the ob_* calls and just set the headers, print the output, and call flush(). The browser will then display the page and your script can continue executing. set_time_limit() is only needed if your script will run longer than max_execution_time in the php.ini file, which is 30 sec. by default.
  • Russell G
    Russell G over 3 years
    @marcvangend: Thanks for the tip about str_pad()! I added a call to ini_get("output_buffering") to get the buffer size in case it's not set to the default 4096.
  • D Durham
    D Durham over 3 years
    Brilliant! I just spent hours trying to figure out why our app stopped working on a page where we used output buffering after moving it to a new server and this was the key. Thanks!
  • Fil
    Fil about 3 years
    What version of php and what operating system are you using? Maybe something has changed with recent versions of php...
  • Eaten by a Grue
    Eaten by a Grue almost 3 years
    @lhermann - I think it depends on server. I tried every combination of those three calls and the only thing that worked for me is having all three. As such, I'm restoring the original answer it's broken (for me) after the recent edit.
  • Eaten by a Grue
    Eaten by a Grue almost 3 years
    I can't for the life of me understand why ob_flush() is required here since it throws a notice PHP Notice: ob_flush(): failed to flush buffer. No buffer to flush in php shell code on line 1. Oddly, this doesn't work without it. I guess it's doing something after all...
  • Sebi2020
    Sebi2020 almost 3 years
    @billynoah I think it's not needed. ob_end_flush() does the same as ob_flush except that it closes the buffer (therefore calling ob_flush() after ob_end_flush() produces the warning you came across.
  • Eaten by a Grue
    Eaten by a Grue almost 3 years
    @Sebi2020 - the whole routine doesn't work for me without it (php 7.4.3) - which is what makes it all the more confusing. There must be something going on behind the scenes in PHP beyond what's documented.
  • Thomas Harris
    Thomas Harris over 2 years
    Could someone point me to what end; does. I could only find end(); function in PHP manual which appears to be different.
  • vcampitelli
    vcampitelli over 2 years
    I don't know why @hexalys wrote this. Maybe he was trying to go for exit / die instead of end, as it doesn't exist. Only the end() function, as you already realized. I've edited the question again and replaced it with a die. Thanks!
  • hexalys
    hexalys over 2 years
    @vcampitelli Indeed I meant exit; I usually use die() myself. Thanks for the correction. However you do not need @ob_flush(); ob_end_flush(); already implies ob_flush();
  • PaulCrp
    PaulCrp over 2 years
    Simple and efficient, the code we like. Thanks
  • Zackattack
    Zackattack over 2 years
    why do you put the header("Connection: close"); before the output? everyone seems to have it after. does it not matter or is there an actually effect?
  • Fil
    Fil over 2 years
    @Zackattack, if you're referring to the last echo message, it's only to check that the script continues its execution but for the client is already ended (thanks to the headers). After I ensured the last echo message doesn't appear on browser, I removed sleep and echo instructions in my script to perform the actual processing. If you want to show something for the user (that's the real output), you have to insert instructions between ob_start and ob_get_length. The order among the header instructions doesn't matter.
  • Zackattack
    Zackattack over 2 years
    thanks for that. so I can put header("Connection: close"); after ob_start(); as well?
  • Fil
    Fil over 2 years
    From php documentation on ob_start instruction: "While output buffering is active no output is sent from the script (other than headers)", so it doesn't change anything... I would put it before ob_start() just to not mistakenly think the header is buffered too as the positioning seems to imply. But it will work anyway even after ob_start() if you prefer.
  • jlh
    jlh about 2 years
    In my case I didn't need ANY of those flushing functions or any manually set headers. I just do session_write_close() and fastcgi_finish_request(). That's all I needed. fastcgi_finish_request() is even documented to already flush automatically.