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

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

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

Длинная история:

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

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

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

4b9b3361

Ответ 1

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

Ответ 2

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

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

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

Ответ 3

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

alt text

Ответ 4

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

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

Теперь дочерний элемент script:

// 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" :)
...

Основная идея:

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

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

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

Ответ 5

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

Ответ 6

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

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

Ответ 7

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

        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?

Ответ 8

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

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

МАЙ 31 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

Ответ 9

Для этого можно использовать 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);
?>

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

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

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

Ответ 10

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

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

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

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

Ответ 11

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

Ответ 12

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

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

Ответ 13

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

output_buffering = off