El búfer de salida de PHP no descarga




php header location); (4)

Tengo una serie de scripts que muestran el progreso mientras se ejecutan porque son de larga ejecución. Básicamente, cada uno hace lo siguiente al final de cada fila en bucle de datos procesados:

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

Esto funcionó perfectamente durante años y luego actualicé a PHP 5.3.x y Apache 2.2.x en varios servidores. Ahora, incluso si relleno el búfer con espacios en blanco o configuro "ob_implicit_flush (1)", no puedo mostrar el resultado en el comando.

Un servidor todavía muestra la salida, pero está en pedazos. Puede tomar casi 5 minutos y de repente aparece una cadena de puntos en la pantalla. Con los otros servidores, no obtengo nada hasta que el script termina de ejecutarse por completo.

Intenté buscar en los archivos php.ini y httpd.conf para ver si podía averiguar qué había cambiado entre los diferentes servidores, pero obviamente me falta algo.

También intenté deshabilitar mod_deflate en .htaccess para los scripts afectados, pero eso tampoco ayuda (desactivando mod_gzip utilizado para solucionar el problema de inmediato).

¿Puede alguien señalarme en la dirección correcta con esto, por favor? No poder monitorear la ejecución de scripts en tiempo real está causando todo tipo de problemas, pero no podemos seguir usando estas versiones anteriores de PHP.

En una nota al margen aún más peculiar, intenté degradar un servidor a PHP 5.2.17 pero el problema del buffer de salida permaneció después de la degradación. Esto me hace sospechar que se trata de algo relacionado con la forma en que Apache maneja la salida de PHP, ya que Apache 2 se dejó en su lugar.


ob_flush () (an flush ()) solo vacía el búfer PHP - el servidor web mantiene un buffer en sí mismo. Y por extraño que pueda parecer, enjuagar el buffer de forma anticipada en realidad disminuye el rendimiento en el servidor, por lo tanto, las versiones más recientes de apache se almacenan de forma más agresiva. También hay problemas terribles relacionados con la compresión y la representación parcial cuando se trabaja con codificación por fragmentos HTTP.

Si desea agregar contenido gradualmente a una página, utilice ajax o websockets para agregarlo a la vez.


Lo más probable es que el cambio descrito en la pregunta original sea que la nueva configuración estaba usando FastCgi ( http://www.fastcgi.com/mod_fastcgi/docs/mod_fastcgi.html ) y esto tiene el almacenamiento en búfer activado por defecto.

Pero hay otros factores para verificar:

Si está utilizando Fcgid, esto también tiene almacenamiento en búfer: http://httpd.apache.org/mod_fcgid/mod/mod_fcgid.html#fcgidoutputbuffersize

Si sus conjuntos de caracteres entre PHP y Apache no coinciden, esto puede mantener las cosas.

mod_deflate y mod_gzip también buffer (como se menciona en la pregunta original)

Entonces los pasos para verificar son:

  1. Vacíe el buffer PHP - (como se describe en la pregunta)

  2. Desactive el almacenamiento en búfer de Apache - Agregue php_value output_buffering off en .htaccess

  3. Deshabilitar las modificaciones que amortiguan la deflación - Deshabilita mod_deflate y mod_gzip

  4. Asegúrese de que la codificación de su char coincida entre PHP y Apache - agregue default_charset = "utf-8"; en php.ini y AddDefaultCharset utf-8 en httpd.conf)

  5. Inhabilite el almacenamiento en búfer en FastCgi o Fcgid: puede desactivar el almacenamiento en búfer en FastCgi agregando la opción -flush. Detalles en el enlace de arriba. Opción para Fcgid también enumerado arriba.

Hasta donde yo sé, esos son los únicos búferes en el servidor; obviamente, otros dispositivos entre el servidor y el navegador también pueden almacenar en búfer, por ejemplo, un proxy puede esperar que se proporcione la salida completa antes de pasarla. Fiddler ( https://www.telerik.com/fiddler ) hace esto, y frecuentemente me deja plantado hasta que lo recuerdo.


Existe una solución posible para este problema que no requiere que edite sus scripts existentes o modifique la configuración de su servidor para detener el almacenamiento en búfer. Mediante el uso de un script de contenedor puede iniciar su proceso de larga ejecución en segundo plano desde el script php que sirve la solicitud web. A continuación, puede canalizar la salida del proceso de ejecución larga en un archivo de texto que se puede leer fácilmente para encontrar el progreso actual del script mediante sondeo. Ejemplo a continuación:

Script de proceso de larga duración

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

Secuencia de comandos para inicializar el proceso de larga ejecución y ver la salida

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

Si envía una solicitud web a proc_watcher.php?action=start la secuencia de comandos debería iniciar el proceso de larga ejecución en segundo plano y luego devolver el contenido del archivo de salida al navegador web cada segundo.

El truco aquí es la línea de comandos nohup php ./long_process.php > ./output.txt & , que ejecuta el proceso en segundo plano y envía la salida a un archivo en lugar de STDOUT.


Este problema tiene más que ver con su servidor (apache) que con la versión de php.

Una opción es desactivar el almacenamiento en búfer de salida, aunque el rendimiento puede sufrir en otras partes del sitio

En Apache

Establezca la directiva php ini ( output_buffering=off ) desde la configuración del servidor, incluido un archivo .htaccess. Así que usé lo siguiente en un archivo .htaccess para desactivar el output_buffering solo para ese archivo:

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

Y luego, en la configuración de mi servidor estático, solo necesitaba AllowOverride Options=php_value (o un martillo más grande, como AllowOverride All ) para que eso se permitiera en un archivo .htaccess .

En Nginx

Para deshabilitar el almacenamiento en búfer para Nginx (agregue "proxy_buffering off;" al archivo de configuración y reinicie Nginx





apache