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

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

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

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

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

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

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

Вам нужна такая настройка

alt text

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

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

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

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

См. Также 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-запроса устанавливает очень малый тайм-аут, поэтому ответ игнорируется.

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

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

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