continue processing php after sending http response
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);
user1700214
Updated on December 03, 2021Comments
-
user1700214 over 2 years
My script is called by server. From server I'll receive
ID_OF_MESSAGE
andTEXT_OF_MESSAGE
.In my script I'll handle incoming text and generate response with params:
ANSWER_TO_ID
andRESPONSE_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 almost 10 yearsIs it possible to do it with a keep-alive connection ?
-
Martin_Lakes almost 10 yearsExcellent!! This is the only response to this question that actually works!!! 10p+
-
CJ Dennis almost 10 yearsAwesome 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 myphp.ini
file. -
ars265 about 9 yearsis 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 about 9 yearsPlease 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 almost 9 yearsTip: I started using PHP-FPM, so I had to add
fastcgi_finish_request()
at the end -
Nate Lampton almost 9 yearsRE: 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 almost 9 yearsI 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 over 8 yearsThis 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 almost 8 yearsTried a LOT of different combinations, THIS is the one that works!!! Thanks Kosta Kontos!!!
-
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 almost 8 yearsFound it worth an upvote, cause it shows what does not work.
-
Ehsan over 7 yearsThanks for this, after spending a few hours this worked for me in nginx
-
FBP over 6 yearswhy 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 about 6 yearsAfter
ob_end_flush();
,ob_flush();
is not needed any more. In fact, it will cause an error. Justob_end_flush();
is enough. -
David Okwii about 6 yearsExcellent answer. Works.
-
Sleepybear almost 6 yearsThis 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 almost 6 yearsThis code may return empty response. I have used that and sometimes a white screen appears. I have moved
header('Connection: close');
afterflush()
and it is OK now. -
Kevin Borders over 5 yearsNote: this will not work if there are any other
ob_start()
calls in your code that have not been terminated withob_end_flush()
. To guarantee all buffers are flushed and ended, you can do:while (ob_get_level() > 0) { ob_end_flush(); }
. -
spice over 5 yearsThis is bloody lovely! After trying everything else above this is the only thing that worked with nginx.
-
KyleBunga over 5 yearsWorks perfectly on apache 2, php 7.0.32 and ubuntu 16.04! Thanks!
-
Accountant م over 5 yearsYour code is almost exactly the same as the code here but your post is older :) +1
-
Accountant م over 5 yearsbe careful with
filter_input
function it returns NULL sometimes. see this user contribution for detail -
Siniša over 4 yearsI've tried other solutions, and only this one worked for me. Order of lines is important as well.
-
HappyDog over 4 yearsDitto - upvoting because I was expecting to see this as an answer, and useful to see that it doesn't work.
-
Siniša over 4 yearsThanks DarkNeuron! Great answer for us using php-fpm, just solved my problem!
-
marcvangend about 4 yearsThis only started working for me once the size of
$response
was sufficiently large, so I changedecho $response;
toecho 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 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 theob_*
calls and just set the headers, print the output, and callflush()
. 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 thanmax_execution_time
in the php.ini file, which is 30 sec. by default. -
Russell G over 3 years@marcvangend: Thanks for the tip about
str_pad()
! I added a call toini_get("output_buffering")
to get the buffer size in case it's not set to the default 4096. -
D Durham over 3 yearsBrilliant! 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 about 3 yearsWhat version of php and what operating system are you using? Maybe something has changed with recent versions of php...
-
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 almost 3 yearsI can't for the life of me understand why
ob_flush()
is required here since it throws a noticePHP 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 almost 3 years@billynoah I think it's not needed.
ob_end_flush()
does the same asob_flush
except that it closes the buffer (therefore callingob_flush()
afterob_end_flush()
produces the warning you came across. -
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 over 2 yearsCould someone point me to what
end;
does. I could only findend();
function in PHP manual which appears to be different. -
vcampitelli over 2 yearsI don't know why @hexalys wrote this. Maybe he was trying to go for
exit
/die
instead ofend
, as it doesn't exist. Only theend()
function, as you already realized. I've edited the question again and replaced it with adie
. Thanks! -
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 over 2 yearsSimple and efficient, the code we like. Thanks
-
Zackattack over 2 yearswhy 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 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 over 2 yearsthanks for that. so I can put
header("Connection: close");
afterob_start();
as well? -
Fil over 2 yearsFrom 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 beforeob_start()
just to not mistakenly think the header is buffered too as the positioning seems to imply. But it will work anyway even afterob_start()
if you prefer. -
jlh about 2 yearsIn my case I didn't need ANY of those flushing functions or any manually set headers. I just do
session_write_close()
andfastcgi_finish_request()
. That's all I needed.fastcgi_finish_request()
is even documented to already flush automatically.