cerrar una conexión temprano

Estoy intentando hacer una llamada AJAX (a través de JQuery) que iniciará un proceso bastante largo. Me gustaría que el script simplemente envíe una respuesta indicando que el proceso ha comenzado, pero JQuery no devolverá la respuesta hasta que el script PHP se haya ejecutado.

Intenté esto con un encabezado “cerrado” (abajo), y también con el buffer de salida; ninguno parece funcionar. ¿Alguna suposición? o es algo que necesito hacer en JQuery?

 

La siguiente página de manual de PHP (incluidas las notas de usuario) sugiere varias instrucciones sobre cómo cerrar la conexión TCP al navegador sin finalizar el script PHP:

  • Documentos de manejo de conexión

Supuestamente requiere un poco más que enviar un encabezado cerrado.


OP luego confirma: Sí, esto lo hizo: apuntando a la nota de usuario # 71172 (noviembre de 2006) copiada aquí:

Cerrar la conexión del navegador de los usuarios manteniendo su script php en ejecución ha sido un problema desde [PHP] 4.1, cuando se modificó el comportamiento de register_shutdown_function() para que no cerrara automáticamente la conexión de los usuarios.

pts en el correo punto xubion punto hu Publicado la solución original:

  

Lo cual funciona bien hasta que sustituyas phpinfo() por echo('text I want user to see'); en cuyo caso los encabezados nunca se envían!

La solución es desactivar explícitamente el almacenamiento en búfer de salida y borrar el búfer antes de enviar su información de encabezado. Ejemplo:

  

Acabo de pasar 3 horas tratando de resolver esto, espero que ayude a alguien 🙂

Probado en:

  • IE 7.5730.11
  • Mozilla Firefox 1.81

Más adelante, en julio de 2010, en una respuesta relacionada con Arctic Fire, se vincularon dos notas de usuario adicionales que fueron seguidas del anterior:

  • Manejo de la conexión user-note # 89177 (Feb 2009)
  • Nota de usuario de manejo de conexiones # 93441 (Sep 2009)

Es necesario enviar estos 2 encabezados:

 Connection: close Content-Length: n (n = size of output in bytes ) 

Ya que necesita saber el tamaño de su salida, necesitará almacenar en búfer su salida, luego enjuagarlo en el navegador:

 // 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 ********/ 

Además, si su servidor web está utilizando la compresión gzip automática en la salida (es decir, Apache con mod_deflate), esto no funcionará porque se cambia el tamaño real de la salida, y la longitud del contenido ya no es precisa. Deshabilita la compresión gzip del script en particular.

Para obtener más información, visite http://www.zulius.com/how-to/close-browser-connection-continue-execution

Versión completa:

 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(); 

Puede usar Fast-CGI con PHP-FPM para usar la función fastcgi_end_request() . De esta forma, puede continuar procesando mientras la respuesta ya ha sido enviada al cliente.

  • ejemplo de cómo usar fastcgi_finish_request () (nov 2010)

Aquí encontrará esto en el manual de PHP: FastCGI Process Manager (FPM) ; Pero esa función específicamente no está más documentada en el manual. Aquí el extracto de PHP-FPM: PHP FastCGI Process Manager Wiki :


fastcgi_finish_request ()

Alcance: función php Categoría: Optimización

Esta característica le permite acelerar la implementación de algunas consultas php. La aceleración es posible cuando hay acciones en el proceso de ejecución de scripts que no afectan la respuesta del servidor. Por ejemplo, guardar la sesión en memcached puede ocurrir después de que la página se haya formado y pasado a un servidor web. fastcgi_finish_request() es una función php que detiene la salida de respuesta. El servidor web inmediatamente comienza a transferir la respuesta “lenta y tristemente” al cliente, y php al mismo tiempo puede hacer muchas cosas útiles en el contexto de una consulta, como guardar la sesión, convertir el video descargado, manejar todo tipo de estadísticas, etc.

fastcgi_finish_request() puede invocar la ejecución de la función de apagado.

Suponiendo que tiene un servidor Linux y acceso de root, intente esto. Es la solución más simple que he encontrado.

Crea un nuevo directorio para los siguientes archivos y dale permisos completos. (Podemos hacerlo más seguro más tarde).

 mkdir test chmod -R 777 test cd test 

Pon esto en un archivo llamado bgping .

 echo starting bgping ping -c 15 www.google.com > dump.txt & echo ending bgping 

Tenga en cuenta el & . El comando ping se ejecutará en segundo plano mientras el proceso actual se mueve al comando echo. Realizará ping a http://www.google.com 15 veces, lo que le llevará unos 15 segundos.

Hazlo ejecutable.

 chmod 777 bgping 

Pon esto en un archivo llamado bgtest.php .

  

Cuando solicite bgtest.php en su navegador, debe obtener la siguiente respuesta rápidamente, sin esperar alrededor de 15 segundos para que se complete el comando ping.

 start bgtest.php output:Array ( [0] => starting bgping [1] => ending bgping ) result:0 end bgtest.php 

El comando ping ahora debería estar ejecutándose en el servidor. En lugar del comando ping, puede ejecutar un script PHP:

 php -n -f largejob.php > dump.txt & 

¡Espero que esto ayude!

Aquí hay una modificación al código de Timbo que funciona con compresión 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 ********/ 

Podrías intentar hacer multihilo.

podría crear un script que haga una llamada al sistema (usando shell_exec ) que llame al binario php con el script para hacer su trabajo como parámetro. Pero no creo que sea la forma más segura. Tal vez puedas mejorar las cosas cerrando el proceso de php y otras cosas

Alternativamente, hay una clase en phpclasses que hace eso http://www.phpclasses.org/browse/package/3953.html . Pero no sé los detalles de la implementación

Una mejor solución es bifurcar un proceso en segundo plano. Es bastante sencillo en Unix / Linux:

 /dev/null &"); ?> 

Debería ver esta pregunta para obtener mejores ejemplos:

PHP ejecuta un proceso en segundo plano

La respuesta de Joeri Sebrechts está cerca, pero destruye cualquier contenido existente que pueda almacenarse en memoria intermedia antes de que desee desconectarse. No llama a ignore_user_abort correctamente, lo que permite que el script finalice prematuramente. la respuesta del diyismo es buena pero no es genéricamente aplicable. Por ejemplo, una persona puede tener más o menos buffers de salida que esa respuesta no maneja, por lo que simplemente no puede funcionar en su situación y no sabrá por qué.

Esta función le permite desconectarse en cualquier momento (siempre que los encabezados no se hayan enviado aún) y retiene el contenido que ha generado hasta el momento. El tiempo de procesamiento adicional es ilimitado por defecto.

 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(); } 

Si necesita memoria adicional, también, asignela antes de llamar a esta función.

esto funcionó para mí

 //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); 

Ok, básicamente la forma en que jQuery hace la solicitud de XHR, incluso el método ob_flush no funcionará porque no puedes ejecutar una función en cada onreadystatechange. jQuery verifica el estado, luego elige las acciones adecuadas a tomar (completar, error, éxito, tiempo de espera). Y aunque no pude encontrar una referencia, recuerdo haber escuchado que esto no funciona con todas las implementaciones de XHR. Un método que creo que debería funcionar para usted es un cruce entre las encuestas ob_flush y para siempre.

 {$str}"; }; 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');"); ?>      

Y como los scripts se ejecutan en línea, a medida que se vacian los búfers, se obtiene la ejecución. Para que esto sea útil, cambie console.log a un método de callback definido en la configuración principal de su script para recibir datos y actuar sobre ellos. Espero que esto ayude. Saludos, Morgan.

Una solución alternativa es agregar el trabajo a una cola y crear un script cron que busque nuevos trabajos y los ejecute.

Tuve que hacerlo de esa manera recientemente para eludir los límites impuestos por un host compartido – exec () et al se deshabilitó para PHP ejecutado por el servidor web pero podría ejecutarse en un script de shell.

Su problema puede resolverse haciendo una progtwigción paralela en php. Hice una pregunta al respecto hace unas semanas: ¿Cómo se puede usar multi threading en aplicaciones PHP?

Y obtuve excelentes respuestas. Me gustó mucho uno en particular. El escritor hizo una referencia al tutorial Easy Parallel Processing en PHP (septiembre de 2008; por johnlim) que realmente puede resolver su problema muy bien, ya que lo he usado ya para hacer frente a un problema similar que surgió hace un par de días.

Nota para los usuarios de mod_fcgid (por favor, use bajo su propio riesgo).

Solución rápida

La respuesta aceptada de Joeri Sebrechts es efectivamente funcional. Sin embargo, si usa mod_fcgid , puede encontrar que esta solución no funciona por sí misma. En otras palabras, cuando se llama a la función de descarga , la conexión con el cliente no se cierra.

El parámetro de configuración FcgidOutputBufferSize de mod_fcgid puede ser el culpable. Encontré este consejo en:

  1. esta respuesta de Travers Carter y
  2. esta publicación de blog de Seumas Mackinnon .

Después de leer lo anterior, puede llegar a la conclusión de que una solución rápida sería agregar la línea (consulte “Ejemplo de host virtual” al final):

 FcgidOutputBufferSize 0 

en su archivo de configuración de Apache (por ejemplo, httpd.conf), su archivo de configuración FCGI (por ejemplo, fcgid.conf) o en su archivo de hosts virtuales (por ejemplo, httpd-vhosts.conf).

En (1) arriba, se menciona una variable llamada “OutputBufferSize”. Este es el nombre anterior del FcgidOutputBufferSize mencionado en (2) (consulte las notas de actualización en la página web de Apache para mod_fcgid ).

Detalles y una segunda solución

La solución anterior desactiva el almacenamiento en búfer realizado por mod_fcgid para todo el servidor o para un host virtual específico. Esto podría llevar a una penalización de rendimiento para su sitio web. Por otro lado, este puede no ser el caso, ya que PHP realiza el almacenamiento en búfer por sí mismo.

En caso de que no desee deshabilitar el almacenamiento en búfer de mod_fcgid , hay otra solución … puede forzar que este búfer se descargue .

El siguiente código hace justamente eso basándose en la solución propuesta por Joeri Sebrechts:

  

Lo que la línea de código añadida esencialmente hace es llenar el buffer de mod_fcgi , forzándolo así a enjuagarse. Se eligió el número “65537” porque el valor predeterminado de la variable FcgidOutputBufferSize es “65536”, como se menciona en la página web de Apache para la directiva correspondiente . Por lo tanto, puede necesitar ajustar este valor en consecuencia si se establece otro valor en su entorno.

Mi entorno

  • WampServer 2.5
  • Apache 2.4.9
  • PHP 5.5.19 VC11, x86, sin hilos de seguridad
  • mod_fcgid / 2.3.9
  • Windows 7 Professional x64

Ejemplo de host virtual

  DocumentRoot "d:/wamp/www/example" ServerName example.local FcgidOutputBufferSize 0  Require all granted   

Estoy en un host compartido y fastcgi_finish_request está configurado para salir de los scripts por completo. No me gusta la connection: close solución connection: close tampoco. Su uso obliga a una conexión separada para solicitudes posteriores, lo que cuesta recursos de servidor adicionales. Leí Transfer-Encoding: cunked Wikipedia Article y Transfer-Encoding: cunked que 0\r\n\r\n termina una respuesta. No he probado exhaustivamente esto en todas las versiones y dispositivos de los navegadores, pero funciona en los 4 navegadores actuales.

 // 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); } 

Si la función flush() no funciona. Debe configurar las siguientes opciones en php.ini como:

 output_buffering = Off zlib.output_compression = Off 

TL; DR Respuesta:

 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. 

Respuesta de la función:

 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. 

La última solución de trabajo

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