Подтвердить что ты не робот

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

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

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

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

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

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

Ответ 3

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

Источник: https://www.php.net/manual/en/function.fastcgi-finish-request.php

Ответ 4

Я потратил несколько часов на эту проблему, и я пришел с этой функцией, которая работает на 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();
}

Вы можете вызвать эту функцию до вашей длительной обработки.

Ответ 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);

Ответ 6

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

Ответ 7

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

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

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

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

Ответ 8

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

Ответ 9

У меня есть кое-что, что может сжать и отправить ответ и позволить другому 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();
}

Ответ 10

Существует другой подход, и его стоит рассмотреть, если вы не хотите вмешиваться в заголовки ответов. Если вы начнете поток в другом процессе, вызываемая функция не будет ждать ответа и вернется в браузер с завершенным 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

Удачи.

Ответ 11

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

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

Ответ 12

Я не могу установить pthread, и ни одно из предыдущих решений не работает для меня. Я нашел только следующее решение для работы (ссылка: fooobar.com/questions/96898/...):

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

Ответ 13

Я знаю, что это старый, но, возможно, полезный на данный момент.

С этим ответом я не поддерживаю фактический вопрос, но как решить эту проблему правильно. Надеюсь, что это помогает другим людям решать подобные проблемы.

Я бы предложил использовать RabbitMQ или аналогичные сервисы и запускать фоновую рабочую нагрузку с использованием рабочих экземпляров. Существует пакет под названием amqplib для php, который делает всю работу за вас, чтобы использовать RabbitMQ.

Pro-х:

  1. Это высокая производительность
  2. Изящно структурированный и ремонтопригодный
  3. Абсолютно масштабируется с рабочими экземплярами

Отр-х:

  1. RabbitMQ должен быть установлен на сервере, это может быть проблемой с некоторым веб-хостером.