Выходной буфер PHP не промывается

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

echo '.'; @ob_flush(); flush(); 

Это работало отлично в течение многих лет, а затем я обновился до PHP 5.3.x и Apache 2.2.x на нескольких серверах. Теперь, даже если я набиваю буфер с пробелом или устанавливаю «ob_implicit_flush (1)», я не могу заставить его отображать вывод по команде.

Один сервер по-прежнему показывает выход, но он находится в кусках. Это может занять почти 5 минут, а затем на экране появляется строка точек. С другими серверами я ничего не получаю, пока скрипт не завершит выполнение целиком.

Я пробовал просматривать файлы php.ini и httpd.conf, чтобы узнать, могу ли я понять, что изменилось между разными серверами, но я, очевидно, что-то пропустил.

Я также попробовал отключить mod_deflate в .htaccess для затронутых скриптов, но это не помогает (отключив mod_gzip, который был использован для устранения проблемы сразу).

Может кто-нибудь указать мне в правильном направлении с этим, пожалуйста? Невозможность контролировать выполнение сценариев в реальном времени вызывает всевозможные проблемы, но мы не можем больше оставаться на этих более старых версиях PHP.

На еще более своеобразной стороне примечания я попытался понизить сервер до PHP 5.2.17, но проблема выходного буфера осталась после понижения. Это заставляет меня подозревать, что это связано с тем, как Apache обрабатывает выход PHP, поскольку Apache 2 остался на месте.

ob_flush () (flush ()) только очищает буфер PHP – веб-сервер поддерживает сам буфер. И, как ни странно, покраснение буфера на самом деле снижает пропускную способность сервера, поэтому более свежие версии буфера apache более агрессивно. Также существуют ужасающие проблемы, связанные с сжатием и частичным рендерингом при работе с кодировкой HTTP.

Если вы хотите постепенно добавлять контент на страницу, используйте ajax или websockets, чтобы добавить его понемногу.

Скорее всего, изменение, описанное в исходном вопросе, заключается в том, что новая настройка использовала FastCgi ( http://www.fastcgi.com/mod_fastcgi/docs/mod_fastcgi.html ), и эта буферизация включена по умолчанию.

Но есть и другие факторы для проверки:

Если вы используете Fcgid, то это также имеет буферизацию: http://httpd.apache.org/mod_fcgid/mod/mod_fcgid.html#fcgidoutputbuffersize

Если ваши кодировки между PHP и Apache не совпадают – это может занять все.

mod_deflate и mod_gzip также буфер (как упоминалось в исходном вопросе)

Итак, шаги для проверки:

  1. Очистите буфер PHP – (как описано в вопросе)

  2. Отключите буферизацию Apache – добавьте php_value output_buffering off в .htaccess

  3. Отключить моды, которые буферизуют для дефляции – отключить mod_deflate и mod_gzip

  4. Убедитесь, что ваша кодировка символов совпадает между PHP и Apache – добавьте default_charset = "utf-8"; в php.ini и AddDefaultCharset utf-8 в httpd.conf)

  5. Отключить буферизацию в FastCgi или Fcgid. Вы можете отключить буферизацию в FastCgi, добавив параметр -flush. Подробности в ссылке выше. Опция для Fcgid также указана выше.

Насколько я знаю, это единственные буферы на сервере; очевидно, что другие устройства между сервером и браузером также могут буферизировать, например, прокси может дождаться полного вывода, который должен быть предоставлен, прежде чем передавать его. Fiddler ( https://www.telerik.com/fiddler ) делает это, и он часто настигает меня, пока я не запомню.

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

Длительный сценарий процесса

 <?php // long_process.php echo "I am a long running process "; for ($i = 0; $i < 10; $i++) { echo "."; sleep(1); } echo " Processing complete"; ?> 

Скрипт для инициализации продолжительного процесса и просмотра вывода

 <?php // proc_watcher.php $output = './output.txt'; if ($_GET['action'] == 'start') { echo 'starting running long process<br>'; $handle = popen("nohup php ./long_process.php > $output &", 'r'); pclose($handle); } else { echo 'Progress at ' . date('H:i:s') . '<br>'; echo file_get_contents($output); } $url = 'proc_watcher.php'; ?> <script> window.setTimeout(function() { window.location = '<?php echo $url;?>'; }, 1000); </script> 

Если вы отправляете веб-запрос proc_watcher.php?action=start скрипт должен запустить длительный процесс в фоновом режиме, а затем возвращать содержимое выходного файла в веб-браузер каждую секунду.

Трюк здесь – это командная строка nohup php ./long_process.php > ./output.txt & , которая запускает процесс в фоновом режиме и отправляет вывод в файл вместо STDOUT.

Эта проблема больше связана с вашим сервером (apache), а не с версией php.

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

На Apache

Задайте директиву php ini ( output_buffering=off ) из конфигурации вашего сервера, включая файл .htaccess. Поэтому я использовал следующее в файле .htaccess чтобы отключить output_buffering только для этого одного файла:

 <Files "q.php"> php_value output_buffering Off </Files> 

И тогда в моей конфигурации статического сервера мне просто нужны AllowOverride Options=php_value (или более крупный молот, например AllowOverride All ), чтобы это было разрешено в файле .htaccess .

На Nginx

Отключить буферизацию для Nginx (добавить «proxy_buffering off» в конфигурационный файл и перезапустить Nginx

Я попробовал все, чтобы это работало, включая все известные настройки, перечисленные выше. Я пытаюсь использовать PHP для работы с фрагментированными видеофайлами с использованием HTTP_RANGE, и он не работает.

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

 <?php // Close open sessions session_write_close(); // Turn off apache-level compression @apache_setenv('no-gzip', 1); // Turn off compression @ini_set('zlib.output_compression', 0); // Turn error reporting off @ini_set('error_reporting', E_ALL & ~ E_NOTICE); // Tell browser not to cache this header("Cache-Control: no-cache, must-revalidate"); // close any existing buffers while (ob_get_level()) ob_end_clean(); // Set this to whatever you like $buffer = 8096; for($i = 1; $i <= 8; $i++) { // Start a output buffer with specified size ob_start(null,$buffer,PHP_OUTPUT_HANDLER_FLUSHABLE); // Output exactly one byte more than that size // \n == 2 bytes, so 8096-1+2 = 8097 echo str_repeat('=', $buffer-1)."\n"; // 0.25s nap usleep(250000); // End output buffering and flush it ob_end_flush(); flush(); } 

Надеюсь, это поможет кому-то!