fermer une connexion tôt [php]


Answers

Il est nécessaire d'envoyer ces 2 en-têtes:

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

Puisque vous devez connaître la taille de votre sortie, vous devez tamponner votre sortie, puis la vider dans le navigateur:

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

En outre, si votre serveur Web utilise la compression gzip automatique sur la sortie (c'est-à-dire Apache avec mod_deflate), cela ne fonctionnera pas car la taille réelle de la sortie est modifiée et la longueur du contenu n'est plus exacte. Désactiver la compression gzip du script particulier.

Pour plus de détails, visitez http://www.zulius.com/how-to/close-browser-connection-continue-execution

Question

J'essaie de faire un appel AJAX (via JQuery) qui va lancer un processus assez long. Je voudrais que le script envoie simplement une réponse indiquant que le processus a commencé, mais JQuery ne retournera pas la réponse tant que le script PHP ne sera pas exécuté.

J'ai essayé ceci avec un en-tête «étroit» (ci-dessous), et également avec la mise en mémoire tampon de production; aucun ne semble fonctionner. Des suppositions? ou est-ce quelque chose que je dois faire dans JQuery?

<?php

echo( "We'll email you as soon as this is done." );

header( "Connection: Close" );

// do some stuff that will take a while

mail( 'dude@thatplace.com', "okay I'm done", 'Yup, all done.' );

?>



Vous pouvez utiliser Fast-CGI avec PHP-FPM pour utiliser la fonction fastcgi_end_request() . De cette façon, vous pouvez continuer à effectuer un traitement pendant que la réponse a déjà été envoyée au client.

  • exemple d'utilisation de fastcgi_finish_request () (Nov 2010)

Vous le trouverez dans le manuel de PHP ici: FastCGI Process Manager (FPM) ; Mais cette fonction n'est spécifiquement pas documentée dans le manuel. Voici l'extrait du PHP-FPM: PHP FastCGI Process Manager Wiki :

fastcgi_finish_request ()

Champ d'application: fonction php Catégorie: Optimisation

Cette fonctionnalité vous permet d'accélérer l'implémentation de certaines requêtes PHP. L'accélération est possible lorsqu'il y a des actions dans le processus d'exécution de script qui n'affectent pas la réponse du serveur. Par exemple, l'enregistrement de la session dans memcached peut se produire après que la page a été formée et transmise à un serveur Web. fastcgi_finish_request() est une fonctionnalité php qui arrête la sortie de la réponse. Le serveur Web commence immédiatement à transférer la réponse "lentement et tristement" au client, et php en même temps peut faire beaucoup de choses utiles dans le contexte d'une requête, comme enregistrer la session, convertir la vidéo téléchargée, manipuler toutes sortes de statistiques, etc.

fastcgi_finish_request() peut invoquer l'exécution de la fonction shutdown.




Vous pourriez essayer de faire du multithreading.

vous pouvez créer un script qui appelle un système (en utilisant shell_exec ) qui appelle le binaire php avec le script pour faire votre travail en tant que paramètre. Mais je ne pense pas que ce soit le moyen le plus sûr. Peut-être que vous pouvez égayer les choses en chrootant le processus php et d'autres choses

Alternativement, il y a une classe à phpclasses qui fait cela http://www.phpclasses.org/browse/package/3953.html . Mais je ne connais pas les détails de la mise en œuvre




Une meilleure solution consiste à utiliser un processus d'arrière-plan. C'est assez simple sur unix / linux:

<?php
echo "We'll email you as soon as this is done.";
system("php somestuff.php dude@thatplace.com >/dev/null &");
?>

Vous devriez regarder cette question pour de meilleurs exemples:

PHP exécute un processus d'arrière-plan




Cela a fonctionné pour moi

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



Une solution alternative consiste à ajouter le travail à une file d'attente et à créer un script cron qui vérifie les nouveaux travaux et les exécute.

J'ai dû le faire récemment pour contourner les limites imposées par un hôte partagé - exec () et al étaient désactivés pour PHP par le serveur web mais pouvaient fonctionner dans un script shell.




Note pour les utilisateurs de mod_fcgid (utilisez-les à vos risques et périls).

Solution rapide

La réponse acceptée de Joeri Sebrechts est en effet fonctionnelle. Cependant, si vous utilisez mod_fcgid, vous pouvez constater que cette solution ne fonctionne pas seule. En d'autres termes, lorsque la fonction flush est appelée, la connexion au client ne se ferme pas.

Le paramètre de configuration FcgidOutputBufferSize de mod_fcgid peut être à blâmer. J'ai trouvé ce conseil dans:

  1. cette réponse de Travers Carter et
  2. ce blog de Seumas Mackinnon .

Après avoir lu ce qui précède, vous pouvez arriver à la conclusion qu'une solution rapide serait d'ajouter la ligne (voir "Exemple d'hôte virtuel" à la fin):

FcgidOutputBufferSize 0

dans votre fichier de configuration Apache (par exemple, httpd.conf), votre fichier de configuration FCGI (par exemple, fcgid.conf) ou dans votre fichier hosts virtuel (par exemple, httpd-vhosts.conf).

Dans (1) ci-dessus, une variable nommée "OutputBufferSize" est mentionnée. C'est l'ancien nom de FcgidOutputBufferSize mentionné dans (2) (voir les notes de mise à niveau dans la page web Apache pour mod_fcgid ).

Détails et une deuxième solution

La solution ci-dessus désactive la mise en mémoire tampon effectuée par mod_fcgid soit pour le serveur entier, soit pour un hôte virtuel spécifique. Cela pourrait entraîner une pénalité de performance pour votre site Web. D'un autre côté, cela peut ne pas être le cas puisque PHP effectue lui-même la mise en mémoire tampon.

Dans le cas où vous ne souhaitez pas désactiver la mise en mémoire tampon de mod_fcgid , il existe une autre solution ... vous pouvez forcer ce tampon à se vider .

Le code ci-dessous fait justement cela en s'appuyant sur la solution proposée par Joeri Sebrechts:

<?php
    ob_end_clean();
    header("Connection: close");
    ignore_user_abort(true); // just to be safe
    ob_start();
    echo('Text the user will see');

    echo(str_repeat(' ', 65537)); // [+] Line added: Fill up mod_fcgi's buffer.

    $size = ob_get_length();
    header("Content-Length: $size");
    ob_end_flush(); // Strange behaviour, will not work
    flush(); // Unless both are called !
    // Do processing here 
    sleep(30);
    echo('Text user will never see');
?>

Ce que la ligne de code ajoutée fait essentiellement est de remplir le tampon de mod_fcgi , le forçant ainsi à vider. Le nombre "65537" a été choisi parce que la valeur par défaut de la variable FcgidOutputBufferSize est "65536", comme mentionné dans la page Web Apache pour la directive correspondante . Par conséquent, vous devrez peut-être ajuster cette valeur en conséquence si une autre valeur est définie dans votre environnement.

Mon environnement

  • WampServer 2.5
  • Apache 2.4.9
  • PHP 5.5.19 VC11, x86, coffre-fort non-thread
  • mod_fcgid / 2.3.9
  • Windows 7 Professionnel x64

Exemple d'hôte virtuel

<VirtualHost *:80>
    DocumentRoot "d:/wamp/www/example"
    ServerName example.local

    FcgidOutputBufferSize 0

    <Directory "d:/wamp/www/example">
        Require all granted
    </Directory>
</VirtualHost>



Si la fonction flush() ne fonctionne pas. Vous devez définir les options suivantes dans php.ini comme:

output_buffering = Off  
zlib.output_compression = Off  



Dernière solution de travail

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