Подтвердить что ты не робот

Выходной буфер 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 остался на месте.

4b9b3361

Ответ 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();
}

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