Я пытаюсь выполнить вызов AJAX (через JQuery), который инициирует довольно длительный процесс. Я хотел бы, чтобы сценарий просто отправил ответ, указывающий, что процесс запущен, но JQuery не вернет ответ, пока скрипт PHP не будет запущен.
Я пробовал это с заголовком «close» (ниже), а также с буферизацией вывода; ничто не работает. Какие-нибудь догадки? или это что-то мне нужно сделать в JQuery?
<?php echo( "We'll email you as soon as this is done." ); header( "Connection: Close" ); // do some stuff that will take a while mail( 'dude@thatplace.com', "okay I'm done", 'Yup, all done.' ); ?>
На следующей странице руководства PHP (включая примечания пользователя) предлагается несколько инструкций о том, как закрыть TCP-соединение с браузером, не заканчивая сценарий PHP:
Предположительно для этого требуется немного больше, чем отправка закрытого заголовка.
OP затем подтверждает: yup, это сделало трюк: указав на примечание пользователя # 71172 (ноябрь 2006 г.), скопированное здесь:
Закрытие соединения с браузером пользователей, в то время как ваш скрипт php работает, был проблемой с [PHP] 4.1, когда поведение
register_shutdown_function()
было изменено таким образом, чтобы оно не закрывало подключение пользователей автоматически.sts at mail dot xubion dot hu Добавлено оригинальное решение:
<?php header("Connection: close"); ob_start(); phpinfo(); $size = ob_get_length(); header("Content-Length: $size"); ob_end_flush(); flush(); sleep(13); error_log("do something in the background"); ?>
Это работает нормально, пока вы не замените
phpinfo()
дляecho('text I want user to see');
в этом случае заголовки никогда не отправляются!Решение состоит в том, чтобы явно отключить буферизацию вывода и очистить буфер до отправки информации заголовка. Пример:
<?php ob_end_clean(); header("Connection: close"); ignore_user_abort(true); // just to be safe 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 ! // Do processing here sleep(30); echo('Text user will never see'); ?>
Просто потратил 3 часа, пытаясь понять это, надеюсь, что это поможет кому-то 🙂
Протестировано:
- IE 7.5730.11
- Mozilla Firefox 1.81
Позже в июле 2010 года в связанном ответе Arctic Fire связал еще два пользовательских заметки, которые были следующими:
Необходимо отправить эти 2 заголовка:
Connection: close Content-Length: n (n = size of output in bytes )
Поскольку вам нужно знать размер вашего вывода, вам нужно будет буферизовать ваш вывод, а затем очистить его до браузера:
// buffer all upcoming output ob_start(); echo "We'll email you as soon as this is done."; // get the size of the output $size = ob_get_length(); // send headers to tell the browser to close the connection header("Content-Length: $size"); header('Connection: close'); // flush all output ob_end_flush(); ob_flush(); flush(); // if you're using sessions, this prevents subsequent requests // from hanging while the background process executes if (session_id()) session_write_close(); /******** background process starts here ********/
Кроме того, если ваш веб-сервер использует автоматическое сжатие gzip на выходе (то есть Apache с mod_deflate), это не сработает, потому что изменяется фактический размер вывода, а Content-Length больше не точен. Отключить сжатие gzip конкретного скрипта.
Для получения дополнительной информации посетите http://www.zulius.com/how-to/close-browser-connection-continue-execution
Полная версия:
ignore_user_abort(true);//avoid apache to kill the php running ob_start();//start buffer output echo "show something to user"; session_write_close();//close session file on server side to avoid blocking other requests header("Content-Encoding: none");//send header to avoid the browser side to take content as gzip format header("Content-Length: ".ob_get_length());//send length header header("Connection: close");//or redirect to some url: header('Location: http://www.google.com'); ob_end_flush();flush();//really send content, can't change the order:1.ob buffer to normal buffer, 2.normal buffer to output //continue do something on server side ob_start(); sleep(5);//the user won't wait for the 5 seconds echo 'for diyism';//user can't see this file_put_contents('/tmp/process.log', ob_get_contents()); ob_end_clean();
Вы можете использовать Fast-CGI с PHP-FPM для использования функции fastcgi_end_request()
. Таким образом, вы можете продолжать выполнять некоторую обработку, пока ответ уже отправлен клиенту.
Вы найдете это в руководстве по PHP здесь: FastCGI Process Manager (FPM) ; Но эта функция специально не документирована в руководстве. Здесь выдержка из PHP-FPM: PHP FastCGI Process Manager Wiki :
Область применения: функция php Категория: Оптимизация
Эта функция позволяет ускорить реализацию некоторых php-запросов. Ускорение возможно, если в процессе выполнения сценария есть действия, которые не влияют на реакцию сервера. Например, сохранение сеанса в memcached может произойти после того, как страница была сформирована и передана на веб-сервер. fastcgi_finish_request()
– это функция php, которая останавливает вывод ответа. Веб-сервер сразу начинает передавать ответ «медленно и грустно» клиенту, и php в то же время может делать много полезных вещей в контексте запроса, таких как сохранение сеанса, преобразование загруженного видео, обработка всех видов статистики и т. д.
fastcgi_finish_request()
может вызывать выполнение функции выключения.
Предполагая, что у вас есть сервер Linux и root, попробуйте это. Это самое простое решение, которое я нашел.
Создайте новый каталог для следующих файлов и дайте ему полные разрешения. (Мы можем сделать его более безопасным позже.)
mkdir test chmod -R 777 test cd test
Поместите это в файл с именем bgping
.
echo starting bgping ping -c 15 www.google.com > dump.txt & echo ending bgping
Обратите внимание на &
. Команда ping будет работать в фоновом режиме, пока текущий процесс переходит к команде echo. Он будет ping http://www.google.com 15 раз, что займет около 15 секунд.
Сделайте его исполняемым.
chmod 777 bgping
Поместите это в файл bgtest.php
.
<?php echo "start bgtest.php\n"; exec('./bgping', $output, $result)."\n"; echo "output:".print_r($output,true)."\n"; echo "result:".print_r($result,true)."\n"; echo "end bgtest.php\n"; ?>
Когда вы запрашиваете bgtest.php в своем браузере, вы должны быстро получить следующий ответ, не дожидаясь около 15 секунд для завершения команды ping.
start bgtest.php output:Array ( [0] => starting bgping [1] => ending bgping ) result:0 end bgtest.php
Теперь команда ping должна быть запущена на сервере. Вместо команды ping вы можете запустить скрипт PHP:
php -n -f largejob.php > dump.txt &
Надеюсь это поможет!
Вот модификация кода Timbo, которая работает с сжатием gzip.
// buffer all upcoming output if(!ob_start("ob_gzhandler")){ define('NO_GZ_BUFFER', true); ob_start(); } echo "We'll email you as soon as this is done."; //Flush here before getting content length if ob_gzhandler was used. if(!defined('NO_GZ_BUFFER')){ ob_end_flush(); } // get the size of the output $size = ob_get_length(); // send headers to tell the browser to close the connection header("Content-Length: $size"); header('Connection: close'); // flush all output ob_end_flush(); ob_flush(); flush(); // if you're using sessions, this prevents subsequent requests // from hanging while the background process executes if (session_id()) session_write_close(); /******** background process starts here ********/
Вы можете попробовать многопоточность.
вы можете взломать скрипт, который выполняет системный вызов (с использованием shell_exec ), который вызывает двоичный код php со сценарием для выполнения вашей работы в качестве параметра. Но я не думаю, что это самый безопасный способ. Может быть, вы можете взломать материал, chrooting php-процесс и другие вещи
Кроме того, в phpclasses есть класс, который делает это http://www.phpclasses.org/browse/package/3953.html . Но я не знаю особенностей реализации
Лучшим решением является разветвление фонового процесса. Это довольно прямолинейно в unix / linux:
<?php echo "We'll email you as soon as this is done."; system("php somestuff.php dude@thatplace.com >/dev/null &"); ?>
Вы должны посмотреть на этот вопрос для более удобных примеров:
PHP выполняет фоновый процесс
Ответ Джори Себрехта близок, но он уничтожает любое существующее содержимое, которое может быть буферизировано, прежде чем вы хотите отключиться. Он не вызывает ignore_user_abort
должным образом, позволяя сценарию заканчиваться преждевременно. ответ дийизма хорош, но не является общеприменимым. Например, у человека может быть больше или меньше выходных буферов, которые этот ответ не обрабатывает, поэтому он может просто не работать в вашей ситуации, и вы не будете знать почему.
Эта функция позволяет вам отключать любое время (до тех пор, пока заголовки еще не отправлены) и сохраняет контент, который вы создали до сих пор. Дополнительное время обработки по умолчанию не ограничено.
function disconnect_continue_processing($time_limit = null) { ignore_user_abort(true); session_write_close(); set_time_limit((int) $time_limit);//defaults to no limit while (ob_get_level() > 1) {//only keep the last buffer if nested ob_end_flush(); } $last_buffer = ob_get_level(); $length = $last_buffer ? ob_get_length() : 0; header("Content-Length: $length"); header('Connection: close'); if ($last_buffer) { ob_end_flush(); } flush(); }
Если вам нужна дополнительная память, выделите ее перед вызовом этой функции.
это сработало для меня
//avoid apache to kill the php running ignore_user_abort(true); //start buffer output ob_start(); echo "show something to user1"; //close session file on server side to avoid blocking other requests session_write_close(); //send length header header("Content-Length: ".ob_get_length()); header("Connection: close"); //really send content, can't change the order: //1.ob buffer to normal buffer, //2.normal buffer to output ob_end_flush(); flush(); //continue do something on server side ob_start(); //replace it with the background task sleep(20);
Итак, в основном, как jQuery делает запрос XHR, даже метод ob_flush не будет работать, потому что вы не можете запускать функцию на каждом onreadystatechange. jQuery проверяет состояние, затем выбирает правильные действия (завершение, ошибка, успех, таймаут). И хотя мне не удалось найти ссылку, я помню, что это не работает со всеми реализациями XHR. Метод, который, я считаю, должен сработать для вас, – это пересечение опроса ob_flush и forever-frame.
<?php function wrap($str) { return "<script>{$str}</script>"; }; ob_start(); // begin buffering output echo wrap("console.log('test1');"); ob_flush(); // push current buffer flush(); // this flush actually pushed to the browser $t = time(); while($t > (time() - 3)) {} // wait 3 seconds echo wrap("console.log('test2');"); ?> <html> <body> <iframe src="ob.php"></iframe> </body> </html>
И поскольку сценарии выполняются в строке, так как буферы очищаются, вы получаете выполнение. Чтобы это было полезно, измените console.log на метод обратного вызова, определенный в настройке основного сценария, чтобы получать данные и действовать на них. Надеюсь это поможет. Привет, Морган.
Альтернативным решением является добавление задания в очередь и создание скрипта cron, который проверяет наличие новых заданий и запускает их.
Я должен был сделать это так недавно, чтобы обойти ограничения, наложенные общим хостом – exec () et al был отключен для PHP, запущенного веб-сервером, но мог работать в сценарии оболочки.
Ваша проблема может быть решена путем выполнения некоторого параллельного программирования в php. Я задал вопрос об этом несколько недель назад здесь: как можно использовать многопоточность в PHP-приложениях
И получил отличные ответы. Особенно мне понравилось. Писатель сделал ссылку на учебник Easy Parallel Processing в PHP (сентябрь 2008 г., johnlim), который может реально решить вашу проблему очень хорошо, поскольку я уже использовал ее для решения аналогичной проблемы, возникшей пару дней назад.
Примечание для пользователей mod_fcgid (пожалуйста, используйте на свой страх и риск).
Принятый ответ Джори Себрехта действительно функциональный. Однако, если вы используете mod_fcgid, вы можете обнаружить, что это решение не работает самостоятельно. Другими словами, когда функция флеша называется соединением с клиентом, он не закрывается.
Может быть виноват параметр конфигурации FcgidOutputBufferSize
для mod_fcgid . Я нашел этот совет в:
После прочтения выше вы можете прийти к выводу, что быстрым решением было бы добавить строку (см. «Пример виртуального хоста» в конце):
FcgidOutputBufferSize 0
в файле конфигурации Apache (например, httpd.conf), в файле конфигурации FCGI (например, fcgid.conf) или в файле виртуальных хостов (например, httpd-vhosts.conf).
В (1) выше указана переменная с именем «OutputBufferSize». Это старое имя
FcgidOutputBufferSize
упомянутое в (2) (см. Примечания об обновлении на веб-странице Apache для mod_fcgid ).
Вышеупомянутое решение отключает буферизацию, выполняемую mod_fcgid либо для всего сервера, либо для определенного виртуального хоста. Это может привести к снижению производительности вашего веб-сайта. С другой стороны, это может быть не так, поскольку PHP выполняет буферизацию самостоятельно.
Если вы не хотите отключать буферизацию mod_fcgid , есть еще одно решение … вы можете заставить этот буфер сбросить флажок .
Приведенный ниже код делает это, основываясь на решении, предложенном Джоери Себрехтом:
<?php ob_end_clean(); header("Connection: close"); ignore_user_abort(true); // just to be safe ob_start(); echo('Text the user will see'); echo(str_repeat(' ', 65537)); // [+] Line added: Fill up mod_fcgi's buffer. $size = ob_get_length(); header("Content-Length: $size"); ob_end_flush(); // Strange behaviour, will not work flush(); // Unless both are called ! // Do processing here sleep(30); echo('Text user will never see'); ?>
Какая добавленная строка кода по существу делает, заполняет буфер mod_fcgi , тем самым заставляя его скрываться . Число «65537» было выбрано, потому что значение по умолчанию для переменной FcgidOutputBufferSize
равно «65536», как указано на веб-странице Apache для соответствующей директивы . Следовательно, вам может потребоваться соответствующим образом настроить это значение, если в вашей среде установлено другое значение.
<VirtualHost *:80> DocumentRoot "d:/wamp/www/example" ServerName example.local FcgidOutputBufferSize 0 <Directory "d:/wamp/www/example"> Require all granted </Directory> </VirtualHost>
Я нахожусь на общем хосте, и fastcgi_finish_request
настроен для полного завершения сценариев. Мне не нравится connection: close
решение. С его помощью создается отдельное соединение для последующих запросов, стоимость дополнительных ресурсов сервера. Я прочитал « Transfer-Encoding: cunked
статья Википедии» и узнал, что 0\r\n\r\n
завершает ответ. Я не тестировал это в разных версиях браузеров и устройствах, но он работает во всех четырех моих текущих браузерах.
// Disable automatic compression // @ini_set('zlib.output_compression', 'Off'); // @ini_set('output_buffering', 'Off'); // @ini_set('output_handler', ''); // @apache_setenv('no-gzip', 1); // Chunked Transfer-Encoding & Gzip Content-Encoding function ob_chunked_gzhandler($buffer, $phase) { if (!headers_sent()) header('Transfer-Encoding: chunked'); $buffer = ob_gzhandler($buffer, $phase); return dechex(strlen($buffer))."\r\n$buffer\r\n"; } ob_start('ob_chunked_gzhandler'); // First Chunk echo "Hello World"; ob_flush(); // Second Chunk echo ", Grand World"; ob_flush(); ob_end_clean(); // Terminating Chunk echo "\x30\r\n\r\n"; ob_flush(); flush(); // Post Processing should not be displayed for($i=0; $i<10; $i++) { print("Post-Processing"); sleep(1); }
Если функция flush()
не работает. Вы должны установить следующие параметры в php.ini, например:
output_buffering = Off zlib.output_compression = Off
TL; DR Ответ:
ignore_user_abort(true); //Safety measure so that the user doesn't stop the script too early. $content = 'Hello World!'; //The content that will be sent to the browser. header('Content-Length: ' . strlen($content)); //The browser will close the connection when the size of the content reaches "Content-Length", in this case, immediately. ob_start(); //Content past this point... echo $content; //...will be sent to the browser (the output buffer gets flushed) when this code executes. ob_end_flush(); ob_flush(); flush(); if(session_id()) { session_write_close(); //Closes writing to the output buffer. } //Anything past this point will be ran without involving the browser.
Ответ функции:
ignore_user_abort(true); function sendAndAbort($content) { header('Content-Length: ' . strlen($content)); ob_start(); echo $content; ob_end_flush(); ob_flush(); flush(); } sendAndAbort('Hello World!'); //Anything past this point will be ran without involving the browser.
Последнее рабочее решение
// client can see outputs if any ignore_user_abort(true); ob_start(); echo "success"; $buffer_size = ob_get_length(); session_write_close(); header("Content-Encoding: none"); header("Content-Length: $buffer_size"); header("Connection: close"); ob_end_flush(); ob_flush(); flush(); sleep(2); ob_start(); // client cannot see the result of code below