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.
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.
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.
Ce dont vous avez besoin est ce genre de configuration
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?¶m=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:
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/
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 .
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.
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?
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
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.
Bah, j'ai mal compris vos exigences. On dirait qu'ils sont réellement:
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.
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
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
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?