it-swarm.com.de

verarbeitung von PHP nach dem Senden der http-Antwort

Mein Skript wird vom Server aufgerufen. Vom Server erhalte ich ID_OF_MESSAGE und TEXT_OF_MESSAGE

In meinem Skript behandele ich eingehenden Text und generiere eine Antwort mit Parametern: ANSWER_TO_ID und RESPONSE_MESSAGE.

Das Problem ist, dass ich eine Antwort an incomming "ID_OF_MESSAGE" sende, aber der Server, der mir eine Nachricht zur Bearbeitung sendet, setzt seine Nachricht als an mich gesendet (dh ich kann ihm eine Antwort auf diese ID senden), nachdem er die http-Antwort 200 erhalten hat. 

Eine Lösung besteht darin, die Nachricht in der Datenbank zu speichern und einen cron zu erstellen, der jede Minute ausgeführt wird, aber ich muss sofort eine Antwortnachricht generieren. 

Gibt es eine Lösung, wie Sie die HTTP-Antwort 200 an den Server senden und dann mit der Ausführung des PHP-Skripts fortfahren können?

Vielen Dank

80
user1700214

Ja. Du kannst das:

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

Ich habe hier eine Menge Antworten gesehen, die die Verwendung von ignore_user_abort(true); vorschlagen, aber dieser Code ist nicht notwendig. Sie müssen lediglich sicherstellen, dass Ihr Skript weiterhin ausgeführt wird, bevor eine Antwort gesendet wird, falls der Benutzer abbricht (durch Schließen des Browsers oder Drücken der Escape-Taste, um die Anforderung zu stoppen). Aber das fragst du nicht. Sie möchten die Ausführung fortsetzen, nachdem eine Antwort gesendet wurde. Alles was Sie brauchen ist folgendes:

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

Wenn Sie befürchten, dass Ihre Hintergrundarbeit länger dauert als die Standard-Ausführungszeit von PHP, halten Sie set_time_limit(0); oben.

31
Kosta Kontos

Wenn Sie FastCGI-Verarbeitung oder PHP-FPM verwenden, können Sie:

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

Quelle: http://fi2.php.net/manual/de/function.fastcgi-finish-request.php

23
DarkNeuron

Ich habe einige Stunden mit diesem Thema verbracht und bin mit dieser Funktion gekommen, die auf Apache und Nginx funktioniert:

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

Sie können diese Funktion vor Ihrer langen Bearbeitung aufrufen. 

12
Ehsan

Die Antwort von @vcampitelli wurde etwas geändert. Denken Sie nicht, dass Sie die close-Kopfzeile benötigen. Ich habe doppelte Kopfzeilen in Chrome gesehen.

<?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
Justin

Ich verwende dazu die PHP-Funktion register_shutdown_function.

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

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

Edit: Das Obige funktioniert nicht. Es scheint, als hätte mich eine alte Dokumentation in die Irre geführt. Das Verhalten von register_shutdown_function hat sich seit PHP 4.1 linklink geändert.

1
martti

Aus unerklärlichen Gründen funktioniert keine dieser Lösungen bei Verwendung von MAMP PRO (Apache, MySQL, PHP).

1
asiby

Ich habe diese Frage im April 2012 an Rasmus Lerdorf gestellt und diese Artikel zitiert:

Ich habe die Entwicklung einer neuen integrierten Funktion PHP vorgeschlagen, um die Plattform darüber zu informieren, dass keine weitere Ausgabe (über stdout?) Generiert wird. Rasmus Lerdorf antwortete:

Siehe Gearman . Sie möchten wirklich nicht, dass Ihre Frontend-Webserver eine solche Backend-Verarbeitung durchführen.

Ich kann seinen Standpunkt verstehen und seine Meinung für einige Anwendungen/Ladeszenarien unterstützen! In einigen anderen Szenarien sind die Lösungen von vcampitelli et al. Jedoch gut.

1
Matthew Slyman

Ich habe etwas, das komprimiert und die Antwort senden und anderen PHP-Code ausführen lassen kann.

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();
}
1
Mayur Shedage

Ich kann pthread nicht installieren und auch die vorherigen Lösungen funktionieren für mich nicht. Ich habe nur die folgende Lösung gefunden (ref: https://stackoverflow.com/a/14469376/1315873 ):

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

im Falle der Verwendung von php file_get_contents reicht das Schließen der Verbindung nicht aus php wartet noch auf eof mit dem Senden durch den Server.

meine Lösung ist "Content-Length:" zu lesen.

hier ist ein Beispiel:

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

Beachten Sie das "\ n" als Antwort auf die schließende Zeile, wenn nicht die erste Zeile gelesen wird, während Sie warten.

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

Wie Sie sehen, warten Sie mit diesem Skript, ob die Inhaltslänge erreicht ist.

hoffe es wird helfen

0
Jérome S.

Es gibt einen anderen Ansatz und es lohnt sich zu überlegen, ob Sie die Antwortheader nicht manipulieren möchten. Wenn Sie einen Thread in einem anderen Prozess starten, wartet die aufgerufene Funktion nicht auf ihre Antwort und kehrt mit einem endgültigen http-Code zum Browser zurück. Sie müssen pthread konfigurieren.

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)
}

Sobald wir $ continue_processing-> start ()ausgeführt haben, wird _ PHP nicht auf das Ergebnis dieses Threads und somit auf rest_endpoint warten. Es ist vollbracht.

Einige Links zur Hilfe bei Pthreads

Viel Glück.

0
Jonathan

Ich weiß, dass es ein altes ist, aber an dieser Stelle möglicherweise nützlich.

Mit dieser Antwort unterstütze ich nicht die eigentliche Frage, sondern wie man dieses Problem richtig löst. Hoffe, es hilft anderen Menschen, Probleme wie diese zu lösen.

Ich würde vorschlagen, RabbitMQ oder ähnliche Dienste zu verwenden und die Hintergrund-Workload mit Worker-Instanzen auszuführen. Es gibt ein Paket namens amqplib für PHP, das die ganze Arbeit für Sie erledigt, um RabbitMQ zu verwenden.

Pro's:

  1. Es ist sehr performant
  2. Schön strukturiert und wartbar
  3. Es ist absolut skalierbar mit Worker-Instanzen

Neg's:

  1. RabbitMQ muss auf dem Server installiert sein, dies kann bei einigen Webhostern ein Problem sein.
0
Sam