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


101

Мій сценарій викликається сервером. Від сервера я отримаю ID_OF_MESSAGEі TEXT_OF_MESSAGE.

У моєму сценарії я оброблятиму вхідний текст та генерувати відповідь за допомогою парам: ANSWER_TO_IDта RESPONSE_MESSAGE.

Проблема полягає в тому, що я надсилаю відповідь на вхідний "ID_OF_MESSAGE", але сервер, який надсилає мені повідомлення для обробки, встановить його повідомлення як доставлене мені (Це означає, що я можу надіслати йому відповідь на цей ідентифікатор), після отримання http відповіді 200.

Одне з рішень - зберегти повідомлення в базі даних і зробити якийсь крон, який буде працювати щохвилини, але мені потрібно генерувати відповідь на повідомлення негайно.

Чи є якесь рішення, як надіслати на сервер http відповідь 200 і чим продовжувати виконувати скрипт php?

Дуже дякую

Відповіді:


191

Так. Ви можете зробити це:

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

5
Чи можливо це зробити за допомогою постійного зв'язку?
Congelli501

3
Відмінно !! Це єдина відповідь на це питання, яка насправді працює !!! 10p +
Martin_Lakes

3
Чи є причина, коли ви використовуєте ob_flush після ob_end_flush? Я розумію необхідність функції змиву там наприкінці, але я не впевнений, чому вам потрібно ob_flush, коли буде викликано ob_end_flush.
ars265

9
Зверніть увагу, що якщо заголовок кодування вмісту встановлено на що-небудь інше, ніж на "жодне", це може зробити цей приклад марним, оскільки він все одно дозволить користувачеві чекати повний час виконання (до часу очікування?). Отже, щоб бути абсолютно впевненим, що він буде працювати локально і у виробничому середовищі, встановіть заголовок "кодування вмісту" на "немає":header("Content-Encoding: none")
Брайан

17
Порада: я почав використовувати PHP-FPM, тому мені довелося додати fastcgi_finish_request()наприкінці
vcampitelli

44

Я бачив тут багато відповідей, які пропонують використовувати, ignore_user_abort(true);але цей код не потрібен. Все це полягає в тому, щоб ваш сценарій продовжував виконуватись до того, як відповідь буде надіслана у випадку, якщо користувач перерветься (закривши браузер або натиснувши клавішу 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 за замовчуванням, тоді дотримуйтесь set_time_limit(0);верхньої частини.


3
Спробував багато різних комбінацій, ЦЕ працює тим !!! Спасибі Коста Контос !!!
Martin_Lakes

1
Відмінно працює на apache 2, php 7.0.32 та ubuntu 16.04! Дякую!
KyleBunga

1
Я спробував інші рішення, і тільки це працювало на мене. Порядок ліній також важливий.
Sinisa

32

Якщо ви використовуєте обробку 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


2
Дякую за це, витративши кілька годин, це працювало для мене в nginx
Ehsan

7
дат сума дорогого розрахунку Тхо: o сильно вражений, такий дорогий!
Фрідріх Ролл

Дякую DarkNeuron! Чудова відповідь для нас за допомогою php-fpm, щойно вирішив мою проблему!
Синіса

21

Я витратив кілька годин на це питання, і я прийшов з такою функцією, яка працює на 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.
спеція

Ваш код майже такий самий, як код тут, але ваша публікація старша :) +1
Бухгалтер

будьте обережні з filter_inputфункцією, вона іноді повертає NULL. детально дивіться цей вклад користувача
Бухгалтер,

4

Відповідь @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);

3
Я згадував про це в оригінальній відповіді, але про це я теж скажу тут. Вам не обов’язково потрібно закривати з'єднання, але тоді, що станеться, наступний актив, запитуваний на цьому ж з'єднанні, буде змушений чекати. Таким чином, ви можете доставити HTML швидко, але тоді один з ваших файлів JS або CSS може завантажуватися повільно, оскільки з'єднання має закінчити отримання відповіді від PHP, перш ніж воно зможе отримати наступний актив. Тому з цього приводу закриття з'єднання є хорошою ідеєю, тому браузеру не доведеться чекати його звільнення.
Нейт Лемптон

3

Я використовую для цього функцію 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 посилання посилання


Про це не вимагають - ця функція в основному просто розширює подію припинення сценарію і все ще є частиною вихідного буфера.
fisk

1
Виявив, що варто заплатити, тому що він показує, що не працює.
Trendfischer

1
Дітто - проголошення, тому що я сподівався побачити це як відповідь, і корисно побачити, що це не працює.
HappyDog

2

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

1

у разі використання php file_get_contents, підключення недостатньо. php все ще чекає eof witch відправкою по серверу.

моє рішення - прочитати "Довжина вмісту:"

ось зразок:

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

Як ви бачите, цей сценарій дозування чекає приблизно eof, якщо досягається тривалість вмісту.

сподіваюся, що це допоможе


1

Я задав це питання Расмусу Лердорфу в квітні 2012 року, цитуючи ці статті:

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

Дивіться Gearman . Ви дійсно не хочете, щоб ваші веб-сервери прифронтових програм виконували подібну обробку.

Я бачу його суть і підтримую його думку щодо деяких застосувань / сценарії завантаження! Однак, за деякими іншими сценаріями, рішення від vcampitelli та ін є хорошими.


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

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

Удачі.


0

Я знаю, що це старе, але можливо корисне на даний момент.

З цією відповіддю я не підтримую актуального питання, але як правильно вирішити цю проблему. Сподіваюся, це допомагає іншим людям вирішувати подібні проблеми.

Я б запропонував скористатися RabbitMQ або подібними службами та запустити фонове навантаження за допомогою робочих екземплярів . Існує пакет під назвою amqplib для php, який виконує всю роботу для використання RabbitMQ.

Професійні:

  1. Це високоефективно
  2. Структуровані та ремонтовані
  3. Це абсолютно масштабується з робочими екземплярами

Ніг:

  1. RabbitMQ повинен бути встановлений на сервері, це може бути проблемою з деяким веб-хостером.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.