продолжить обработку php после отправки http ответа

80

Мой script вызывается сервером. С сервера я получу ID_OF_MESSAGE и TEXT_OF_MESSAGE.

В моем script я обработаю входящий текст и создаю ответ с параметрами: ANSWER_TO_ID и RESPONSE_MESSAGE.

Проблема в том, что я отправляю ответ на incomming "ID_OF_MESSAGE", но сервер, который отправляет мне сообщение для обработки, установит свое сообщение как доставленное мне (это означает, что я могу отправить ему ответ на этот идентификатор), после получения http ответ 200.

Одним из решений является сохранение сообщения в базе данных и создание некоторой cron, которая будет работать каждую минуту, но мне нужно немедленно создать ответное сообщение.

Есть ли какое-то решение, как отправить на сервер HTTP-ответ 200, и продолжить выполнение php script?

Большое спасибо

Теги:
http
response

12 ответов

157

Да. Вы можете сделать это:

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
    Можно ли сделать это с помощью keep-alive соединения?
  • 3
    Отлично!! Это единственный ответ на этот вопрос, который действительно работает !!! 10p +
Показать ещё 12 комментариев
30

Я видел много ответов здесь, которые предлагают использовать 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); вверху.

  • 2
    хороший код! очень полезно!
  • 3
    Перепробовал много разных комбинаций, это то, что работает !!! Спасибо Коста Контос !!!
Показать ещё 2 комментария
22

Если вы используете обработку 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

  • 1
    Спасибо за это, потратив несколько часов, это сработало для меня в nginx
  • 1
    fastcgi_finish_request (); работает над iis, спасибо
Показать ещё 1 комментарий
11

Я потратил несколько часов на эту проблему, и я пришел с этой функцией, которая работает на 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.
  • 0
    Ваш код почти такой же, как здесь, но ваш пост старше :) +1
Показать ещё 1 комментарий
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);
  • 1
    Я упоминал об этом в первоначальном ответе, но я скажу это и здесь. Вам не обязательно закрывать соединение, но тогда произойдет ожидание следующего актива, запрошенного для того же соединения. Таким образом, вы можете быстро доставить HTML, но затем один из ваших файлов JS или CSS может загружаться медленно, так как соединение должно завершить получение ответа от PHP, прежде чем сможет получить следующий ресурс. Поэтому по этой причине закрытие соединения является хорошей идеей, поэтому браузеру не нужно ждать, пока оно освободится.
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();
}
1

По некоторым неясным причинам ни одно из этих решений не работает при использовании MAMP PRO (Apache, MySQL, PHP).

  • 0
    Я начинаю думать, что это потому, что и клиент, и сервер находятся на одном IP / хосте. Я постараюсь назначить разные локальные IP-адреса или интерфейс или локально установленную виртуальную машину и посмотреть, смогу ли я заставить ее работать. PS: работает на удаленном сервере.
1

Я задал этот вопрос Расмусу Лердорфу в апреле 2012 года, сославшись на эти статьи:

Я предложил создать новую встроенную функцию PHP, чтобы уведомить платформу о том, что никакой новый вывод (на stdout?) не будет сгенерирован (такая функция может позаботиться о закрытии соединения). Расмус Лердорф ответил:

См. Gearman. Вы действительно не хотите, чтобы ваши интерфейсные веб-серверы выполняли бэкэнд-обработку таким образом.

Я вижу его точку зрения и поддерживаю его мнение для некоторых приложений/сценариев загрузки! Однако в некоторых других сценариях решения от vcampitelli и др. Являются хорошими.

1

Для этого я использую функцию 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 ссылка ссылка

  • 0
    Это не то, что запрашивается - эта функция в основном просто расширяет событие завершения скрипта и все еще является частью выходного буфера.
  • 1
    @Fiskie Fiskie Ты прав. Я добавил редактирование.
Показать ещё 1 комментарий
0

Я не могу установить 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');
?>
0

в случае использования 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 ожидает об эф, если длина контента достигает.

надеюсь, что это поможет

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. Это сделано.

Некоторые ссылки для помощи с pthreads

Удачи.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню