Мой script вызывается сервером. С сервера я получу ID_OF_MESSAGE
и TEXT_OF_MESSAGE
.
В моем script я обработаю входящий текст и создаю ответ с параметрами: ANSWER_TO_ID
и RESPONSE_MESSAGE
.
Проблема в том, что я отправляю ответ на incomming "ID_OF_MESSAGE"
, но сервер, который отправляет мне сообщение для обработки, установит свое сообщение как доставленное мне (это означает, что я могу отправить ему ответ на этот идентификатор), после получения http ответ 200.
Одним из решений является сохранение сообщения в базе данных и создание некоторой cron, которая будет работать каждую минуту, но мне нужно немедленно создать ответное сообщение.
Есть ли какое-то решение, как отправить на сервер HTTP-ответ 200, и продолжить выполнение php script?
Большое спасибо
Да. Вы можете сделать это:
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...
Я видел много ответов здесь, которые предлагают использовать ignore_user_abort(true);
, но этот код не нужен. Все это гарантирует, что ваш script продолжит выполнение до того, как ответ будет отправлен в случае, если пользователь отменит (закрыв свой браузер или нажав кнопку 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 по умолчанию script, тогда нажмите set_time_limit(0);
вверху.
Если вы используете обработку 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
Я потратил несколько часов на эту проблему, и я пришел с этой функцией, которая работает на 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();
}
Вы можете вызвать эту функцию до вашей длительной обработки.
Изменен ответ на @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);
У меня есть кое-что, что может сжать и отправить ответ и позволить другому 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();
}
По некоторым неясным причинам ни одно из этих решений не работает при использовании MAMP PRO (Apache, MySQL, PHP).
Я задал этот вопрос Расмусу Лердорфу в апреле 2012 года, сославшись на эти статьи:
Я предложил создать новую встроенную функцию PHP, чтобы уведомить платформу о том, что никакой новый вывод (на stdout?) не будет сгенерирован (такая функция может позаботиться о закрытии соединения). Расмус Лердорф ответил:
См. Gearman. Вы действительно не хотите, чтобы ваши интерфейсные веб-серверы выполняли бэкэнд-обработку таким образом.
Я вижу его точку зрения и поддерживаю его мнение для некоторых приложений/сценариев загрузки! Однако в некоторых других сценариях решения от vcampitelli и др. Являются хорошими.
Для этого я использую функцию php register_shutdown_function.
void register_shutdown_function ( callable $callback [, mixed $parameter [, mixed $... ]] )
http://php.net/manual/en/function.register-shutdown-function.php
Изменить. Вышеуказанное не работает. Кажется, я был введен в заблуждение какой-то старой документацией. Поведение register_shutdown_function сменилось с PHP 4.1 ссылка ссылка
Я не могу установить pthread, и ни одно из предыдущих решений не работает для меня. Я нашел только следующее решение для работы (ссылка: https://stackoverflow.com/questions/3833013/continue-php-execution-after-sending-http-response):
<?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');
?>
в случае использования php file_get_contents, закрытия соединения недостаточно. php по-прежнему ожидает, что eof witch отправит сервер.
Мое решение - прочитать "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" в ответ на закрытие строки, если не считать fget во время ожидания eof.
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);
Как вы можете видеть, этот доцент script ожидает об эф, если длина контента достигает.
надеюсь, что это поможет
Существует другой подход, и его стоит рассмотреть, если вы не хотите вмешиваться в заголовки ответов. Если вы начнете поток в другом процессе, вызываемая функция не будет ждать ответа и вернется в браузер с завершенным 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
Удачи.