Continue PHP execution after sending HTTP response

PhpFork

Php Problem Overview


How can I have PHP 5.2 (running as apache mod_php) send a complete HTTP response to the client, and then keep executing operations for one more minute?

The long story:

I have a PHP script that has to execute a few long database requests and send e-mail, which takes 45 to 60 seconds to run. This script is called by an application that I have no control over. I need the application to report any error messages received from the PHP script (mostly invalid parameter errors).

The application has a timeout delay shorter than 45 seconds (I do not know the exact value) and therefore registers every execution of the PHP script as an error. Therefore, I need PHP to send the complete HTTP response to the client as fast as possible (ideally, as soon as the input parameters have been validated), and then run the database and e-mail processing.

I'm running mod_php, so pcntl_fork is not available. I could work my way around this by saving the data to be processed to the database and run the actual process from cron, but I'm looking for a shorter solution.

Php Solutions


Solution 1 - Php

I had this snippet in my "special scripts" toolbox, but it got lost (clouds were not common back then), so I was searching for it and came up with this question, surprised to see that it's missing, I searched more and came back here to post it:

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

I actually use it in few places. And it totally makes sense there: a banklink is returning the request of a successful payment and I have to call a lot of services and process a lot of data when that happens. That sometimes takes more than 10 seconds, yet the banklink has fixed timeout period. So I acknowledge the banklink and show him the way out, and do my stuff when he is already gone.

Solution 2 - Php

Have the script that handles the initial request create an entry in a processing queue, and then immediately return. Then, create a separate process (via cron maybe) that regularly runs whatever jobs are pending in the queue.

Solution 3 - Php

What you need is this kind of setup

alt text

Solution 4 - Php

One can to use "http fork" to oneself or any other script. I mean something like this:

// parent sript, called by user request from browser

// create socket for calling child script
$socketToChild = fsockopen("localhost", 80);

// HTTP-packet building; header first
$msgToChild = "POST /sript.php?&param=value&<more params> HTTP/1.0\n";
$msgToChild .= "Host: localhost\n";
$postData = "Any data for child as POST-query";
$msgToChild .= "Content-Length: ".strlen($postData)."\n\n";

// header done, glue with data
$msgToChild .= $postData;

// send packet no oneself www-server - new process will be created to handle our query
fwrite($socketToChild, $msgToChild);

// wait and read answer from child
$data = fread($socketToChild, $dataSize);

// close connection to child
fclose($socketToChild);
...

Now the child script:

// parse HTTP-query somewhere and somehow before this point

// "disable partial output" or 
// "enable buffering" to give out all at once later
ob_start();

// "say hello" to client (parent script in this case) disconnection
// before child ends - we need not care about it
ignore_user_abort(1);

// we will work forever
set_time_limit(0);

// we need to say something to parent to stop its waiting
// it could be something useful like client ID or just "OK"
...
echo $reply;

// push buffer to parent
ob_flush();

// parent gets our answer and disconnects
// but we can work "in background" :)
...

The main idea is:

  • parent script called by user request;
  • parent calls child script (same as parent or another) on the same server (or any other server) and gives request data to them;
  • parent says ok to user and ends;
  • child works.

If you need to interact with child - you can use DB as "communication medium": parent may read child status and write commands, child may read commands and write status. If you need that for several child scripts - you should keep child id on the user side to discriminate them and send that id to parent each time you want to check status of respective child.

I've found that here - http://linuxportal.ru/forums/index.php/t/22951/

Solution 5 - Php

What about calling a script on the file server to execute as if it had been triggered at the command line? You can do this with PHP's exec.

Solution 6 - Php

You can use the PHP function register-shutdown-function that will execute something after the script has completed its dialog with the browser.

See also ignore_user_abort - but you shouldn't need this function if you use the register_shutdown_function. On the same page, set_time_limit(0) will prevent your script to time out.

Solution 7 - Php

Using a queue, exec or cron would be an overkill to this simple task. There is no reason not to stay within the same script. This combination worked great for me:

		ignore_user_abort(true);
		$response = "some response"; 
		header("Connection: close");
		header("Content-Length: " . mb_strlen($response));
		echo $response;
		flush(); // releasing the browser from waiting
        // continue the script with the slow processing here...

		
	

read more in: https://stackoverflow.com/questions/1481247/how-to-continue-process-after-responding-to-ajax-request-in-php

Solution 8 - Php

It is possible to use cURL for that, with a very short timeout. This would be your main file:

<?php>
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, "http://example.com/processor.php");
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_TIMEOUT_MS, 10);      //just some very short timeout
    curl_exec($ch);
    curl_close($ch);
?>

And this your processor file:

<?php
    ignore_user_abort(true);                       //very important!
	for($x = 0; $x < 10; $x++)                     //do some very time-consuming task
		sleep(10);
?>

As you can see, the upper script will timeout after a short time (10 milliseconds in this case). It is possible that CURLOPT_TIMEOUT_MS will not work like this, in this case, it would be equivalent to curl_setopt($ch, CURLOPT_TIMEOUT, 1).

So when the processor file has been accessed, it will do its tasks no matter that the user (i.e. the calling file) aborts the connection.

Of course you can also pass GET or POST parameters between the pages.

Solution 9 - Php

You can create an http request between server and server. (not browser is needed). The secret to create a background http request is setting a very small timeout, so the response is ignored.

This is a working function that I have used for that pupose:

MAY 31 PHP asynchronous background request Another way to create an asynchronous request in PHP (simulating background mode).

 /**
  * Another way to make asyncronous (o como se escriba asincrono!) request with php
  * Con esto se puede simpular un fork en PHP.. nada que envidarle a javita ni C++
  * Esta vez usando fsockopen
  * @author PHPepe
  * @param  unknown_type $url
  * @param  unknown_type $params
  */
 function phpepe_async($url, $params = array()) {
  $post_params = array(); 
     foreach ($params as $key => &$val) {
       if (is_array($val)) $val = implode(',', $val);
         $post_params[] = $key.'='.urlencode($val);
     }
     $post_string = implode('&', $post_params);
 
     $parts=parse_url($url);
 
     $fp = fsockopen($parts['host'],
         isset($parts['port'])?$parts['port']:80,
         $errno, $errstr, 30);
 
     $out = "POST ".$parts['path']." HTTP/1.1\r\n";
     $out.= "Host: ".$parts['host']."\r\n";
     $out.= "Content-Type: application/x-www-form-urlencoded\r\n";
     $out.= "Content-Length: ".strlen($post_string)."\r\n";
     $out.= "Connection: Close\r\n\r\n";
     if (isset($post_string)) $out.= $post_string;
 
     fwrite($fp, $out);
     fclose($fp);
 }
 
 // Usage:
 phpepe_async("http://192.168.1.110/pepe/feng_scripts/phprequest/fork2.php");

For more info you can take a look at http://www.phpepe.com/2011/05/php-asynchronous-background-request.html

Solution 10 - Php

Bah, I misunderstood your requirements. Looks like they're actually:

  • Script receives input from an external source you do not control
  • Script processes and validates the input, and lets the external app know if they're good or not and terminates the session.
  • Script kicks off a long-running proccess.

In this case, then yes, using an outside job queue and/or cron would work. After the input is validated, insert the job details into the queue, and exit. Another script can then run, pick up the job details from the queue, and kick off the longer process. Alex Howansky has the right idea.

Sorry, I admit I skimmed a bit the first time around.

Solution 11 - Php

You can split these functions into three scripts.

  1. Initiate process and call second one via exec or command, this is also possible to run via http call.
  2. second one will run database processing and at the end will start last one
  3. last one will email

Solution 12 - Php

I would recommend spawning a new async request at the end, rather than continuing the process with the user.

You can spawn the other request using the answer here: https://stackoverflow.com/questions/124462/asynchronous-php-calls/2924987#2924987

Solution 13 - Php

In your Apache php.ini config file, make sure that output buffering is disabled:

output_buffering = off

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
QuestionVictor NicolletView Question on Stackoverflow
Solution 1 - PhppovilaspView Answer on Stackoverflow
Solution 2 - PhpAlex HowanskyView Answer on Stackoverflow
Solution 3 - PhpYanick RochonView Answer on Stackoverflow
Solution 4 - PhpSomeGuyView Answer on Stackoverflow
Solution 5 - PhpSorcyCatView Answer on Stackoverflow
Solution 6 - PhpDéjà vuView Answer on Stackoverflow
Solution 7 - PhpNir O.View Answer on Stackoverflow
Solution 8 - PhpsigalorView Answer on Stackoverflow
Solution 9 - PhpIgnacio VazquezView Answer on Stackoverflow
Solution 10 - PhpRyan ChouinardView Answer on Stackoverflow
Solution 11 - PhpAramView Answer on Stackoverflow
Solution 12 - PhpBrentView Answer on Stackoverflow
Solution 13 - PhpLuan CostaView Answer on Stackoverflow