发送 HTTP 响应后继续执行 PHP
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3833013/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Continue PHP execution after sending HTTP response
提问by Victor Nicollet
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?
如何让 PHP 5.2(作为 apache mod_php 运行)向客户端发送完整的 HTTP 响应,然后再继续执行一分钟的操作?
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).
我有一个 PHP 脚本,它必须执行几个长数据库请求并发送电子邮件,运行需要 45 到 60 秒。该脚本由我无法控制的应用程序调用。我需要应用程序报告从 PHP 脚本收到的任何错误消息(主要是无效参数错误)。
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.
该应用程序的超时延迟短于 45 秒(我不知道确切值),因此将 PHP 脚本的每次执行都注册为错误。因此,我需要 PHP 尽可能快地将完整的 HTTP 响应发送到客户端(理想情况下,一旦输入参数已被验证),然后运行数据库和电子邮件处理。
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.
我正在运行 mod_php,所以pcntl_fork
不可用。我可以通过将要处理的数据保存到数据库并从 运行实际过程来解决这个问题cron
,但我正在寻找更短的解决方案。
采纳答案by Alex Howansky
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.
让处理初始请求的脚本在处理队列中创建一个条目,然后立即返回。然后,创建一个单独的进程(可能通过 cron)定期运行队列中挂起的任何作业。
回答by povilasp
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.
我实际上在几个地方使用它。这完全有道理:银行链接正在返回成功付款的请求,当发生这种情况时,我必须调用大量服务并处理大量数据。这有时需要超过 10 秒,但银行链接有固定的超时时间。所以我确认了银行通,并给他指明了出路,等他走了之后再做我的事。
回答by Yanick Rochon
What you need is this kind of setup
你需要的是这种设置
回答by SomeGuy
One can to use "http fork" to oneself or any other script. I mean something like this:
可以对自己或任何其他脚本使用“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?¶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);
...
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.
- 由用户请求调用的父脚本;
- parent 在同一台服务器(或任何其他服务器)上调用子脚本(与 parent 或另一个相同)并向它们提供请求数据;
- 父母对用户说好并结束;
- 孩子工作。
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.
如果您需要与孩子互动 - 您可以使用 DB 作为“通信媒介”:父母可以读取子状态和写入命令,子可以读取命令和写入状态。如果您需要为多个子脚本使用它 - 您应该在用户端保留子 ID 以区分它们,并在每次要检查各个子脚本的状态时将该 ID 发送给父脚本。
I've found that here - http://linuxportal.ru/forums/index.php/t/22951/
回答by SorcyCat
回答by e2-e4
You can use the PHP function register-shutdown-functionthat will execute something afterthe script has completed its dialog with the browser.
您可以使用 PHP 函数register-shutdown-function在脚本完成与浏览器的对话后执行某些操作。
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.
另请参阅ignore_user_abort- 但如果您使用 register_shutdown_function,则不需要此功能。在同一页面上,set_time_limit(0)
将防止您的脚本超时。
回答by Nir O.
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:
使用队列、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...
read more in: How to continue process after responding to ajax request in PHP?
阅读更多内容: 在 PHP 中响应 ajax 请求后如何继续处理?
回答by Ignacio Vazquez
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.
您可以在服务器和服务器之间创建 http 请求。(不需要浏览器)。创建后台 http 请求的秘诀是设置一个非常小的超时时间,因此忽略响应。
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).
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");
For more info you can take a look at http://www.phpepe.com/2011/05/php-asynchronous-background-request.html
有关更多信息,您可以查看 http://www.phpepe.com/2011/05/php-asynchronous-background-request.html
回答by sigalor
It is possible to use cURL for that, with a very short timeout. This would be your main file:
可以为此使用 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);
?>
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)
.
如您所见,上层脚本将在很短的时间(在本例中为 10 毫秒)后超时。可能CURLOPT_TIMEOUT_MS
不会像这样工作,在这种情况下,它相当于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.
当然你也可以在页面之间传递 GET 或 POST 参数。
回答by Luan Costa
In your Apache php.ini
config file, make sure that output buffering is disabled:
在您的 Apachephp.ini
配置文件中,确保禁用输出缓冲:
output_buffering = off