тесная связь ранних
Я пытаюсь сделать вызов AJAX (через JQuery), который инициирует довольно длительный процесс. Я бы хотел, чтобы скрипт просто отправил ответ, указывающий, что процесс запущен, но JQuery не вернет ответ, пока скрипт PHP не будет запущен.
Я пробовал это с заголовком "закрыть" (ниже), а также с буферизацией вывода; ни один из них не работает. Какие-нибудь догадки? или это то, что мне нужно сделать в 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.' );
?>
19 ответов:
Следующая страница руководства PHP (ВКЛ. user-notes) предлагает несколько инструкций о том, как закрыть TCP-соединение с браузером без завершения PHP-скрипта:
предположительно, это требует немного больше, чем отправка закрыть заголовок.
OP затем подтверждает:да, это сделал трюк: указывая на пользователя-Примечание #71172 (Nov 2006) скопировать здесь:
закрытие соединения с браузером пользователей при сохранении вашего PHP-скрипта было проблемой с [PHP] 4.1, когда поведение
register_shutdown_function()
был изменен таким образом, что он не будет автоматически закрывать соединение пользователей.sts в 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 часа, пытаясь понять это, надеюсь, что это поможет кому-то :)
проверен в:
- 7.5730.11 т. е.
- Mozilla Firefox 1.81
позже, в июле 2010 года в обзоры ответаАрктика Огонь затем связал еще две заметки пользователя, которые были-последующие действия к одному выше:
необходимо отправить эти 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), это не будет работать, потому что фактический размер вывода изменяется, и длина содержимого больше не является точной. Отключите сжатие 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:
fastcgi_finish_request ()
объем: функция php Категория: Оптимизация
эта функция позволяет ускорить реализацию некоторых запросов php. Ускорение возможно при наличии действий в процессе выполнения скрипта, которые не влияют на ответ сервера. Например, сохранение сессии в memcached может произойти после того, как страница сформирована и прошла на веб-сервер.
fastcgi_finish_request()
- это функция php, которая останавливает вывод ответа. Веб-сервер сразу же начинает передавать ответ" медленно и печально " клиенту, а php в то же время может делать много полезных вещей в контексте запроса, таких как сохранение сеанса, преобразование загруженного видео, обработка всех видов статистики и т. д.
fastcgi_finish_request()
может вызвать выполнение функции отключения.
если у вас есть сервер Linux и корневой доступ, попробуйте это. Это самое простое решение, которое я нашел.
создайте новый каталог для следующих файлов и дайте ему полные разрешения. (Мы можем сделать его более безопасным позже.)
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. Он будет пинговать 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 ********/
лучшим решением является развилка фонового процесса. Это довольно прямо вперед на unix / linux:
<?php echo "We'll email you as soon as this is done."; system("php somestuff.php dude@thatplace.com >/dev/null &"); ?>
вы должны посмотреть на этот вопрос для лучших примеров:
вы можете попробовать сделать многопоточность.
вы можете создать скрипт, который делает системный вызов ( используя shell_exec), который вызывает PHP-двоичный файл со скриптом для выполнения вашей работы в качестве параметра. Но я не думаю, что это самый безопасный способ. Может быть, вы можете улучшить материал, укореняя процесс php и другие вещи
кроме того, есть класс в phpclasses, которые делают это http://www.phpclasses.org/browse/package/3953.html. но Я не знаю специфики реализации
Я на общем Хосте и
fastcgi_finish_request
настраивается для полного выхода из скриптов. Мне не нравитсяconnection: close
выход. С его помощью принудительно устанавливается отдельное соединение для последующих запросов, что требует дополнительных ресурсов сервера. Я прочиталTransfer-Encoding: cunked
Статья В Википедии и узнал, что0\r\n\r\n
завершает ответ. Я не полностью протестировал это в разных версиях браузеров и устройствах, но он работает на всех 4 моих текущих браузерах.// 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); }
Joeri Sebrechts' ответ близко, но он уничтожает любое существующее содержимое, которое может быть буферизовано, прежде чем вы захотите отключить. Он не вызывает
ignore_user_abort
правильно, что позволяет скрипту завершиться преждевременно. diyism это хорошо, но не является общеприменимым. Например, у человека может быть больше или меньше выходных буферов, которые этот ответ не обрабатывает, поэтому он может просто не работать в вашей ситуации, и вы не будете знать, почему.эта функция позволяет отключаться в любое время (пока заголовки еще не отправлены) и сохраняет контент, который вы создали до сих пор. Дополнительное время обработки по умолчанию не ограничено.
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(); }
Если вам тоже нужна дополнительная память, выделите ее перед вызовом этой функции.
Примечание для пользователей mod_fcgid (пожалуйста, используйте на свой страх и риск).
Быстрый Решение
принятый ответ Joeri Sebrechts действительно функциональной. Однако, если вы используете mod_fcgid вы можете обнаружить, что это решение не работает на свой собственный. Другими словами, когда flush функция называется соединение с клиентом не закрывается.
The
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буферизация есть еще одно решение... вы можете заставить этот буфер для промывки.
приведенный ниже код делает именно это, опираясь на решение, предложенное Joeri Sebrechts:
<?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 для соответствующей директивы. Следовательно, вам может потребоваться соответствующим образом настроить это значение, если в вашей среде установлено другое значение.Мое Окружение
- WampServer 2.5
- Apache 2.4.9
- PHP 5.5.19 VC11, x86, Non Thread Safe
- mod_fcgid / 2.3.9
- Windows 7 Professional x64
Пример Виртуального Хоста
<VirtualHost *:80> DocumentRoot "d:/wamp/www/example" ServerName example.local FcgidOutputBufferSize 0 <Directory "d:/wamp/www/example"> Require all granted </Directory> </VirtualHost>
это работает для меня
//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);
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.
ок, так что в основном так, как jQuery делает запрос XHR, даже метод ob_flush не будет работать, потому что вы не можете запустить функцию на каждом onreadystatechange. jQuery проверяет состояние,а затем выбирает правильные действия (complete,error,success, timeout). И хотя я не смог найти ссылку, я помню, что слышал, что это не работает со всеми реализациями 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>
и потому, что сценарии выполняются встраиваемые, как буферы сбрасываются, вы получаете выполнение. Чтобы сделать это полезным, измените консоль.войдите в метод обратного вызова, определенный в настройке основного скрипта, чтобы получить данные и действовать по ним. Надеюсь, это поможет. Твое Здоровье, Морган.
альтернативным решением является добавление задания в очередь и создание скрипта cron, который проверяет наличие новых заданий и запускает их.
недавно мне пришлось это сделать, чтобы обойти ограничения, наложенные общим хостом-exec () et al был отключен для PHP, запущенного веб-сервером, но мог работать в сценарии оболочки.
ваша проблема может быть решена путем выполнения параллельного программирования на PHP. Я задал вопрос об этом несколько недель назад здесь: как можно использовать многопоточность в PHP-приложениях
и получил отличные ответы. Особенно мне понравился один из них. Писатель сделал ссылку до простая параллельная обработка в PHP (Sep 2008; by johnlim) учебник который на самом деле может решить вашу проблему очень хорошо, как я уже использовал его, чтобы иметь дело с a аналогичная проблема возникла пару дней назад.
Если
flush()
функция не работает. Вы должны установить следующие параметры в php.ini как:output_buffering = Off zlib.output_compression = Off
Последнее Рабочее Решение
// 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
попробовав много разных решений из этой темы (После того, как ни один из них не работал для меня), я нашел решение на официальном сайте PHP.net страница:
function sendResponse($response) { ob_end_clean(); header("Connection: close\r\n"); header("Content-Encoding: none\r\n"); ignore_user_abort(true); ob_start(); echo $response; // Actual response that will be sent to the user $size = ob_get_length(); header("Content-Length: $size"); ob_end_flush(); flush(); if (ob_get_contents()) { ob_end_clean(); } }