发送http响应后继续处理php

时间:2013-03-07 14:20:02

标签: php http response

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

在我的脚本中,我将处理传入文本并使用参数生成响应:ANSWER_TO_IDRESPONSE_MESSAGE

问题在于我正在向"ID_OF_MESSAGE"发送回复,但是向我发送消息的服务器会将他的消息设置为发送给我(这意味着我可以向他发送对该ID的响应),之后接收http响应200.

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

是否有一些解决方案如何发送到服务器http响应200而不是继续执行php脚本?

非常感谢

12 个答案:

答案 0 :(得分:173)

是。你可以这样做:

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

答案 1 :(得分:33)

我在这里看到很多回复建议使用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);放在顶部。

答案 2 :(得分:23)

如果您正在使用FastCGI处理或PHP-FPM,您可以:

session_write_close(); //close the session
fastcgi_finish_request(); //this returns 200 to the user, and processing continues

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

来源:http://fi2.php.net/manual/en/function.fastcgi-finish-request.php

答案 3 :(得分:14)

我在这个问题上花了几个小时,我已经使用了这个适用于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();
}

您可以在长时间处理之前调用此功能。

答案 4 :(得分:3)

稍微修改了@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);

答案 5 :(得分:2)

我使用php函数register_shutdown_function。

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

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

修改:以上操作无效。似乎我被一些旧的文档误导了。自PHP 4.1 link link

以来,register_shutdown_function的行为已发生变化

答案 6 :(得分:1)

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

我建议开发一个新的PHP内置函数来通知平台没有生成进一步的输出(在stdout上?)(这样的函数可能会关闭连接)。 Rasmus Lerdorf回复:

  

Gearman。你真的不希望你的前端Web服务器像这样进行后端处理。

我可以看到他的观点,并支持他对某些应用程序/加载方案的意见!然而,在其他一些情况下,vcampitelli等人的解决方案很好。

答案 7 :(得分: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();
}

答案 8 :(得分: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-&gt; start() PHP就不会等待此线程的返回结果,因此只要考虑rest_endpoint。它完成了。

一些帮助pthreads的链接

祝你好运。

答案 9 :(得分:0)

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

我的解决方案是阅读内容长度:&#39;

这里是样本:

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

注意&#34; \ n&#34;响应近线,如果不是等待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。

希望它会有所帮助

答案 10 :(得分:0)

我无法安装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');
?>

答案 11 :(得分:0)

我知道它已经很老了,但目前可能有用。

使用此答案,我不支持实际的问题,但如何正确解决此问题。希望它可以帮助其他人解决此类问题。

我建议使用RabbitMQ或类似服务,并使用worker instances运行后台工作负载。有一个名为amqplib的php软件包,可以为您使用RabbitMQ进行所有工作。

专业人士:

  1. 表现出色
  2. 结构合理且易于维护
  3. 它具有工作实例的绝对可扩展性

否定的:

  1. RabbitMQ必须安装在服务器上,这可能是一个问题 一些网络托管服务商。