continuer à traiter php après avoir envoyé une réponse http

mon script est appelé par le serveur. Du serveur je recevrai ID_OF_MESSAGE et TEXT_OF_MESSAGE .

dans mon script, Je manipulerai le texte entrant et générerai la réponse avec les params: ANSWER_TO_ID et RESPONSE_MESSAGE .

le problème est que j'envoie la réponse à incomming "ID_OF_MESSAGE" , mais le serveur qui m'envoie le message à gérer va régler son message comme livré à moi( cela signifie que je peux lui envoyer la réponse à cette ID), après avoir reçu la réponse http 200.

l'une des solutions est de sauvegarder le message dans la base de données et de créer un cron qui sera lancé à chaque minute, mais je dois générer le message de réponse immédiatement.

y a-t-il une solution pour envoyer la réponse http 200 au serveur et pour continuer à exécuter le script php?

Merci beaucoup

75
demandé sur Rahul 2013-03-07 18:20:02

11 réponses

Oui. Vous pouvez le faire:

ignore_user_abort(true);
set_time_limit(0);

ob_start();
// do initial processing here
echo $response; // send the response
header('Connection: close');
header('Content-Length: '.ob_get_length());
ob_end_flush();
ob_flush();
flush();

// now the request is sent to the browser, but the script is still running
// so, you can continue...
149
répondu vcampitelli 2013-03-07 14:24:42

j'ai vu beaucoup de réponses ici qui suggèrent d'utiliser ignore_user_abort(true); mais ce code n'est pas nécessaire. Tout ce que cela fait est de s'assurer que votre script continue l'exécution avant qu'une réponse ne soit envoyée dans le cas où l'utilisateur interrompt (en fermant leur navigateur ou en appuyant sur escape pour arrêter la demande). Mais ce n'est pas ce que vous demandez. Vous demandez de poursuivre l'exécution après l'envoi d'une réponse. Tout ce dont vous avez besoin est le suivant:

    // Buffer all upcoming output...
    ob_start();

    // Send your response.
    echo "Here be response";

    // Get the size of the output.
    $size = ob_get_length();

    // Disable compression (in case content length is compressed).
    header("Content-Encoding: none");

    // Set the content length of the response.
    header("Content-Length: {$size}");

    // Close the connection.
    header("Connection: close");

    // Flush all output.
    ob_end_flush();
    ob_flush();
    flush();

    // Close current session (if it exists).
    if(session_id()) session_write_close();

    // Start your background work here.
    ...

si vous craignez que votre le travail de fond prendra plus de temps que le délai d'exécution du script par défaut de PHP, puis coller set_time_limit(0); en haut.

24
répondu Kosta Kontos 2015-02-26 09:21:27

si vous utilisez FastCGI processing ou PHP-FPM, vous pouvez:

session_write_close(); //close the session
fastcgi_finish_request(); //this returns 200 to the user, and processing continues

// do desired processing ...
$expensiveCalulation = 1+1;
error_log($expensiveCalculation);

Source: http://fi2.php.net/manual/en/function.fastcgi-finish-request.php

17
répondu DarkNeuron 2017-10-17 08:52:33

j'ai passé quelques heures sur cette question et je suis venu avec cette fonction qui fonctionne sur Apache et Nginx:

/**
 * respondOK.
 */
protected function respondOK()
{
    // check if fastcgi_finish_request is callable
    if (is_callable('fastcgi_finish_request')) {
        /*
         * This works in Nginx but the next approach not
         */
        session_write_close();
        fastcgi_finish_request();

        return;
    }

    ignore_user_abort(true);

    ob_start();
    $serverProtocole = filter_input(INPUT_SERVER, 'SERVER_PROTOCOL', FILTER_SANITIZE_STRING);
    header($serverProtocole.' 200 OK');
    header('Content-Encoding: none');
    header('Content-Length: '.ob_get_length());
    header('Connection: close');

    ob_end_flush();
    ob_flush();
    flush();
}

vous pouvez appeler cette fonction avant votre long traitement.

10
répondu Ehsan 2017-02-15 09:23:37

a un peu modifié la réponse de @vcampitelli. Ne pensez pas que vous avez besoin de l'en-tête close . Je voyais des en-têtes proches en double dans Chrome.

<?php

ignore_user_abort(true);

ob_start();
echo '{}';
header($_SERVER["SERVER_PROTOCOL"] . " 202 Accepted");
header("Status: 202 Accepted");
header("Content-Type: application/json");
header('Content-Length: '.ob_get_length());
ob_end_flush();
ob_flush();
flush();

sleep(10);
3
répondu Justin 2015-06-03 00:42:12

j'ai posé cette question à Rasmus Lerdorf en avril 2012, citant ces articles:

j'ai suggéré le développement d'un nouveau Fonction PHP pour informer la plate-forme qu'aucune autre sortie (stdout?) sera généré (une telle fonction pourrait prendre soin de fermer la connexion). Rasmus Lerdorf répond:

Voir Gearman . Vous ne voulez vraiment pas que vos serveurs web frontend fassent un traitement d'arrière-plan comme ça.

je peux voir son point, et soutenir son opinion pour certaines applications/ scénarios de chargement! Cependant, dans d'autres cas, les solutions de vcampitelli et al. sont bonnes.

1
répondu Matthew Slyman 2017-06-17 07:59:48

pour des raisons obscures, aucune de ces solutions ne fonctionne avec MAMP PRO (Apache, MySQL, PHP).

1
répondu asiby 2018-05-21 21:28:54

j'utilise la fonction php register_shutdown_function pour cela.

void register_shutdown_function ( callable $callback [, mixed $parameter [, mixed $... ]] )

http://php.net/manual/en/function.register-shutdown-function.php

Edit : ce qui précède ne fonctionne pas. Il semble que j'ai été induit en erreur par de vieux documents. Le comportement de register_shutdown_function a changé depuis PHP 4.1 lien lien

0
répondu martti 2016-01-08 11:08:59

il y a une autre approche et cela vaut la peine de considérer si vous ne voulez pas trafiquer les en-têtes de réponse. Si vous démarrez un thread sur un autre processus, la fonction appelée n'attendra pas sa réponse et retournera au navigateur avec un code http finalisé. Vous devrez configurer pthread .

class continue_processing_thread extends Thread 
{
     public function __construct($param1) 
     {
         $this->param1 = $param1
     }

     public function run() 
     {
        //Do your long running process here
     }
}

//This is your function called via an HTTP GET/POST etc
function rest_endpoint()
{
  //do whatever stuff needed by the response.

  //Create and start your thread. 
  //rest_endpoint wont wait for this to complete.
  $continue_processing = new continue_processing_thread($some_value);
  $continue_processing->start();

  echo json_encode($response)
}

une Fois que nous exécuter $continue_processing->start() PHP habitude d'attendre le retourner le résultat de ce thread et donc aussi loin que rest_endpoint est considéré. Il est fait.

quelques liens pour aider avec pthreads

bonne chance.

0
répondu Jonathan 2017-05-23 11:47:26

en cas d'utilisation de php file_get_contents, la fermeture de la connexion ne suffit pas. php attendre encore eof sorcière envoyer par le serveur.

ma solution est de lire ' Content-Length:'

voici l'échantillon:

réponse.php:

 <?php

ignore_user_abort(true);
set_time_limit(500);

ob_start();
echo 'ok'."\n";
header('Connection: close');
header('Content-Length: '.ob_get_length());
ob_end_flush();
ob_flush();
flush();
sleep(30);

noter le" \N " en réponse à la ligne étroite, sinon le fget lire pendant l'attente eof.

lire.php:

<?php
$vars = array(
    'hello' => 'world'
);
$content = http_build_query($vars);

fwrite($fp, "POST /response.php HTTP/1.1\r\n");
fwrite($fp, "Content-Type: application/x-www-form-urlencoded\r\n");
fwrite($fp, "Content-Length: " . strlen($content) . "\r\n");
fwrite($fp, "Connection: close\r\n");
fwrite($fp, "\r\n");

fwrite($fp, $content);

$iSize = null;
$bHeaderEnd = false;
$sResponse = '';
do {
    $sTmp = fgets($fp, 1024);
    $iPos = strpos($sTmp, 'Content-Length: ');
    if ($iPos !== false) {
        $iSize = (int) substr($sTmp, strlen('Content-Length: '));
    }
    if ($bHeaderEnd) {
        $sResponse.= $sTmp;
    }
    if (strlen(trim($sTmp)) == 0) {
        $bHeaderEnd = true;
    }
} while (!feof($fp) && (is_null($iSize) || !is_null($iSize) && strlen($sResponse) < $iSize));
$result = trim($sResponse);

Comme vous pouvez le voir ce dosent de script attend environ eof si la longueur du contenu est portée.

j'espère que ça aidera

0
répondu Jérome S. 2016-08-26 08:50:26

j'ai quelque chose qui peut compresser et envoyer la réponse et laisser le code php s'exécuter.

function sendResponse($response){
    $contentencoding = 'none';
    if(ob_get_contents()){
        ob_end_clean();
        if(ob_get_contents()){
            ob_clean();
        }
    }
    header('Connection: close');
    header("cache-control: must-revalidate");
    header('Vary: Accept-Encoding');
    header('content-type: application/json; charset=utf-8');
    ob_start();
    if(phpversion()>='4.0.4pl1' && extension_loaded('zlib') && GZIP_ENABLED==1 && !empty($_SERVER["HTTP_ACCEPT_ENCODING"]) && (strpos($_SERVER["HTTP_ACCEPT_ENCODING"], 'gzip') !== false) && (strstr($GLOBALS['useragent'],'compatible') || strstr($GLOBALS['useragent'],'Gecko'))){
        $contentencoding = 'gzip';
        ob_start('ob_gzhandler');
    }
    header('Content-Encoding: '.$contentencoding);
    if (!empty($_GET['callback'])){
        echo $_GET['callback'].'('.$response.')';
    } else {
        echo $response;
    }
    if($contentencoding == 'gzip') {
        if(ob_get_contents()){
            ob_end_flush(); // Flush the output from ob_gzhandler
        }
    }
    header('Content-Length: '.ob_get_length());
    // flush all output
    if (ob_get_contents()){
        ob_end_flush(); // Flush the outer ob_start()
        if(ob_get_contents()){
            ob_flush();
        }
        flush();
    }
    if (session_id()) session_write_close();
}
0
répondu Mayur Shedage 2018-08-11 13:06:14