web-dev-qa-db-fra.com

Continuer PHP exécution après l'envoi de la réponse HTTP

Comment faire en sorte que PHP 5.2 (exécuté sous Apache mod_php) envoie une réponse HTTP complète au client, puis continue à exécuter les opérations pendant une minute supplémentaire? 

La longue histoire:

J'ai un script PHP qui doit exécuter quelques requêtes de base de données longues et envoyer un courrier électronique, ce qui prend 45 à 60 secondes. Ce script est appelé par une application sur laquelle je n’ai aucun contrôle. J'ai besoin que l'application signale tous les messages d'erreur reçus du script PHP (principalement des erreurs de paramètres non valides). 

L'application a un délai d'expiration inférieur à 45 secondes (je ne connais pas la valeur exacte) et enregistre par conséquent toutes les exécutions du script PHP sous forme d'erreur. Par conséquent, j'ai besoin de PHP pour envoyer la réponse HTTP complète au client aussi rapidement que possible (idéalement, dès que les paramètres d'entrée ont été validés), puis pour exécuter le traitement de la base de données et du courrier électronique. 

J'utilise mod_php, donc pcntl_fork n'est pas disponible. Je pourrais résoudre ce problème en enregistrant les données à traiter dans la base de données et en exécutant le processus à partir de cron, mais je cherche une solution plus courte.

58
Victor Nicollet

Demandez au script qui gère la demande initiale de créer une entrée dans une file d'attente de traitement, puis de le renvoyer immédiatement. Ensuite, créez un processus séparé (via cron peut-être) qui exécute régulièrement les tâches en attente dans la file.

25
Alex Howansky

J'avais cet extrait dans ma boîte à outils "scripts spéciaux", mais il s'est perdu (les nuages ​​n'étaient pas courants à l'époque), alors je le cherchais et ai posé cette question. retour ici pour le poster:

<?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');
?>

Je l'utilise effectivement dans quelques endroits. Et c'est tout à fait logique ici: un lien bancaire renvoie la demande de paiement réussi et je dois faire appel à de nombreux services et traiter beaucoup de données lorsque cela se produit. Cela prend parfois plus de 10 secondes, mais banklink a un délai d'expiration fixe. Donc, je reconnais le lien bancaire et lui montre le chemin, et fais ce que je fais quand il est déjà parti.

46
povilasp

Ce dont vous avez besoin est ce genre de configuration

alt text

7
Yanick Rochon

On peut utiliser "http fork" pour soi-même ou pour tout autre script. Je veux dire quelque chose comme ça:

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

Maintenant le script enfant:

// 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" :)
...

L'idée principale est:

  • script parent appelé à la demande de l'utilisateur;
  • parent appelle le script enfant (identique à parent ou autre) sur le même serveur (ou tout autre serveur) et leur donne les données de requête;
  • le parent dit ok à l'utilisateur et se termine;
  • l'enfant travaille.

Si vous avez besoin d'interagir avec l'enfant, vous pouvez utiliser la base de données comme "moyen de communication": le parent peut lire le statut de l'enfant et écrire des commandes, l'enfant peut lire les commandes et écrire le statut. Si vous en avez besoin pour plusieurs scripts enfants, vous devez conserver l'ID enfant du côté utilisateur pour les distinguer et envoyer cet ID au parent chaque fois que vous souhaitez vérifier le statut de l'enfant correspondant.

J'ai trouvé cela ici - http://linuxportal.ru/forums/index.php/t/22951/

6
SomeGuy

Pourquoi ne pas appeler un script sur le serveur de fichiers pour l'exécuter comme s'il avait été déclenché en ligne de commande? Vous pouvez le faire avec PHP exec .

5
SorcyCat

Vous pouvez utiliser la fonction PHP register-shutdown-function qui exécutera quelque chose après que le script a terminé son dialogue avec le navigateur.

Voir aussi ignore_user_abort - mais vous ne devriez pas avoir besoin de cette fonction si vous utilisez la fonction register_shutdown_function. Sur la même page, set_time_limit(0) empêchera l’exécution de votre script.

4
Ring Ø

Utiliser une file d'attente, un exécutable ou un cron constituerait une surcharge pour cette tâche simple… .. Il n'y a aucune raison de ne pas rester dans le même script… .. Cette combinaison a très bien fonctionné pour moi:

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

lire plus dans: Comment continuer le processus après avoir répondu à la requête ajax en PHP?

3
Nir O.

Vous pouvez créer une requête http entre serveur et serveur. (Le navigateur ne nécessite pas de navigateur.) Le secret pour créer une demande http en arrière-plan est un très petit délai d'attente, de sorte que la réponse est ignorée. 

C’est une fonction de travail que j’ai utilisée pour cela:

MAI 31 Demande d'arrière-plan PHP asynchrone Une autre façon de créer une demande asynchrone dans PHP (simulation du mode d'arrière-plan).

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

Pour plus d'informations, vous pouvez consulter http://www.phpepe.com/2011/05/php-asynchronous-background-request.html

2
Ignacio Vazquez

Il est possible d'utiliser cURL pour cela, avec un délai d'expiration très court. Ce serait votre fichier 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);
?>

Et ceci votre fichier de processeur:

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

Comme vous pouvez le constater, le script supérieur expirera après un court laps de temps (10 millisecondes dans ce cas). Il est possible que CURLOPT_TIMEOUT_MS ne fonctionne pas comme cela, dans ce cas, il serait équivalent à curl_setopt($ch, CURLOPT_TIMEOUT, 1).

Ainsi, après avoir accédé au fichier de processeur, il effectuera ses tâches, que l’utilisateur (le fichier appelant) abandonne la connexion.

Bien entendu, vous pouvez également transmettre les paramètres GET ou POST entre les pages.

1
sigalor

Bah, j'ai mal compris vos exigences. On dirait qu'ils sont réellement:

  • Le script reçoit les entrées d'une source externe que vous ne contrôlez pas
  • Le script traite et valide les entrées, et permet à l'application externe de savoir si elles sont bonnes ou non et met fin à la session.
  • Le script lance un processus de longue durée.

Dans ce cas, alors oui, utiliser une file d'attente de travaux externe et/ou cron fonctionnerait. Une fois l'entrée validée, insérez les détails du travail dans la file d'attente et quittez. Un autre script peut ensuite être exécuté, extraire les détails du travail de la file d'attente et lancer le processus plus long. Alex Howansky a la bonne idée.

Désolé, j'avoue que j'ai écrémé un peu la première fois.

0
Ryan Chouinard

Vous pouvez diviser ces fonctions en trois scripts . 1. Lancer le processus et appeler le second via exec ou command. Il est également possible de le lancer via http call . le second exécutera le traitement de la base de données et finira par le dernier 3. dernier email

0
Aram

Dans votre fichier de configuration Apache php.ini, assurez-vous que la mise en mémoire tampon de sortie est désactivée:

output_buffering = off
0
Luan Costa

Je recommanderais de créer une nouvelle demande asynchrone à la fin, plutôt que de poursuivre le processus avec l'utilisateur.

Vous pouvez générer l’autre requête en utilisant la réponse suivante: Appels asynchrones PHP?

0
Brent