continue processing php after sending http response

PhpHttpResponse

Php Problem Overview


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

Php Solutions


Solution 1 - Php

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 - Php

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 - Php

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 - Php

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 - Php

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

Solution 6 - Php

I asked this question to Rasmus Lerdorf in April 2012, citing these articles:

I suggested the development of a new PHP built-in function to notify the platform that no further output (on stdout?) will be generated (such a function might take care of closing the connection). Rasmus Lerdorf responded:

> See Gearman. You really really don't want your frontend Web servers doing backend processing like this.

I can see his point, and support his opinion for some applications/ loading scenarios! However, under some other scenarios, the solutions from vcampitelli et al, are good ones.

Solution 7 - Php

I use the php function register_shutdown_function for this.

void register_shutdown_function ( callable $callback [, mixed $parameter [, mixed $... ]] )

http://php.net/manual/en/function.register-shutdown-function.php

Edit: The above is not working. It seems I was misled by some old documentation. The behaviour of register_shutdown_function has changed since PHP 4.1 link link

Solution 8 - Php

I can't install pthread and neither the previous solutions work for me. I found only the following solution to work (ref: https://stackoverflow.com/a/14469376/1315873):

<?php
ob_end_clean();
header("Connection: close");
ignore_user_abort(); // optional
ob_start();
echo ('Text the user will see');
$size = ob_get_length();
header("Content-Length: $size");
ob_end_flush(); // Strange behaviour, will not work
flush();            // Unless both are called !
session_write_close(); // Added a line suggested in the comment
// Do processing here 
sleep(30);
echo('Text user will never see');
?>

Solution 9 - Php

I have something that can compressed and send the response and let other php code to execute.

function sendResponse($response){
	$contentencoding = 'none';
	if(ob_get_contents()){
		ob_end_clean();
		if(ob_get_contents()){
			ob_clean();
		}
	}
	header('Connection: close');
	header("cache-control: must-revalidate");
	header('Vary: Accept-Encoding');
	header('content-type: application/json; charset=utf-8');
	ob_start();
	if(phpversion()>='4.0.4pl1' && extension_loaded('zlib') && GZIP_ENABLED==1 && !empty($_SERVER["HTTP_ACCEPT_ENCODING"]) && (strpos($_SERVER["HTTP_ACCEPT_ENCODING"], 'gzip') !== false) && (strstr($GLOBALS['useragent'],'compatible') || strstr($GLOBALS['useragent'],'Gecko'))){
		$contentencoding = 'gzip';
		ob_start('ob_gzhandler');
	}
	header('Content-Encoding: '.$contentencoding);
	if (!empty($_GET['callback'])){
		echo $_GET['callback'].'('.$response.')';
	} else {
		echo $response;
	}
	if($contentencoding == 'gzip') {
		if(ob_get_contents()){
			ob_end_flush(); // Flush the output from ob_gzhandler
		}
	}
	header('Content-Length: '.ob_get_length());
	// flush all output
	if (ob_get_contents()){
		ob_end_flush(); // Flush the outer ob_start()
		if(ob_get_contents()){
			ob_flush();
		}
		flush();
	}
	if (session_id()) session_write_close();
}

Solution 10 - Php

in case of php file_get_contents use, connection close is not enough. php still wait for eof witch send by server.

my solution is to read 'Content-Length:'

here is sample :

response.php:

 <?php
		
ignore_user_abort(true);
set_time_limit(500);

ob_start();
echo 'ok'."\n";
header('Connection: close');
header('Content-Length: '.ob_get_length());
ob_end_flush();
ob_flush();
flush();
sleep(30);

Note the "\n" in response to close line, if not the fget read while wait eof.

read.php :

<?php
$vars = array(
    'hello' => 'world'
);
$content = http_build_query($vars);

fwrite($fp, "POST /response.php HTTP/1.1\r\n");
fwrite($fp, "Content-Type: application/x-www-form-urlencoded\r\n");
fwrite($fp, "Content-Length: " . strlen($content) . "\r\n");
fwrite($fp, "Connection: close\r\n");
fwrite($fp, "\r\n");

fwrite($fp, $content);

$iSize = null;
$bHeaderEnd = false;
$sResponse = '';
do {
    $sTmp = fgets($fp, 1024);
    $iPos = strpos($sTmp, 'Content-Length: ');
    if ($iPos !== false) {
        $iSize = (int) substr($sTmp, strlen('Content-Length: '));
    }
    if ($bHeaderEnd) {
        $sResponse.= $sTmp;
    }
    if (strlen(trim($sTmp)) == 0) {
        $bHeaderEnd = true;
    }
} while (!feof($fp) && (is_null($iSize) || !is_null($iSize) && strlen($sResponse) < $iSize));
$result = trim($sResponse);

As you can see this script dosent wait about eof if content length is reach.

hope it will help

Solution 11 - Php

There is another approach and its worthwhile considering if you don't want to tamper with the response headers. If you start a thread on another process the called function wont wait for its response and will return to the browser with a finalized http code. You will need to configure pthread.

class continue_processing_thread extends Thread 
{
     public function __construct($param1) 
     {
         $this->param1 = $param1
     }
        
     public function run() 
     {
        //Do your long running process here
     }
}
    
//This is your function called via an HTTP GET/POST etc
function rest_endpoint()
{
  //do whatever stuff needed by the response.

  //Create and start your thread. 
  //rest_endpoint wont wait for this to complete.
  $continue_processing = new continue_processing_thread($some_value);
  $continue_processing->start();

  echo json_encode($response)
}

Once we execute $continue_processing->start() PHP wont wait for the return result of this thread and therefore as far as rest_endpoint is considered. It is done.

Some links to help with pthreads

Good luck.

Solution 12 - Php

I have an important addition to all the other great answers!
TL;DR:
add

echo str_repeat(' ', 1024);



In my use case I would like to mark the API Call as "Accepted" and don't make the client wait for finished processing.

Actually it feels right: The client should stop waiting for answer when it receives the "Connection: close" header, but in fact at least MY php does not send those headers yet. (tested with two different PHP-Servers, and via Browser+Insomnia client each)

There is some special behaviour that flush() would not send the first content if not at least a special amount of bytes were echoed, yet (in my case 1024 bytes). (probably some hotfix in case there is some leading or trailing whitespace in php files which are actually treated like an echo statement or something like that which would prevent later header() statements to take effect.)

To solve the problem one can send 1024 leading whitespace characters which should be ignored by JSON/XML/HTML interpreters.

So the full code looks like this:

ob_start();
echo str_repeat(' ', 1024);
echo $response; // send the response
header('Connection: close');
header('Content-Length: '.ob_get_length());
ob_end_flush();
@ob_flush();
flush();

(Here is some backup for my arguments, cant find the correct source rn: https://stackoverflow.com/questions/3133209/how-to-flush-output-after-each-echo-call)

Solution 13 - Php

In addition to the answers, I returned a JSON string as response. I discovered the response is being truncated for unknown reason. The fix to the problem was adding extra space:

echo $json_response;
//the fix
echo str_repeat(' ', 10);

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionuser1700214View Question on Stackoverflow
Solution 1 - PhpvcampitelliView Answer on Stackoverflow
Solution 2 - PhpKosta KontosView Answer on Stackoverflow
Solution 3 - PhpDarkNeuronView Answer on Stackoverflow
Solution 4 - PhpEhsanView Answer on Stackoverflow
Solution 5 - PhpJustinView Answer on Stackoverflow
Solution 6 - PhpMatthew SlymanView Answer on Stackoverflow
Solution 7 - PhpmarttiView Answer on Stackoverflow
Solution 8 - PhpFilView Answer on Stackoverflow
Solution 9 - PhpMayur ShedageView Answer on Stackoverflow
Solution 10 - PhpJérome S.View Answer on Stackoverflow
Solution 11 - PhpJonathanView Answer on Stackoverflow
Solution 12 - PhpCharliexyxView Answer on Stackoverflow
Solution 13 - PhpWakeelView Answer on Stackoverflow