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

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

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

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

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

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

 Henrik P. Hessel30 сент. 2010 г., 19:07
Извините, но это выглядит как полное неправильное использование языка PHP.
 Leksat07 сент. 2011 г., 11:52
Это решение работает -stackoverflow.com/questions/265073/php-background-processes/... ?
 Wrikken30 сент. 2010 г., 19:10
Не столько злоупотребление языком PHP, сколько злоупотребление процессом веб-сервера. Если HTTP / web больше не используется, веб-сервер не должен этим заниматься.
 Henrik P. Hessel30 сент. 2010 г., 19:13
@Wrikken Согласен! :)
 Joshua Burns08 дек. 2011 г., 19:48
давайте попробуем сохранить личное мнение при себе. ответь на вопрос или иди в другое место, пожалуйста.
 Ryan Chouinard30 сент. 2010 г., 20:26
Злоупотребление системой или нет, иногда мы должны делать вещи, которые нам не нравятся из-за требований, находящихся вне нашего контроля. Не делает вопрос недействительным, просто делает ситуацию неудачной.
 Tex29 апр. 2011 г., 16:34
Я вообще не понимаю, как это злоупотреблять. Если это так, кто-то должен сказать Amazon, чтобы он закрыл amazon.com, так как большая часть работы, связанной с упаковкой и доставкой заказа, выполняется после завершения веб-запроса на покупку. Либо это, либо установите двухнедельный тайм-аут на запросы на покупку amazon.com и доставьте ответ в браузер только после того, как заказ будет доставлен клиенту.

Ответы на вопрос(12)

браузер). Секрет создания фонового 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

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?

 Graham Christensen26 мар. 2012 г., 18:08
Вам может потребоваться отключить дополнительную буферизацию, которая происходит в Apache:<?php apache_setenv('no-gzip', 1); ini_set('zlib.output_compression', 0); ini_set('implicit_flush', 1);?>

а не продолжать процесс с пользователем.

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

Решение Вопроса

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

 Victor Nicollet01 окт. 2010 г., 00:32
Это решение, которое я изначально имел в виду. С другой стороны, настройка очереди на обработку с единственной целью обойти тайм-аут в стороннем приложении заставляет меня чувствовать себя немного неловко.
 mas.morozov30 сент. 2014 г., 21:36
Это решение страдает от недостатка параллелизма ... или нужно будет запустить пул рабочих процессов для обслуживания очереди. Я закончил публиковать и затем отключать http-запросы к self-localhost (способом, описанным здесь SomeGuy), чтобы использовать пул существующих работников httpd в качестве фоновых процессоров.
 Victor Nicollet11 окт. 2010 г., 20:59
Это то, что я в итоге сделал, из-за отсутствия портативного решения на основе форка ...

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

В этом случае, тогда да, использование внешней очереди заданий и / или 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');
?>

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

 Michael Wolf20 дек. 2013 г., 22:59
Интересный подход, но, к сожалению, он не работает, если вы работаете за лаком или, по-видимому, другими прокси.
 ficuscr03 июн. 2014 г., 20:51
ignore_user_abort(); // optional не будет иметь никакого эффекта, без передачи значения (логическое), что функция возвращает текущее значение.
 Jarek Jakubowski17 июн. 2015 г., 13:46
Там должен бытьignore_user_abort(true); вместоignore_user_abort();
 Steel Brain24 мар. 2014 г., 18:55
он не работает на php5 и браузер chrome на linux, chrome ждет 30 секунд, прежде чем разорвать соединение
 NLemay08 мая 2015 г., 16:37
Я протестировал это решение на своем виртуальном хостинге, и после 30 секунд ожидания было показано «Текстовый пользователь никогда не увидит».
 Pavel Kostenko21 нояб. 2013 г., 14:10
Советую добавитьsession_write_close(); послеflush(); если вы используете сеансы, в противном случае вы не сможете использовать свой сайт (на той же вкладке браузера) до тех пор, пока не завершится обработка (фоновая).

т ваш основной файл:

<?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 между страницами.

 Erik Kalkoken03 февр. 2017 г., 10:12
Я уже давно ищу решение этой проблемы, и это работает! Большое спасибо. Другие решения могут работать в определенных сценариях, кроме случаев, когда вы имеете ограниченный контроль только над своим веб-сервером и не можете создавать новые процессы; Конфигурация, которую я обычно нахожу на коммерческих веб-серверах. Это решение все еще работает! Одно важное дополнение. Для систем UNIX вам нужно добавитьcurl_setopt($ch, CURLOPT_NOSIGNAL, 1); для тайм-аутов <1 сек на работу. Проверьте здесь дляобъяснение.

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

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

 Victor Nicollet01 окт. 2010 г., 00:33
По-видимому, согласно документации, register_shutdown_function вызывается до того, как скрипт завершил диалог с 4.1.0. Другая ваша ссылка, однако, содержит многообещающий комментарий:php.net/manual/en/features.connection-handling.php#89177 Я постараюсь углубиться в это и доложу здесь.

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

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

output_buffering = off

как если бы он был запущен в командной строке? Вы можете сделать это с помощью PHPExec.

 ErnestV31 янв. 2015 г., 12:54
exec () часто является проблемой в общих / размещенных пространствах. Плюс огромный риск для безопасности.
 Wrikken30 сент. 2010 г., 19:11
+1, что-то вродеGearman уже настроен для этого (но другие / собственные решения, конечно, одинаково действительны).

имею в виду что-то вроде этого:

// 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 вызывает дочерний скрипт (такой же, как parent или другой) на том же сервере (или любом другом сервере) и передает им данные запроса;родитель говорит, что хорошо для пользователя и заканчивается;ребенок работает.

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

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

 user389650111 июл. 2015 г., 18:29
этот подход работает для меня, потрясающе!
 BeetleJuice04 июн. 2016 г., 09:36
В родительском скриптеfread($socketToChild, $dataSize), где же$dataSize родом из? Вам нужно точно знать, сколько данных ожидать от сокета (включая размер заголовков)? Я должен что-то упустить.
 mas.morozov30 сент. 2014 г., 21:27
Этот подход (слегка измененный) является единственным рабочим решением, которое я нашел для создания фоновой задачи из mod_php apache.без накладные расходы на запуск отдельного процесса ОС - вместо этого он будет занимать и использовать одного из уже существующих работников httpd

Ваш ответ на вопрос