Intereting Posts
Ошибка Codeigniter «Не удается подключиться к серверу базы данных с помощью предоставленных настроек» Почему состояние флажка не всегда передается скрипту PHP? Получать пользовательские данные с помощью токена доступа в приложении клиента паспорта laravel Только один (первый) переключатель в группе выбирается / активен Colon после объявления метода? PHP – завершение сеанса после X минут Как добавить пользовательские шрифты в TCPDF? Запретить URL-адрес URL-адреса из htaccess rewrite Cant't запрос данных json в laravel 5.2 PHP: запуск запланированных заданий (задания cron) Как обрабатывать push-уведомления API Google Calendar? получение пустых результатов массива с использованием preg_match_all для значений, которые не соответствуют Как сделать form_rest () не отображать поле с Symfony2? Доступ к API REST с использованием имени пользователя, пароля и подписи Paypal Laravel 5.3 хранить и читать каталоги файлов

закрыть соединение раньше

Я пытаюсь выполнить вызов 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 связал еще два пользовательских заметки, которые были следующими:

  • Замечание пользователя по подключению # 89177 (февраль 2009 г.)
  • Замечание пользователя по подключению # 93441 (сентябрь 2009 г.)

Необходимо отправить эти 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() . Таким образом, вы можете продолжать выполнять некоторую обработку, пока ответ уже отправлен клиенту.

  • пример использования fastcgi_finish_request () (ноябрь 2010 г.)

Вы найдете это в руководстве по 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 и 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 . Я нашел этот совет в:

  1. этот ответ Траверса Картера и
  2. это сообщение в блоге Seumas Mackinnon .

После прочтения выше вы можете прийти к выводу, что быстрым решением было бы добавить строку (см. «Пример виртуального хоста» в конце):

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

Моя окружающая среда

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

Я нахожусь на общем хосте, и 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