web-dev-qa-db-ja.com

HTTP応答を送信した後、PHPの処理を続行します

私のスクリプトはサーバーによって呼び出されます。サーバーからID_OF_MESSAGEおよびTEXT_OF_MESSAGEを受け取ります。

私のスクリプトでは、着信テキストを処理し、paramsで応答を生成します:ANSWER_TO_IDおよびRESPONSE_MESSAGE

問題は、着信"ID_OF_MESSAGE"に応答を送信しますが、処理するメッセージを送信するサーバーは、http応答を受信した後、メッセージを配信済みとして設定します(つまり、そのIDに応答を送信できることを意味します) 200。

解決策の1つは、メッセージをデータベースに保存し、毎分実行されるcronを作成することですが、すぐに応答メッセージを生成する必要があります。

サーバーにHTTPレスポンス200を送信し、PHPスクリプトの実行を継続する方法はありますか?

本当にありがとうございます

90
user1700214

はい。あなたはこれを行うことができます:

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

ここでignore_user_abort(true);の使用を提案する多くの応答を見てきましたが、このコードは必要ありません。これはすべて、ユーザーが中止した場合に(ブラウザーを閉じるか、エスケープを押して要求を停止することにより)応答が送信される前に、スクリプトの実行を継続することです。しかし、それはあなたが求めていることではありません。応答が送信された後、実行を継続するよう求めています。必要なものは次のとおりです。

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

バックグラウンドでの作業にPHPのデフォルトのスクリプト実行時間制限よりも時間がかかることが心配な場合は、set_time_limit(0);を一番上に貼り付けてください。

34
Kosta Kontos

FastCGI処理またはPHP-FPMを使用している場合、次のことができます。

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

ソース: https://www.php.net/manual/en/function.fastcgi-finish-request.php

24
DarkNeuron

私はこの問題に数時間を費やし、Apacheと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();
}

長い処理の前にこの関数を呼び出すことができます。

17
Ehsan

@vcampitelliによる回答を少し修正しました。 closeヘッダーが必要だとは思わないでください。 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
Justin

これには、php関数register_shutdown_functionを使用します。

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

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

Edit:上記は機能しません。古いドキュメントに惑わされたようです。 register_shutdown_functionの動作はPHP 4.1以降に変更されました linklink

2
martti

あいまいな理由のために、MAMP PRO(Apache、MySQL、PHP)を使用する場合、これらのソリューションはいずれも機能しません。

1
asiby

2012年4月、ラスマスラードルフにこの質問をして、次の記事を引用しました。

プラットフォームにこれ以上の出力(stdout?)が生成されないことを通知する新しいPHP組み込み関数の開発を提案しました(そのような関数が接続のクローズを処理するかもしれません)。ラスマス・ラードルフは次のように答えました:

Gearman を参照してください。フロントエンドWebサーバーがこのようなバックエンド処理を行うことは本当に望ましくありません。

私は彼の主張を見ることができ、いくつかのアプリケーション/ロードシナリオに対する彼の意見を支持します!ただし、他のいくつかのシナリオでは、vcampitelliらのソリューションは良いものです。

1
Matthew Slyman

圧縮して応答を送信し、他のphpコードを実行できるものがあります。

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 S

Pthreadをインストールできず、以前のソリューションも機能しません。動作するのは次のソリューションのみです(参照: https://stackoverflow.com/a/14469376/131587 ):

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

php file_get_contentsを使用する場合、接続を閉じるだけでは不十分です。 PHPはeof witchがサーバーから送信されるのを待ちます。

私の解決策は「Content-Length:」を読むことです

ここにサンプルがあります:

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

Egetの待機中にfgetが読み込まれなかった場合は、閉じる行に応答して「\ n」に注意してください。

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

このスクリプトを見るとわかるように、コンテンツの長さに達した場合、eofを待つ必要はありません。

それが役立つことを願って

0
Jérome S.

別のアプローチがあり、応答ヘッダーを改ざんしたくない場合に検討する価値があります。別のプロセスでスレッドを開始すると、呼び出された関数はその応答を待たずに、最終的なhttpコードでブラウザーに戻ります。設定する必要があります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)
}

$ continue_processing-> start()PHPを実行すると、このスレッドの戻り結果を待たず、したがってrest_endpointが考慮されます。されております。

Pthreadを支援するリンク

幸運を。

0
Jonathan

私はそれが古いものであることを知っていますが、おそらくこの時点で役に立つでしょう。

この答えでは、実際の質問をサポートしていませんが、この問題を正しく解決する方法をサポートしています。他の人がこのような問題を解決するのに役立つことを願っています。

RabbitMQ または同様のサービスを使用し、 workerインスタンス を使用してバックグラウンドワークロードを実行することをお勧めします。 RabbitMQを使用するためのすべての作業を行うphp用の amqplib というパッケージがあります。

プロの:

  1. 高性能です
  2. うまく構造化され、保守可能
  3. ワーカーインスタンスで完全にスケーラブルです

ネガ:

  1. RabbitMQをサーバーにインストールする必要があります。これは、一部のWebホスティング業者に問題がある可能性があります。
0
Sam