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


101

サーバーからスクリプトが呼び出されます。サーバーから私は受け取りますID_OF_MESSAGETEXT_OF_MESSAGE

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

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

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

サーバーにhttp応答200を送信し、phpスクリプトを実行し続ける方法はありますか?

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

回答:


191

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

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

5
キープアライブ接続でそれを行うことは可能ですか?
Congelli501

3
優れた!!これは、実際に機能するこの質問に対する唯一の回答です!!! 10p +
Martin_Lakes 2014

3
ob_end_flushの後にob_flushを使用する理由はありますか?最後にフラッシュ関数の必要性を理解していますが、ob_end_flushが呼び出されてob_flushが必要になる理由がわかりません。
ars265

9
content-encodingヘッダーが「none」以外に設定されている場合、ユーザーが完全な実行時間(タイムアウトになるまで)待機するため、この例は役に立たなくなる可能性があることに注意してください。だから、本番環境でローカルと動作します、絶対に確認するために、「none」に「コンテンツエンコード」ヘッダーを設定します。header("Content-Encoding: none")
ブライアン

17
ヒント:私はPHP-FPMを使い始めたのでfastcgi_finish_request()、最後に追加する必要がありました
vcampitelli '16

44

ここで、使用を提案する多くの応答を見ました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);は、一番上に固執 してください。


3
さまざまな組み合わせをたくさん試してみましたが、これはうまくいくものです!!! コスタ・コントス、ありがとう!
Martin_Lakes

1
Apache 2、php 7.0.32、ubuntu 16.04で完全に動作します!ありがとう!
KyleBunga

1
私は他の解決策を試しましたが、これだけがうまくいきました。行の順序も重要です。
Sinisa、

32

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

session_write_close(); //close the session
ignore_user_abort(true); //Prevent echo, print, and flush from killing the script
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

PHPの問題#68722:https ://bugs.php.net/bug.php?id=68772


2
おかげで、数時間を費やした後、これはnginxで私のために働きました
Ehsan

7
dat sum高額計算tho:非常に感銘を受けました。
Friedrich Roell、2018

DarkNeuronに感謝します!php-fpmを使用して私たちに素晴らしい答えを、ちょうど私の問題を解決しました!
Sinisa

21

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

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


2
これは血まみれの素敵です!上記のすべてを試した後、これはnginxで動作した唯一のものです。
スパイス

あなたのコードはコードとほとんど同じであるここが、あなたの投稿が古い:) +1です
会計士م

filter_input関数がNULLを返す場合があるので注意してください。詳細については、このユーザー投稿を参照してください
会計士م'19

4

@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
これについては元の回答で述べましたが、ここでも説明します。必ずしも接続を閉じる必要はありませんが、同じ接続で要求された次のアセットは強制的に待機させられます。したがって、HTMLを高速で配信できますが、次のアセットを取得する前に接続がPHPからの応答を取得する必要があるため、JSまたはCSSファイルのいずれかの読み込みが遅くなる可能性があります。そのため、接続を閉じることをお勧めします。これにより、ブラウザは接続が解放されるのを待つ必要がなくなります。
Nate Lampton、2015

3

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

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

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

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


これは求められていることではありません-この関数は基本的にスクリプト終了イベントを拡張するだけで、まだ出力バッファの一部です。
フィスク

1
それは賛成票に値することがわかりました、何がうまくいかないかを示しているからです。
Trendfischer 16

1
同上-私はこれを答えとして期待していたので賛成票を投じ、それが機能しないことを確認するのに役立ちました。
HappyDog

2

pthreadをインストールできず、以前のソリューションも機能しません。私は次の解決策のみが機能することを見つけました(参照: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');
?>

1

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

クローズ行に応答する「\ n」に注意してください。そうでない場合は、egetの待機中にfgetが読み取られます。

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について待機しません。

それが役に立てば幸い


1

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

これ以上の出力(stdout?)が生成されないことをプラットフォームに通知するために、新しいPHP組み込み関数の開発を提案しました(このような関数が接続を閉じる処理を行う場合があります)。ラスムス・ラードルフは答えた:

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

私は彼のポイントを見ることができ、いくつかのアプリケーション/読み込みシナリオについて彼の意見を支持します!ただし、他のいくつかのシナリオでは、vcampitelli et alのソリューションが適切です。


1

私は圧縮して応答を送信し、他の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();
}

0

別のアプローチがあり、応答ヘッダーを改ざんしたくないかどうかを検討する価値があります。別のプロセスでスレッドを開始した場合、呼び出された関数はその応答を待たずに、最終的な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

私はそれが古いものであることを知っていますが、この時点でおそらく有用です。

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

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

プロの:

  1. 高性能です
  2. 洗練された構造と保守性
  3. ワーカーインスタンスで絶対にスケーラブルです

ネガ:

  1. RabbitMQをサーバーにインストールする必要があります。これは、一部のWebホスティング会社に問題がある可能性があります。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.