发送 http 响应后继续处理 php

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

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

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

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

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

非常感谢

87995 次浏览

是的,你可以这么做:

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

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

我使用 php 函数 register _ close _ function 来完成这个任务。

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

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

编辑 : 以上内容不起作用。看来我被一些旧文件误导了。自从 PHP 4.1 链接以来,register _ close _ function 的行为已经发生了变化

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

如果您不想篡改响应标头,还有另一种方法值得考虑。如果您在另一个进程上启动一个线程,被调用的函数将不会等待其响应,而是返回到浏览器,并带有最终的 http 代码。您需要配置 Pthread < a href = “ http://php.net/Manual/kr/pthreads.installation.php”rel = “ nofollow noReferrer”> 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)
}

一旦我们执行了 $keep _ Processing-> start (),PHP 不会等待这个线程的返回结果,因此只考虑 rest _ endpoint。结束了。

一些帮助 pthread 的链接

祝你好运。

如果您正在使用 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

在使用 php file _ get _ content 的情况下,关闭连接是不够的。 Php 仍在等待服务器发送的 eof。

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

下面是一个例子:

返回文章页面回应:

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

请注意,如果在等待 eof 时没有读取 fget,则响应关闭行的“ n”。

返回文章页面

<?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。

希望能有所帮助

我花了几个小时在这个问题上,我带来了这个在 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();
}

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

2012年4月,我向拉斯马斯 · 勒多夫(Rasmus Lerdorf)提出了这个问题,并引用了以下文章:

我建议开发一个新的 PHP 内置函数,通知平台没有进一步的输出(在 stdout 上?)将生成(这样的函数可能负责关闭连接)。拉斯马斯•勒道夫(Rasmus Lerdorf)回应道:

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

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

我有一些东西,可以压缩和发送响应,让其他 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();
}

我不能安装 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');
?>

我有一个重要的补充,所有其他伟大的答案!
译者:

echo str_repeat(' ', 1024);



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

实际上感觉是对的: 当客户端收到“ Connection: close”头信息时,它应该停止等待回复,但事实上至少 MY php 还没有发送这些头信息。 (通过两个不同的 PHP 服务器测试,并通过浏览器 + 失眠客户端各)

如果没有回显至少一个特殊数量的字节(在我的例子中是1024字节) ,那么 flash ()不会发送第一个内容,这里有一些特殊的行为。 (可能是一些修补程序,以防在 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: 如何在每次“ echo”调用后刷新输出?)

除了答案之外,我还返回了一个 JSON 字符串作为响应。我发现由于不明原因,反应被截断了。解决这个问题的办法是增加额外的空间:

echo $json_response;
//the fix
echo str_repeat(' ', 10);