发送HTTP响应后继续执行PHP
我怎样才能让PHP 5.2(以Apache mod_php身份运行)发送一个完整的HTTP响应到客户端,然后继续执行操作多一分钟?
漫长的故事:
我有一个PHP脚本,必须执行几个长的数据库请求,并发送电子邮件,这需要45至60秒运行。 这个脚本被我无法控制的应用程序调用。 我需要应用程序报告从PHP脚本收到的任何错误消息(大多数无效的参数错误)。
该应用程序的超时延迟短于45秒(我不知道确切的值),因此将PHP脚本的每个执行注册为一个错误。 因此,我需要PHP尽快将完整的HTTP响应发送到客户端(理想情况下只要input参数已经过validation),然后运行数据库和电子邮件处理。
我正在运行mod_php,所以pcntl_fork
不可用。 我可以通过将要处理的数据保存到数据库并从cron
运行实际进程来解决此问题,但是我正在寻找一个较短的解决scheme。
让处理初始请求的脚本在处理队列中创build一个条目,然后立即返回。 然后,创build一个单独的进程(通过cron也许),定期运行队列中待处理的任何工作。
我在我的“特殊脚本”工具箱中有这个片段,但是它丢失了(当时云层不常见),所以我正在寻找它,并提出这个问题,惊讶地发现它缺less,我search了更多,回到这里发布它:
<?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'); ?>
我实际上在less数地方使用它。 在这里完全有道理:银行链接正在返回成功支付的请求,并且在发生这种情况时,我必须调用大量服务并处理大量数据。 有时需要10秒以上,但银行连接有固定的超时期限。 所以我承认银行链接,并告诉他出路,当他已经走了,做我的东西。
你需要的是这种设置
一个人可以使用“http叉”自己或任何其他脚本。 我的意思是这样的:
// 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?¶m=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" :) ...
主要想法是:
- 由用户请求调用的父脚本;
- 父母在同一台服务器(或任何其他服务器)上调用子脚本(与父母或其他父母相同)并向它们提供请求数据;
- 父母对用户说好,结束;
- 小孩工作。
如果您需要与孩子进行互动 – 您可以使用DB作为“沟通媒介”:父母可以阅读孩子的状态和写入命令,孩子可以读取命令和写入状态。 如果你需要几个子脚本 – 你应该在用户端保留子标识符来区分它们,并且每当你想要检查相应子标签的状态时,就把这个标识符发给父代。
在文件服务器上调用一个脚本来执行,就好像它已经在命令行中被触发一样呢? 你可以用PHP的exec来做到这一点。
您可以使用PHP函数register-shutdown-function在脚本完成与浏览器的对话后执行某些操作。
另请参阅ignore_user_abort – 但如果使用register_shutdown_function,则不需要此函数。 在同一页面上, set_time_limit(0)
将防止脚本超时。
使用一个队列,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...
阅读更多: 如何在PHP中响应ajax请求后继续进程?
您可以在服务器和服务器之间创build一个http请求。 (不需要浏览器)。 创build一个后台http请求的秘密是设置一个非常小的超时,所以响应被忽略。
这是我曾经用过的一个工作函数:
5月31日PHPasynchronous后台请求另一种在PHP中创buildasynchronous请求的方法(模拟后台模式)。
/** * 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
可以使用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参数。
您可以将这些function分成三个脚本。 1.启动进程并通过exec或command
调用第二个进程,这也可以通过http调用来运行。 2.第二个将运行数据库处理,最后将开始最后一个3.最后一个将发送电子邮件
巴,我误解了你的要求。 看起来他们实际上是:
- 脚本接收来自您不受控制的外部来源的input
- 脚本处理和validationinput,并让外部应用程序知道他们是否好,并终止会话。
- 脚本启动了一个长期运行的过程。
在这种情况下,是的,使用外部的工作队列和/或cron会起作用。 在inputvalidation之后,将作业详细信息插入到队列中,然后退出。 然后可以运行另一个脚本,从队列中提取作业详细信息,并启动较长的过程。 Alex Howansky有正确的想法。
对不起,我承认我第一次看了一下。
我会build议在最后产生一个新的asynchronous请求,而不是继续与用户的过程。
你可以使用这里的答案产生另一个请求: asynchronousPHP调用?
在您的Apache php.ini
configuration文件中,确保输出缓冲被禁用:
output_buffering = off