发送 HTTP 响应后继续执行 PHP

发布于 2024-09-25 06:38:54 字数 445 浏览 2 评论 0原文

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

长话短说:

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

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

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

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

戏剧牡丹亭 2024-10-02 06:38:54

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

<?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 有固定的超时时间。所以我承认了银行链接并给他指明了出路,并在他离开后做我的事情。

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.

乄_柒ぐ汐 2024-10-02 06:38:54

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

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.

祁梦 2024-10-02 06:38:54

您需要的是这种设置

alt text

What you need is this kind of setup

alt text

恍梦境° 2024-10-02 06:38:54

人们可以对自己或任何其他脚本使用“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);
...

现在是子脚本:

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

主要思想是:

  • 由用户请求调用的父脚本;
  • 父级调用同一服务器(或任何其他服务器)上的子脚本(与父级或另一个相同)并向它们提供请求数据;
  • 父级对用户说“确定”并结束;
  • 孩子工作。

如果您需要与子进程交互 - 您可以使用数据库作为“通信媒介”:父进程可以读取子进程状态并写入命令,子进程可以读取命令并写入状态。如果您需要多个子脚本 - 您应该在用户端保留子 ID 以区分它们,并在每次您想要检查相应子脚本的状态时将该 ID 发送给父脚本。

我在这里发现 - http://linuxportal.ru/forums/index.php /t/22951/

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

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/

雨落星ぅ辰 2024-10-02 06:38:54

调用文件服务器上的脚本来执行怎么样,就好像它是在命令行触发的一样?您可以使用 PHP 的 exec 来完成此操作。

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.

日裸衫吸 2024-10-02 06:38:54

您可以使用 PHP 函数 register-shutdown-function 脚本完成与浏览器的对话后执行某些操作。

另请参阅 ignore_user_abort - 但如果您使用,则不需要此功能寄存器关闭函数。在同一页面上,set_time_limit(0) 将防止您的脚本超时。

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.

天气好吗我好吗 2024-10-02 06:38:54

使用队列、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...

阅读更多内容:
如何在响应ajax请求后继续处理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:
How to continue process after responding to ajax request in PHP?

云巢 2024-10-02 06:38:54

可以使用 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参数。

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.

如梦 2024-10-02 06:38:54

您可以在服务器和服务器之间创建 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

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

靖瑶 2024-10-02 06:38:54

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

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

穿越时光隧道 2024-10-02 06:38:54

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

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

在这种情况下,那么是的,使用外部作业队列和/或 cron 就可以了。输入验证后,将作业详细信息插入队列,然后退出。然后可以运行另一个脚本,从队列中获取作业详细信息,并启动较长的进程。亚历克斯·霍万斯基的想法是正确的。

抱歉,我承认我第一次浏览了一下。

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.

此生挚爱伱 2024-10-02 06:38:54

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

您可以使用此处的答案生成另一个请求:
异步 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:
Asynchronous PHP calls?

莫言歌 2024-10-02 06:38:54

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

output_buffering = off

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

output_buffering = off
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文