126

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

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

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

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

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

十分感谢

4

13 回答 13

220

是的。你可以这样做:

ignore_user_abort(true);//not required
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();
fastcgi_finish_request();//required for PHP-FPM (PHP > 5.3.3)

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

die(); //a must especially if set_time_limit=0 is used and the task ends
于 2013-03-07T14:24:42.327 回答
66

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

// 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);在顶部。

于 2015-02-26T09:05:05.030 回答
44

如果您使用 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/en/function.fastcgi-finish-request.php

PHP 问题 #68722:https ://bugs.php.net/bug.php?id=68772

于 2016-08-12T12:39:04.490 回答
26

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

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

于 2017-02-15T09:23:37.837 回答
5

稍微修改了@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);
于 2015-06-03T00:42:12.690 回答
4

我为此使用了 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链接 链接以来 register_shutdown_function 的行为发生了变化

于 2015-05-18T11:00:07.350 回答
4

我无法安装 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');
?>
于 2019-02-01T00:55:33.327 回答
4

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

我建议开发一个新的 PHP 内置函数来通知平台不会生成进一步的输出(在标准输出上?)(这样的函数可能会关闭连接)。拉斯穆斯·勒多夫回应:

齿轮人。您真的不希望您的前端 Web 服务器像这样进行后端处理。

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

于 2017-06-17T07:59:48.330 回答
3

我有一些可以压缩和发送响应并让其他 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();
}
于 2018-08-11T13:06:14.513 回答
2

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

我的解决方案是阅读“内容长度:”

这是示例:

响应.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。

阅读.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。

希望它会有所帮助

于 2016-08-26T08:26:03.913 回答
1

如果您不想篡改响应标头,还有另一种方法值得考虑。如果你在另一个进程上启动一个线程,被调用的函数不会等待它的响应,而是会返回一个最终的 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。它完成了。

一些帮助 pthreads 的链接

祝你好运。

于 2016-07-19T01:33:08.587 回答
0

除了答案之外,我还返回了一个 JSON 字符串作为响应。我发现响应因未知原因而被截断。解决问题的方法是增加额外的空间:

echo $json_response;
//the fix
echo str_repeat(' ', 10);
于 2021-10-06T06:40:14.240 回答
0

除了所有其他出色的答案之外,我还有一个重要的补充!
TL;DR:
添加

echo str_repeat(' ', 1024);



在我的用例中,我想将 API 调用标记为“已接受”,并且不要让客户端等待完成处理。

实际上感觉是对的:客户端在收到“Connection: close”标头时应该停止等待答案,但实际上至少我的 php 还没有发送这些标头。(使用两个不同的 PHP 服务器进行测试,并分别通过 Browser+Insomnia 客户端进行测试)

如果没有回显至少特殊数量的字节(在我的情况下为 1024 个字节),则存在一些特殊行为,flush() 不会发送第一个内容。(可能是一些修补程序,以防 php 文件中有一些前导或尾随空格,这些空格实际上被视为 echo 语句或类似的东西,这会阻止以后的 header() 语句生效。)

为了解决这个问题,可以发送 1024 个前导空格字符,这些字符应该被 JSON/XML/HTML 解释器忽略。

所以完整的代码如下所示:

ob_start();
echo str_repeat(' ', 1024);
echo $response; // send the response
header('Connection: close');
header('Content-Length: '.ob_get_length());
ob_end_flush();
@ob_flush();
flush();

(这是我的论点的一些备份,找不到正确的来源 rn: How to flush output after each `echo` call?

于 2021-08-25T14:39:29.927 回答