2010-09-30 63 views
57

¿Cómo puedo tener PHP 5.2 (que funciona como Apache mod_php) enviar una respuesta HTTP completa al cliente, y luego seguir la ejecución de las operaciones durante un minuto más?continuar la ejecución de PHP después de enviar la respuesta HTTP

La larga historia:

Tengo un script PHP que tiene que ejecutar algunas peticiones larga base de datos y enviar correo electrónico, que tiene de 45 a 60 segundos para correr. Este script es llamado por una aplicación que no tengo control. Necesito que la aplicación informe cualquier mensaje de error recibido del script PHP (la mayoría de los errores de parámetros no son válidos).

La aplicación tiene un retardo de tiempo de espera más corto de 45 segundos (no sé el valor exacto) y, por tanto, registra cada ejecución del script PHP como un error. Por lo tanto, necesito PHP para enviar la respuesta HTTP completa al cliente lo más rápido posible (idealmente, tan pronto como se hayan validado los parámetros de entrada), y luego ejecutar la base de datos y el procesamiento del correo electrónico.

estoy corriendo mod_php, por lo pcntl_fork no está disponible. Podría solucionar este problema guardando los datos para procesarlos en la base de datos y ejecutar el proceso real desde cron, pero estoy buscando una solución más corta.

+4

Lo sentimos, pero esto parece ser un mal uso total del lenguaje PHP. –

+2

No tanto como el mal uso del lenguaje PHP como el mal uso de un proceso de servidor web. Si ya no está involucrado HTTP/web, ningún servidor web debería estar ocupado con él. – Wrikken

+0

@Wrikken ¡De acuerdo! :) –

Respuesta

24

Haga que la secuencia de comandos que maneja la solicitud inicial cree una entrada en una cola de procesamiento y luego regrese de inmediato. Luego, cree un proceso separado (a través de cron quizás) que ejecute regularmente los trabajos pendientes en la cola.

+0

Esta es la solución que originalmente tenía en mente. Por otro lado, configurar una cola de procesamiento con el único propósito de trabajar alrededor de un tiempo de espera en una aplicación de terceros me hace sentir un poco incómodo. –

+0

Esto es lo que terminé haciendo, por falta de una solución portátil basada en horquillas ... –

+0

Esta solución adolece de falta de paralelismo ... o será necesario iniciar un grupo de procesos de trabajo para servir a la cola. Terminé publicando y luego desconectando las solicitudes http a self-localhost (de la manera descrita por SomeGuy aquí) para utilizar un conjunto de trabajadores httpd existentes como procesadores en segundo plano. –

5

¿Qué tal si llamamos a una secuencia de comandos en el servidor de archivos para que se ejecute como si se hubiera activado en la línea de comandos? Puede hacer esto con PHP exec.

+0

+1, algo así como 'Gearman' ya está configurado para eso (pero las soluciones de los otros/por supuesto son igualmente válidas). – Wrikken

+1

exec() suele ser un problema en espacios compartidos/alojados. Además de un gran riesgo de seguridad. – ErnestV

4

Puede utilizar la función PHP register-shutdown-function que ejecutará algo después de el script ha completado su diálogo con el navegador.

Ver también ignore_user_abort - pero que no deberían tener esta función si utiliza la register_shutdown_function. En la misma página, set_time_limit(0) evitará que su secuencia de comandos caduque.

+0

Aparentemente, de acuerdo con los documentos, se llama a register_shutdown_function antes de que el script haya completado el diálogo desde 4.1.0. Su otro enlace, sin embargo, contiene un comentario prometedor: http://www.php.net/manual/en/features.connection-handling.php # 89177 Trataré de profundizar en esto e informar aquí. –

0

Bah, he entendido mal sus requisitos. Parece que en realidad son:

  • script recibe la entrada desde una fuente externa que no controlas
  • procesos guión y valida la entrada, y permite que la aplicación externa saber si son buenos o no, y termina el sesión.
  • Script inicia un proceso de larga ejecución.

En este caso, entonces sí, usar una cola de trabajos externa y/o cron funcionaría. Después de validar la entrada, inserte los detalles del trabajo en la cola y salga. A continuación, se puede ejecutar otro script, recoger los detalles del trabajo de la cola e iniciar el proceso más largo. Alex Howansky tiene la idea correcta.

Lo siento, admito que he desnatado un poco la primera vez.

0

Puede dividir estas funciones en tres secuencias de comandos. 1. Inicie el proceso y llame al segundo a través de exec o command, esto también se puede ejecutar a través de una llamada http. 2. el segundo ejecutará el procesamiento de la base de datos y al final se iniciará el último 3.último enviará por correo electrónico

6

Lo que necesita es este tipo de configuración

alt text

+0

Umm, pero, según este diagrama, el mensaje de estado se envía de vuelta al cliente solo cuando se ejecuta cron: 5-10 minutos como máximo. De todos modos, buen diagrama! –

+0

mensajes de estado podrían ser solicitados en cualquier momento :) el punto era que aquí hay dos procesos separados e independientes. Pero de lo contrario, ¡gracias! –

+1

+1 ¡Guau, gran diagrama! Pero en lugar de que el usuario solicite continuamente el estado, creo que los websockets son mejores. – Oriol

0

recomendaría desove una nueva solicitud asíncrona al final, en lugar de seguir el proceso con el usuario.

Usted puede generar la otra solicitud a través de la respuesta aquí: Asynchronous PHP calls?

3

El uso de una cola, o ejecutivo de cron sería una exageración para esta tarea simple. No hay ninguna razón para no permanecer dentro del mismo script. Esta combinación funcionó muy bien para mí:

 ignore_user_abort(true); 
     $response = "some response"; 
     header("Connection: close"); 
     header("Content-Length: " . mb_strlen($response)); 
     echo $response; 
     flush(); // releasing the browser from waiting 
     // continue the script with the slow processing here... 

leer más en: How to continue process after responding to ajax request in PHP?

+1

Es posible que deba desactivar el almacenamiento en búfer adicional que se produce en Apache: '' ' '' ' –

6

Una lata de usar "tenedor http" a sí mismo oa cualquier otro script. Me refiero a algo como esto:

// parent sript, called by user request from browser 

// create socket for calling child script 
$socketToChild = fsockopen("localhost", 80); 

// HTTP-packet building; header first 
$msgToChild = "POST /sript.php?&param=value&<more params> HTTP/1.0\n"; 
$msgToChild .= "Host: localhost\n"; 
$postData = "Any data for child as POST-query"; 
$msgToChild .= "Content-Length: ".strlen($postData)."\n\n"; 

// header done, glue with data 
$msgToChild .= $postData; 

// send packet no oneself www-server - new process will be created to handle our query 
fwrite($socketToChild, $msgToChild); 

// wait and read answer from child 
$data = fread($socketToChild, $dataSize); 

// close connection to child 
fclose($socketToChild); 
... 

Ahora el guión niño:

// parse HTTP-query somewhere and somehow before this point 

// "disable partial output" or 
// "enable buffering" to give out all at once later 
ob_start(); 

// "say hello" to client (parent script in this case) disconnection 
// before child ends - we need not care about it 
ignore_user_abort(1); 

// we will work forever 
set_time_limit(0); 

// we need to say something to parent to stop its waiting 
// it could be something useful like client ID or just "OK" 
... 
echo $reply; 

// push buffer to parent 
ob_flush(); 

// parent gets our answer and disconnects 
// but we can work "in background" :) 
... 

La idea principal es: la escritura

  • padre llamado a petición del usuario;
  • padre llama al script hijo (igual que el padre u otro) en el mismo servidor (o cualquier otro servidor) y les proporciona datos de solicitud;
  • padre dice que está bien para el usuario y termina;
  • niño trabaja.

Si necesita interactuar con un elemento secundario, puede utilizar DB como "medio de comunicación": el elemento primario puede leer el estado secundario y escribir comandos, el niño puede leer comandos y escribir el estado. Si lo necesita para varios scripts secundarios, debe mantener el identificador secundario en el lado del usuario para discriminarlos y enviar ese ID al padre cada vez que desee verificar el estado del hijo respectivo.

He encontrado que aquí - http://linuxportal.ru/forums/index.php/t/22951/

+0

Este enfoque (ligeramente modificado) es la única solución de trabajo que encontré para crear una tarea en segundo plano desde mod_php de apache _sin_ gastos generales de iniciar un proceso de SO separado; esto ocupará y usará uno de los trabajadores httpd ya existentes en su lugar –

+0

este enfoque me funciona, ¡increíble! – user3896501

+0

En el script del padre 'fread ($ socketToChild, $ dataSize)', ¿de dónde viene '$ dataSize'? ¿Necesita saber exactamente cuántos datos esperar del zócalo (incluido el tamaño de los encabezados)? Debo estar perdiendo algo. – BeetleJuice

45

tuve este fragmento en mi caja de herramientas "secuencias de comandos especiales", pero se perdió (nubes no eran comunes en ese entonces), por lo que estaba en busca de ella y se le ocurrió con esta pregunta, sorprendido al ver que le falta, busqué más y regresé aquí para publicarlo:

<?php 
ob_end_clean(); 
header("Connection: close"); 
ignore_user_abort(); // optional 
ob_start(); 
echo ('Text the user will see'); 
$size = ob_get_length(); 
header("Content-Length: $size"); 
ob_end_flush(); // Strange behaviour, will not work 
flush();   // Unless both are called ! 
session_write_close(); // Added a line suggested in the comment 
// Do processing here 
sleep(30); 
echo('Text user will never see'); 
?> 

realidad lo uso en pocos lugares. Y tiene sentido allí: un banklink está devolviendo la solicitud de un pago exitoso y tengo que llamar a muchos servicios y procesar una gran cantidad de datos cuando eso sucede. A veces lleva más de 10 segundos, pero el enlace bancario tiene un período de tiempo de espera fijo. Así que reconozco el enlace bancario y le muestro la salida, y hago mis cosas cuando ya se ha ido.

+2

Aconsejo agregar '' 'session_write_close();' '' after '' 'flush();' '' si está utilizando sesiones, de lo contrario no podrá usar su sitio (en la misma pestaña del navegador) hasta su acabado de procesamiento (de fondo). –

+1

Enfoque interesante, pero desafortunadamente no funciona si se está ejecutando detrás de barniz o, presumiblemente, de otros proxies. –

+3

no funciona en php5 y el navegador Chrome en Linux, Chrome espera 30 segundos antes de finalizar la conexión –

2

Puede crear una solicitud http entre el servidor y el servidor. (no se necesita navegador). El secreto para crear una solicitud http de fondo es establecer un tiempo de espera muy pequeño, por lo que se ignora la respuesta.

Ésta es una función de trabajo que he utilizado para ese pupose:

MAYO PHP solicitud asíncrona fondo Otra forma de crear una solicitud asincrónica en PHP (simulando el modo de fondo).

/** 
    * Another way to make asyncronous (o como se escriba asincrono!) request with php 
    * Con esto se puede simpular un fork en PHP.. nada que envidarle a javita ni C++ 
    * Esta vez usando fsockopen 
    * @author PHPepe 
    * @param unknown_type $url 
    * @param unknown_type $params 
    */ 
function phpepe_async($url, $params = array()) { 
    $post_params = array(); 
    foreach ($params as $key => &$val) { 
     if (is_array($val)) $val = implode(',', $val); 
     $post_params[] = $key.'='.urlencode($val); 
    } 
    $post_string = implode('&', $post_params); 

    $parts=parse_url($url); 

    $fp = fsockopen($parts['host'], 
     isset($parts['port'])?$parts['port']:80, 
     $errno, $errstr, 30); 

    $out = "POST ".$parts['path']." HTTP/1.1\r\n"; 
    $out.= "Host: ".$parts['host']."\r\n"; 
    $out.= "Content-Type: application/x-www-form-urlencoded\r\n"; 
    $out.= "Content-Length: ".strlen($post_string)."\r\n"; 
    $out.= "Connection: Close\r\n\r\n"; 
    if (isset($post_string)) $out.= $post_string; 

    fwrite($fp, $out); 
    fclose($fp); 
} 

// Usage: 
phpepe_async("http://192.168.1.110/pepe/feng_scripts/phprequest/fork2.php"); 

Para más información se puede echar un vistazo a http://www.phpepe.com/2011/05/php-asynchronous-background-request.html

0

En el archivo de configuración de Apache php.ini, asegúrese de que el búfer de salida se desactiva:

output_buffering = off 
1

Es posible utilizar cURL para eso, con un tiempo de espera muy corto. Este sería su archivo principal:

<?php> 
    $ch = curl_init(); 
    curl_setopt($ch, CURLOPT_URL, "http://example.com/processor.php"); 
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); 
    curl_setopt($ch, CURLOPT_TIMEOUT_MS, 10);  //just some very short timeout 
    curl_exec($ch); 
    curl_close($ch); 
?> 

Y esto a su archivo de procesador:

<?php 
    ignore_user_abort(true);      //very important! 
    for($x = 0; $x < 10; $x++)      //do some very time-consuming task 
     sleep(10); 
?> 

Como se puede ver, el guión superior será el tiempo de espera después de un corto período de tiempo (10 milisegundos en este caso). Es posible que CURLOPT_TIMEOUT_MS no funcione así, en este caso, sería equivalente a curl_setopt($ch, CURLOPT_TIMEOUT, 1).

Por lo tanto, cuando se haya accedido al archivo del procesador, realizará sus tareas sin importar que el usuario (es decir, el archivo de llamada) cancele la conexión.

Por supuesto, también puede pasar los parámetros GET o POST entre las páginas.

+1

¡He estado buscando una solución a este problema desde hace bastante tiempo y este funciona! Muchas gracias. Las otras soluciones pueden funcionar en escenarios específicos, excepto si usted tiene un control limitado sobre su servidor web solamente y no puede bifurcar nuevos procesos; una configuración que comúnmente encuentro en servidores web comerciales. ¡Esta solución aún funciona! Una adición importante. Para los sistemas UNIX, debe agregar 'curl_setopt ($ ch, CURLOPT_NOSIGNAL, 1);' para los tiempos de espera <1 segundo para que funcionen. Consulte aquí la [explicación] (https://ravidhavlesha.wordpress.com/2012/01/08/curl-timeout-problem-and-solution/). –

Cuestiones relacionadas