发送HTTP响应后继续执行PHP

时间:2010-09-30 17:05:07

标签: php fork

如何让PHP 5.2(作为apache mod_php运行)向客户端发送完整的HTTP响应,然后继续执行操作一分钟?

长篇故事:

我有一个PHP脚本,必须执行一些长数据库请求并发送电子邮件,这需要45到60秒才能运行。该脚本由我无法控制的应用程序调用。我需要应用程序报告从PHP脚本收到的任何错误消息(主要是无效的参数错误)。

应用程序的超时延迟小于45秒(我不知道确切的值),因此将每次执行PHP脚本都记录为错误。因此,我需要PHP尽可能快地将完整的HTTP响应发送到客户端(理想情况下,只要输入参数已经过验证),然后运行数据库和电子邮件处理。

我正在运行mod_php,因此pcntl_fork不可用。我可以通过将要处理的数据保存到数据库并从cron运行实际过程来解决这个问题,但我正在寻找更短的解决方案。

13 个答案:

答案 0 :(得分:46)

我在“特殊脚本”工具箱中有这个片段,但它丢失了(当时云不常见),所以我正在搜索它并提出这个问题,惊讶地发现它丢失了,我搜索了更多,并回到这里发布它:

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

我实际上在很少的地方使用它。它完全有道理:银行链接正在返回成功支付的请求,我必须调用大量服务并在发生这种情况时处理大量数据。这有时需要10秒以上,但是banklink有固定的超时时间。因此,我承认银联并告诉他出路,并在他已经离开时做我的事情。

答案 1 :(得分:25)

让处理初始请求的脚本在处理队列中创建一个条目,然后立即返回。然后,创建一个单独的进程(可能通过cron),定期运行队列中待处理的任何作业。

答案 2 :(得分:7)

您需要的是这种设置

alt text

答案 3 :(得分:6)

可以对自己或任何其他脚本使用“http fork”。我的意思是这样的:

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

现在是儿童剧本:

// 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" :)
...

主要思想是:

  • 用户请求调用的父脚本;
  • parent在同一服务器(或任何其他服务器)上调用子脚本(与父节点或其他节点相同),并向它们提供请求数据;
  • 父母对用户说好并结束;
  • 孩子的工作。

如果您需要与孩子互动 - 您可以使用DB作为“通信媒介”:父母可以阅读孩子状态并写入命令,孩子可以阅读命令和写入状态。如果您需要多个子脚本 - 您应该在用户端保留子ID以区别对象,并在每次要检查相应子项的状态时将该ID发送给父级。

我在这里找到了 - http://linuxportal.ru/forums/index.php/t/22951/

答案 4 :(得分:5)

如果调用文件服务器上的脚本执行就像在命令行中触发一样?您可以使用PHP的exec

执行此操作

答案 5 :(得分:4)

您可以使用PHP函数register-shutdown-function,该函数将在脚本完成与浏览器的对话后执行

另请参阅ignore_user_abort - 但如果使用register_shutdown_function,则不需要此功能。在同一页面上,set_time_limit(0)会阻止您的脚本超时。

答案 6 :(得分:3)

使用队列,exec或cron对这个简单的任务来说太过分了。 没有理由不留在同一个脚本中。 这种组合对我很有用:

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

阅读更多内容: How to continue process after responding to ajax request in PHP?

答案 7 :(得分:2)

您可以在服务器和服务器之间创建http请求。 (不需要浏览器)。 创建后台http请求的秘诀是设置一个非常小的超时,因此忽略响应。

这是我用于该目的的一个功能函数:

MAY 31 PHP异步后台请求 在PHP中创建异步请求的另一种方法(模拟后台模式)。

 /**
  * 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");

有关详细信息,请查看 http://www.phpepe.com/2011/05/php-asynchronous-background-request.html

答案 8 :(得分:1)

可以使用cURL,超时很短。这将是您的主要文件:

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

这是您的处理器文件:

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

正如您所看到的,上面的脚本将在短时间后超时(在这种情况下为10毫秒)。 CURLOPT_TIMEOUT_MS可能不会像这样工作,在这种情况下,它将等同于curl_setopt($ch, CURLOPT_TIMEOUT, 1)

因此,当访问处理器文件时,无论用户(即调用文件)是否中止连接,它都将执行其任务。

当然,您也可以在页面之间传递GET或POST参数。

答案 9 :(得分:0)

呸,我误解了你的要求。看起来他们实际上是:

  • 脚本接收来自您无法控制的外部源的输入
  • 脚本处理并验证输入,并让外部应用程序知道它们是否良好并终止会话。
  • 脚本启动了一个长期运行的过程。

在这种情况下,然后是,使用外部作业队列和/或cron将起作用。验证输入后,将作业详细信息插入队列,然后退出。然后可以运行另一个脚本,从队列中获取作业详细信息,然后启动更长的过程。 Alex Howansky有正确的想法。

对不起,我承认我第一次撇去了一下。

答案 10 :(得分:0)

您可以将这些功能拆分为三个脚本。 1.启动进程并通过exec或command调用第二个进程,这也可以通过http调用运行。 2.第二个将运行数据库处理,最后将启动最后一个 3.最后一个将通过电子邮件发送

答案 11 :(得分:0)

我建议最后生成一个新的异步请求,而不是继续与用户进行该过程。

您可以使用以下答案生成其他请求: Asynchronous PHP calls?

答案 12 :(得分:0)

在Apache php.ini配置文件中,确保禁用输出缓冲:

output_buffering = off