Продолжить выполнение PHP после отправки HTTP-ответа


Как я могу заставить PHP 5.2 (работающий как apache mod_php) отправить полный HTTP-ответ клиенту, а затем продолжать выполнять операции еще одну минуту?

история:

у меня есть php-скрипт, который должен выполнить несколько запросов к базе данных и отправлять электронную почту, которая занимает от 45 до 60 секунд, чтобы убежать. Этот скрипт вызывается приложением, которое я не могу контролировать. Мне нужно, чтобы приложение сообщало о любых сообщениях об ошибках, полученных от PHP скрипт (в основном неверные ошибки параметров).

приложение имеет задержку таймаута менее 45 секунд (я не знаю точного значения) и поэтому регистрирует каждое выполнение PHP-скрипта как ошибку. Поэтому мне нужен PHP, чтобы отправить полный HTTP-ответ клиенту как можно быстрее (в идеале, как только входные параметры будут проверены), а затем запустить базу данных и обработку электронной почты.

Я запускаю mod_php, так что pcntl_fork не имеется. Я мог бы работать мой путь вокруг этого путем сохранения данных в базу данных и запустить процесс из cron, но я ищу более короткое решение.

13 58

13 ответов:

пусть скрипт, обрабатывающий начальный запрос, создаст запись в очереди обработки, а затем немедленно вернет ее. Затем создайте отдельный процесс (возможно, через cron), который регулярно запускает все задания, ожидающие в очереди.

У меня был этот фрагмент в моем наборе инструментов "специальные скрипты", но он потерялся (облака тогда не были распространены), поэтому я искал его и придумал этот вопрос, удивленный тем, что он отсутствует, я искал больше и вернулся сюда, чтобы опубликовать его:

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

Я на самом деле использовать его в нескольких местах. И это полностью имеет смысл там: banklink возвращает запрос на успешный платеж, и я должен вызвать много услуг и обработать много данных, когда это произойдет. Что иногда занимает более 10 секунд, но banklink имеет фиксированный период ожидания. Поэтому я признаю банклинк и показываю ему выход, и делаю свое дело, когда он уже ушел.

Что вам нужно, это такая настройка

alt text

можно использовать "HTTP fork" для себя или любого другого скрипта. Я имею в виду что-то вроде этого:

// parent sript, called by user request from browser

// create socket for calling child script
$socketToChild = fsockopen("localhost", 80);

// HTTP-packet building; header first
$msgToChild = "POST /sript.php?&param=value&<more params> HTTP/1.0\n";
$msgToChild .= "Host: localhost\n";
$postData = "Any data for child as POST-query";
$msgToChild .= "Content-Length: ".strlen($postData)."\n\n";

// header done, glue with data
$msgToChild .= $postData;

// send packet no oneself www-server - new process will be created to handle our query
fwrite($socketToChild, $msgToChild);

// wait and read answer from child
$data = fread($socketToChild, $dataSize);

// close connection to child
fclose($socketToChild);
...

теперь дочерний скрипт:

// parse HTTP-query somewhere and somehow before this point

// "disable partial output" or 
// "enable buffering" to give out all at once later
ob_start();

// "say hello" to client (parent script in this case) disconnection
// before child ends - we need not care about it
ignore_user_abort(1);

// we will work forever
set_time_limit(0);

// we need to say something to parent to stop its waiting
// it could be something useful like client ID or just "OK"
...
echo $reply;

// push buffer to parent
ob_flush();

// parent gets our answer and disconnects
// but we can work "in background" :)
...

основные идеи:

  • Родительский скрипт по запросу пользователя;
  • родитель вызывает дочерний скрипт (такой же, как родительский или другой) на том же сервере (или любом другом сервере) и дает им данные запроса;
  • родитель говорит Ок пользователю и заканчивается;
  • ребенок завод.

Если вам нужно взаимодействовать с ребенком - вы можете использовать БД в качестве "средства связи": родитель может читать статус ребенка и писать команды, ребенок может читать команды и писать статус. Если вам это нужно для нескольких дочерних сценариев - вы должны сохранить дочерний идентификатор на стороне пользователя, чтобы различать их и отправлять этот идентификатор родителю каждый раз, когда вы хотите проверить статус соответствующего ребенка.

Я нашел это здесь - http://linuxportal.ru/forums/index.php/t/22951/

Как насчет вызова скрипта на файловом сервере для выполнения, как если бы он был запущен в командной строке? Вы можете сделать это с помощью PHP exec.

вы можете использовать функцию PHP register-shutdown-function что будет выполнять что-то после скрипт завершил диалог с браузером.

см. также ignore_user_abort - но вы не должны нуждаться в этой функции, если вы используете register_shutdown_function. На той же странице,set_time_limit(0) предотвратит ваш скрипт тайм-аут.

использование очереди, exec или cron было бы излишним для этой простой задачи. Нет причин не оставаться в рамках одного и того же сценария. Эта комбинация отлично сработала для меня:

        ignore_user_abort(true);
        $response = "some response"; 
        header("Connection: close");
        header("Content-Length: " . mb_strlen($response));
        echo $response;
        flush(); // releasing the browser from waiting
        // continue the script with the slow processing here...

подробнее в: как продолжить процесс после ответа на запрос ajax в PHP?

вы можете создать http-запрос между сервером и сервером. (не требуется браузер). Секрет для создания фонового http-запроса устанавливает очень маленький тайм-аут, поэтому ответ игнорируется.

это рабочая функция, которую я использовал для этого pupose:

может Тридцать один PHP асинхронный фоновый запрос Еще один способ создания асинхронного запроса в PHP (имитация фонового режима).

 /**
  * Another way to make asyncronous (o como se escriba asincrono!) request with php
  * Con esto se puede simpular un fork en PHP.. nada que envidarle a javita ni C++
  * Esta vez usando fsockopen
  * @author PHPepe
  * @param  unknown_type $url
  * @param  unknown_type $params
  */
 function phpepe_async($url, $params = array()) {
  $post_params = array(); 
     foreach ($params as $key => &$val) {
       if (is_array($val)) $val = implode(',', $val);
         $post_params[] = $key.'='.urlencode($val);
     }
     $post_string = implode('&', $post_params);

     $parts=parse_url($url);

     $fp = fsockopen($parts['host'],
         isset($parts['port'])?$parts['port']:80,
         $errno, $errstr, 30);

     $out = "POST ".$parts['path']." HTTP/1.1\r\n";
     $out.= "Host: ".$parts['host']."\r\n";
     $out.= "Content-Type: application/x-www-form-urlencoded\r\n";
     $out.= "Content-Length: ".strlen($post_string)."\r\n";
     $out.= "Connection: Close\r\n\r\n";
     if (isset($post_string)) $out.= $post_string;

     fwrite($fp, $out);
     fclose($fp);
 }

 // Usage:
 phpepe_async("http://192.168.1.110/pepe/feng_scripts/phprequest/fork2.php");

для получения дополнительной информации вы можете посмотреть на http://www.phpepe.com/2011/05/php-asynchronous-background-request.html

для этого можно использовать cURL с очень коротким таймаутом. Это будет ваш основной файл:

<?php>
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, "http://example.com/processor.php");
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_TIMEOUT_MS, 10);      //just some very short timeout
    curl_exec($ch);
    curl_close($ch);
?>

и это ваш файл процессора:

<?php
    ignore_user_abort(true);                       //very important!
    for($x = 0; $x < 10; $x++)                     //do some very time-consuming task
        sleep(10);
?>

как вы можете видеть, верхний скрипт будет тайм-аут через короткое время (10 миллисекунд в этом случае). Вполне возможно, что CURLOPT_TIMEOUT_MS не будет работать так, в этом случае это будет эквивалентно curl_setopt($ch, CURLOPT_TIMEOUT, 1).

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

конечно, вы также можете передавать параметры GET или POST между страницами.

вы можете разделить эти функции на три сценария. 1. Инициируйте процесс и вызовите второй через exec или command, Это также можно запустить через http-вызов. 2. второй запускает обработку базы данных и в конце запускает последний 3. последний будет по электронной почте

Ба, я неправильно понял ваши требования. Похоже, что они на самом деле:

  • скрипт получает входные данные от внешнего источника, который вы не контролируете
  • скрипт обрабатывает и проверяет входные данные, а также позволяет внешнему приложению знать, хороши они или нет, и завершает сеанс.
  • скрипт запускает длительный процесс.

в этом случае, то да, используя внешнюю очередь заданий и/или cron будет работать. После проверки входных данных, вставьте детали в очереди и выйти. Затем можно запустить другой сценарий, получить сведения о задании из очереди и запустить более длительный процесс. У Алекса Хованского есть правильная идея.

Извините, я признаю, что в первый раз немного просмотрел.

Я бы рекомендовал создать новый асинхронный запрос в конце, а не продолжать процесс с пользователем.

вы можете вызвать другой запрос, используя ответ здесь: асинхронные вызовы PHP?

в вашем Apache php.ini конфигурационный файл, убедитесь, что буферизация вывода отключена:

output_buffering = off