Выходной буфер PHP не промывается
У меня есть несколько сценариев, которые повторяют прогресс, когда они выполняются, потому что они долго работают. Каждый из них в конце каждого цикла обрабатываемых строк обрабатывает следующее:
echo '.';
@ob_flush();
flush();
Это работало отлично в течение многих лет, а затем я обновился до PHP 5.3.x и Apache 2.2.x на нескольких серверах. Теперь, даже если я набиваю буфер с пробелом или устанавливаю "ob_implicit_flush (1)", я не могу заставить его отображать вывод по команде.
Один сервер по-прежнему показывает вывод, но он находится в кусках. Это может занять почти 5 минут, а затем на экране появляется строка точек. С другими серверами я ничего не получаю, пока script не закончит выполнение полностью.
Я пробовал просматривать файлы php.ini и httpd.conf, чтобы узнать, могу ли я понять, что изменилось между разными серверами, но я, очевидно, что-то пропустил.
Я также попытался отключить mod_deflate в .htaccess для затронутых скриптов, но это тоже не помогает (отключив mod_gzip, чтобы устранить проблему сразу).
Может ли кто-нибудь указать мне в правильном направлении с этим, пожалуйста? Невозможность отслеживать выполнение script в реальном времени вызывает всевозможные проблемы, но мы больше не можем оставаться на этих более старых версиях PHP.
В еще более своеобразной заметке я попытался понизить сервер до PHP 5.2.17, но проблема с выходным буфером осталась после понижения. Это заставляет меня подозревать, что это связано с тем, как Apache обрабатывает выход PHP, поскольку Apache 2 остался на месте.
Ответы
Ответ 1
ob_flush() (flush()) только очищает буфер PHP - веб-сервер поддерживает сам буфер. И, как ни странно, покраснение буфера на самом деле снижает пропускную способность сервера, поэтому более свежие версии буфера apache более агрессивно. Там также ужасающие проблемы, связанные с сжатием и частичным рендерингом при работе с кодировкой HTTP chunked.
Если вы хотите постепенно добавлять контент на страницу, используйте ajax или websockets, чтобы добавить его немного за раз.
Ответ 2
Скорее всего, изменение, описанное в исходном вопросе, заключается в том, что новая настройка использовала 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 также буфер (как указано в исходном вопросе)
Итак, шаги для проверки:
-
Сбросить буфер PHP -
(как описано в вопросе)
-
Отключите буферизацию Apache -
Добавить php_value output_buffering off
в .htaccess
-
Отключить моды, которые буферизуют для дефляции -
Отключить mod_deflate и mod_gzip
-
Убедитесь, что ваша кодировка char совпадает между PHP и Apache -
добавьте default_charset = "utf-8";
в php.ini и AddDefaultCharset utf-8
в httpd.conf)
-
Отключить буферизацию в FastCgi или Fcgid -
Вы можете отключить буферизацию в FastCgi, добавив параметр -flush. Подробности в ссылке выше. Опция для Fcgid также указана выше.
Насколько я знаю, это единственные буферы на сервере; очевидно, что другие устройства между сервером и браузером также могут буферироваться, например. прокси-сервер может дождаться полного вывода, который должен быть предоставлен, прежде чем передавать его. Fiddler (https://www.telerik.com/fiddler) делает это, и он часто настигает меня, пока я не запомню.
Ответ 3
В этой проблеме есть возможная работа, которая не требует изменения существующих сценариев или изменения конфигурации сервера для остановки вывода буферизации. Используя обертку script, вы можете запустить свой длительный процесс в фоновом режиме с PHP скрипт, обслуживающего веб-запрос. Затем вы можете вывести вывод длинного процесса в текстовый файл, который можно легко прочитать, чтобы найти текущий ход script через опрос. Пример ниже:
Длительный процесс script
<?php
// long_process.php
echo "I am a long running process ";
for ($i = 0; $i < 10; $i++) {
echo ".";
sleep(1);
}
echo " Processing complete";
?>
Script для инициализации продолжительного процесса и просмотра вывода
<?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
, script должен запустить длительный процесс в фоновом режиме и затем каждый раз возвращать содержимое выходного файла в веб-браузер.
Трюк здесь - это командная строка nohup php ./long_process.php > ./output.txt &
, которая запускает процесс в фоновом режиме и отправляет вывод в файл вместо STDOUT.
Ответ 4
Эта проблема больше связана с вашим сервером (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
Ответ 5
Я попробовал все, чтобы это работало, включая все известные настройки, перечисленные выше. Я пытался использовать PHP для работы с фрагментированными видеофайлами с использованием HTTP_RANGE, и он не работал.
После того, как вытащил большую часть моих волос, я нашел ответ: вам нужно вывести хотя бы один байт больше, чем размер буфера, чтобы он мог выводиться в браузер. Здесь script, который закончился для меня:
<?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();
}
Надеюсь, это поможет кому-то!