发送http响应后继续处理php


101

我的脚本由服务器调用。从服务器我将收到ID_OF_MESSAGETEXT_OF_MESSAGE

在我的脚本中,我将处理传入的文本并使用params:ANSWER_TO_ID和生成响应RESPONSE_MESSAGE

问题是我正在发送对incoming的响应"ID_OF_MESSAGE",但是向我发送消息的服务器将在收到http响应200之后将他的消息设置为传递给我(这意味着我可以向他发送该ID的响应)。

解决方案之一是将消息保存到数据库中,并创建将在每分钟运行的cron,但是我需要立即生成响应消息。

有一些解决方案如何发送到服务器http响应200,然后继续执行php脚本?

非常感谢

Answers:


191

是。你可以这样做:

ignore_user_abort(true);
set_time_limit(0);

ob_start();
// do initial processing here
echo $response; // send the response
header('Connection: close');
header('Content-Length: '.ob_get_length());
ob_end_flush();
ob_flush();
flush();

// now the request is sent to the browser, but the script is still running
// so, you can continue...

5
是否可以通过保持活动连接来实现?
Congelli501

3
优秀的!!这是对该问题实际有效的唯一答案!!!10p +
Martin_Lakes

3
您有理由在ob_end_flush之后使用ob_flush吗?我知道最后需要那里的flush函数,但是我不确定为什么需要在调用ob_end_flush的情况下使用ob_flush。
2015年

9
请注意,如果将内容编码标头设置为除“ none”以外的任何其他内容,则此示例可能会变得无用,因为它仍然会让用户等待完整的执行时间(直到超时?)。因此,要完全确保它在生产环境中可以正常使用,请将“ content-encoding”标头设置为“ none”:header("Content-Encoding: none")
Brian

17
提示:我开始使用PHP-FPM,因此必须fastcgi_finish_request()在末尾添加
vcampitelli 2015年

44

我在这里看到了很多建议使用的响应,ignore_user_abort(true);但是此代码不是必需的。所有这一切都是为了确保您的脚本在用户中止(通过关闭浏览器或按Escape键停止请求)而发送响应之前继续执行。但这不是您要的。您要求在发送响应后继续执行。您只需要以下内容:

    // Buffer all upcoming output...
    ob_start();

    // Send your response.
    echo "Here be response";

    // Get the size of the output.
    $size = ob_get_length();

    // Disable compression (in case content length is compressed).
    header("Content-Encoding: none");

    // Set the content length of the response.
    header("Content-Length: {$size}");

    // Close the connection.
    header("Connection: close");

    // Flush all output.
    ob_end_flush();
    ob_flush();
    flush();

    // Close current session (if it exists).
    if(session_id()) session_write_close();

    // Start your background work here.
    ...

如果您担心您的后台工作将花费比PHP的默认脚本执行时间更长的时间,那么请 set_time_limit(0);放在顶部。


3
尝试了很多不同的组合,这是可行的!!!感谢Kosta Kontos !!!
Martin_Lakes '16

1
在apache 2,php 7.0.32和ubuntu 16.04上完美工作!谢谢!
KyleBunga

1
我尝试了其他解决方案,但只有这个对我有用。线的顺序也很重要。
Sinisa

32

如果您使用的是FastCGI处理或PHP-FPM,则可以:

session_write_close(); //close the session
ignore_user_abort(true); //Prevent echo, print, and flush from killing the script
fastcgi_finish_request(); //this returns 200 to the user, and processing continues

// do desired processing ...
$expensiveCalulation = 1+1;
error_log($expensiveCalculation);

资源: https //www.php.net/manual/zh/function.fastcgi-finish-request.php

PHP问题#68722: https? =


2
为此,在花了几个小时后,我在nginx中为我工作了
-Ehsan

7
数据总和昂贵,太令人印象深刻了,太贵了!
Friedrich Roell

谢谢DarkNeuron!使用php-fpm给我们的好答案,解决了我的问题!
Sinisa

21

我花了几个小时解决这个问题,并提供了可在Apache和Nginx上运行的此功能:

/**
 * respondOK.
 */
protected function respondOK()
{
    // check if fastcgi_finish_request is callable
    if (is_callable('fastcgi_finish_request')) {
        /*
         * This works in Nginx but the next approach not
         */
        session_write_close();
        fastcgi_finish_request();

        return;
    }

    ignore_user_abort(true);

    ob_start();
    $serverProtocole = filter_input(INPUT_SERVER, 'SERVER_PROTOCOL', FILTER_SANITIZE_STRING);
    header($serverProtocole.' 200 OK');
    header('Content-Encoding: none');
    header('Content-Length: '.ob_get_length());
    header('Connection: close');

    ob_end_flush();
    ob_flush();
    flush();
}

您可以在长时间处理之前调用此函数。


2
这真是血腥可爱!在尝试了以上所有其他内容之后,这才是使用nginx的唯一方法。
香料

您的代码与此处的代码几乎完全相同,但是您的帖子更老了:) +1
会计师م19年

注意filter_input函数有时会返回NULL。看到这个用户贡献的细节
会计师م

4

通过@vcampitelli稍微修改了答案。不要以为您不需要close标题。我在Chrome中看到重复的关闭标题。

<?php

ignore_user_abort(true);

ob_start();
echo '{}';
header($_SERVER["SERVER_PROTOCOL"] . " 202 Accepted");
header("Status: 202 Accepted");
header("Content-Type: application/json");
header('Content-Length: '.ob_get_length());
ob_end_flush();
ob_flush();
flush();

sleep(10);

3
我在原始答案中提到了这一点,但我也会在这里说出来。您不一定需要关闭连接,但是随后将发生的是,同一连接上请求的下一个资产将被迫等待。因此,您可以快速交付HTML,但是您的JS或CSS文件之一可能加载缓慢,因为该连接必须先从PHP获取响应,然后才能获取下一个资产。因此,出于这个原因,关闭连接是一个好主意,因此浏览器不必等待释放它。
内特·兰普顿

3

我为此使用php函数register_shutdown_function。

void register_shutdown_function ( callable $callback [, mixed $parameter [, mixed $... ]] )

http://php.net/manual/zh/function.register-shutdown-function.php

编辑:上面的行不通。似乎我被一些旧文档误导了。因为PHP 4.1 register_shutdown_function的行为已更改链接 链接


这不是要求的内容-此函数基本上只是扩展脚本终止事件,并且仍然是输出缓冲区的一部分。
Fisk

1
发现它值得投票,因为它表明什么都不起作用。
Trendfischer

1
同上-赞成,因为我希望将其作为答案,并且对它不起作用很有用。
HappyDog

2

我无法安装pthread,以前的解决方案都无法为我工作。我发现只有以下解决方案可以工作(参考:https : //stackoverflow.com/a/14469376/1315873):

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

1

如果使用php file_get_contents,关闭连接是不够的。php仍在等待服务器发送eof女巫。

我的解决方案是阅读“ Content-Length:”

这是示例:

response.php:

 <?php

ignore_user_abort(true);
set_time_limit(500);

ob_start();
echo 'ok'."\n";
header('Connection: close');
header('Content-Length: '.ob_get_length());
ob_end_flush();
ob_flush();
flush();
sleep(30);

注意“ \ n”是对关闭行的响应,如果不是,则在等待eof时读取fget。

read.php:

<?php
$vars = array(
    'hello' => 'world'
);
$content = http_build_query($vars);

fwrite($fp, "POST /response.php HTTP/1.1\r\n");
fwrite($fp, "Content-Type: application/x-www-form-urlencoded\r\n");
fwrite($fp, "Content-Length: " . strlen($content) . "\r\n");
fwrite($fp, "Connection: close\r\n");
fwrite($fp, "\r\n");

fwrite($fp, $content);

$iSize = null;
$bHeaderEnd = false;
$sResponse = '';
do {
    $sTmp = fgets($fp, 1024);
    $iPos = strpos($sTmp, 'Content-Length: ');
    if ($iPos !== false) {
        $iSize = (int) substr($sTmp, strlen('Content-Length: '));
    }
    if ($bHeaderEnd) {
        $sResponse.= $sTmp;
    }
    if (strlen(trim($sTmp)) == 0) {
        $bHeaderEnd = true;
    }
} while (!feof($fp) && (is_null($iSize) || !is_null($iSize) && strlen($sResponse) < $iSize));
$result = trim($sResponse);

如您所见,如果达到内容长度,此脚本将等待eof。

希望对你有帮助


1

我在2012年4月向Rasmus Lerdorf提出了这个问题,并引用了以下文章:

我建议开发一个新的PHP内置函数,以通知平台不会再产生任何输出(在stdout上?)(此类函数可能需要关闭连接)。拉斯姆斯·莱尔多夫(Rasmus Lerdorf)回应:

请参阅Gearman。您确实真的不希望前端Web服务器执行这样的后端处理。

我可以理解他的观点,并支持他对某些应用程序/加载方案的看法!但是,在某些其他情况下,vcampitelli等人的解决方案是不错的选择。


1

我有一些可以压缩并发送响应并让其他php代码执行的东西。

function sendResponse($response){
    $contentencoding = 'none';
    if(ob_get_contents()){
        ob_end_clean();
        if(ob_get_contents()){
            ob_clean();
        }
    }
    header('Connection: close');
    header("cache-control: must-revalidate");
    header('Vary: Accept-Encoding');
    header('content-type: application/json; charset=utf-8');
    ob_start();
    if(phpversion()>='4.0.4pl1' && extension_loaded('zlib') && GZIP_ENABLED==1 && !empty($_SERVER["HTTP_ACCEPT_ENCODING"]) && (strpos($_SERVER["HTTP_ACCEPT_ENCODING"], 'gzip') !== false) && (strstr($GLOBALS['useragent'],'compatible') || strstr($GLOBALS['useragent'],'Gecko'))){
        $contentencoding = 'gzip';
        ob_start('ob_gzhandler');
    }
    header('Content-Encoding: '.$contentencoding);
    if (!empty($_GET['callback'])){
        echo $_GET['callback'].'('.$response.')';
    } else {
        echo $response;
    }
    if($contentencoding == 'gzip') {
        if(ob_get_contents()){
            ob_end_flush(); // Flush the output from ob_gzhandler
        }
    }
    header('Content-Length: '.ob_get_length());
    // flush all output
    if (ob_get_contents()){
        ob_end_flush(); // Flush the outer ob_start()
        if(ob_get_contents()){
            ob_flush();
        }
        flush();
    }
    if (session_id()) session_write_close();
}

0

还有另一种方法,如果您不想篡改响应头,则值得考虑。如果您在另一个进程上启动线程,则被调用函数将不等待其响应,并将返回带有最终http代码的浏览器。您将需要配置pthread

class continue_processing_thread extends Thread 
{
     public function __construct($param1) 
     {
         $this->param1 = $param1
     }

     public function run() 
     {
        //Do your long running process here
     }
}

//This is your function called via an HTTP GET/POST etc
function rest_endpoint()
{
  //do whatever stuff needed by the response.

  //Create and start your thread. 
  //rest_endpoint wont wait for this to complete.
  $continue_processing = new continue_processing_thread($some_value);
  $continue_processing->start();

  echo json_encode($response)
}

一旦我们执行$ continue_processing-> start() PHP将不会等待该线程的返回结果,因此只要考虑rest_endpoint。完成了

一些帮助pthread的链接

祝好运。


0

我知道这是一个古老的版本,但目前可能有用。

有了这个答案,我不支持实际的问题,而是如何正确解决此问题。希望它可以帮助其他人解决这样的问题。

我建议使用RabbitMQ或类似的服务,并使用辅助实例运行后台工作负载。有一个名为amqplib的软件包的php,它为您使用RabbitMQ所做的所有工作。

专业人士:

  1. 高性能
  2. 精巧的结构和可维护性
  3. 它可以通过工作实例进行绝对扩展

负数:

  1. 必须在服务器上安装RabbitMQ,这可能是某些Web主机的问题。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.