Продолжить выполнение PHP после отправки ответа HTTP - PullRequest
60 голосов
/ 30 сентября 2010

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

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

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

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

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

Ответы [ 13 ]

46 голосов
/ 23 января 2013

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

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

25 голосов
/ 30 сентября 2010

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

7 голосов
/ 30 сентября 2010

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

alt text

6 голосов
/ 27 июня 2011

Можно использовать «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" :)
...

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

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

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

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

5 голосов
/ 30 сентября 2010

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

4 голосов
/ 30 сентября 2010

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

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

3 голосов
/ 26 февраля 2011

Использование очереди, 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?

2 голосов
/ 29 мая 2013

Вы можете создать запрос 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

1 голос
/ 21 декабря 2016

Для этого можно использовать 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 между страницами.

0 голосов
/ 07 апреля 2015

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

output_buffering = off
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...