2013-03-07 4 views
62

Мой скрипт вызывается сервером. С сервера я получу ID_OF_MESSAGE и TEXT_OF_MESSAGE.продолжить обработку php после отправки ответа HTTP

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

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

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

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

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

ответ

128

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

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

Возможно ли это с подключением к сети? – Congelli501

+2

Отлично !! Это единственный ответ на этот вопрос, который на самом деле работает !!! 10p + –

+2

Удивительный ответ! Единственное, что я изменил, это 'set_time_limit (0);'. Вероятно, вы хотите, чтобы он работал дольше, чем 30 секунд по умолчанию, но неопределенно может вызвать проблемы, если он перейдет в бесконечный цикл! У меня есть более длинное значение, установленное в файле 'php.ini'. –

19

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

+1

красивый код! очень полезно! –

+2

Пробовал много разных комбинаций, это тот, который работает !!! Спасибо, Kosta Kontos !!! –

+0

Не работает для меня в nginx, php 5.6 – Dave

0

Для этого я использую функцию php register_shutdown_function.

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

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

Edit: выше не работает. Кажется, я был введен в заблуждение какой-то старой документацией. Поведение register_shutdown_function изменилось с PHP 4.1 linklink

+0

Это не то, что требуется - эта функция в основном просто расширяет событие завершения скрипта и по-прежнему является частью выходного буфера. – fisk

+0

@Fiskie Вы правы. Я добавил редактирование. – martti

+0

Обнаружил, что это стоит вверх, потому что это показывает, что не работает. – Trendfischer

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

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-> старт() PHP обыкновение ждать результата возвращения этой нити и, следовательно, насколько rest_endpoint считается. Сделано.

Некоторые ссылки, чтобы помочь с Pthreads

удачи.

12

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

+0

Спасибо за это, потратив несколько часов, это сработало для меня в nginx – Ehsan

+1

fastcgi_finish_request(); работаю на iis, спасибо – dian

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

Обратите внимание на "\ п" в ответ, чтобы закрыть линию, если не считать 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); 

Как вы можете видеть этот сценарий ждать около доцент EOF, если длина контента достигнет.

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

3

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

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

1

Я задал этот вопрос Rasmus Лердорф в апреле 2012 года, ссылаясь на эти статьи:

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

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

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

Смежные вопросы