どうすればPHP 5.2(Apache mod_phpとして実行)がクライアントに完全なHTTP応答を送信し、さらに1分間操作を実行し続けることができますか?
長い話:
PHPいくつかの長いデータベースリクエストを実行し、実行に45〜60秒かかる電子メールを送信する必要があるスクリプト。このスクリプトは、制御できないアプリケーションによって呼び出されます。 PHPスクリプト(主に無効なパラメーターエラー)から受信したエラーメッセージを報告するアプリケーションが必要です。
アプリケーションのタイムアウト遅延は45秒より短く(正確な値はわかりません)、したがって、PHPスクリプトのすべての実行をエラーとして登録します。したがって、PHPを使用して、完全なHTTP応答を可能な限り高速にクライアントに送信し(理想的には、入力パラメーターが検証されるとすぐに)、データベースおよび電子メール処理を実行します。
Mod_phpを実行しているので、pcntl_fork
利用できません。処理するデータをデータベースに保存し、cron
から実際のプロセスを実行することでこれを回避できますが、より短い解決策を探しています。
最初の要求を処理するスクリプトに処理キューにエントリを作成させ、すぐに戻ります。次に、キューで保留中のジョブを定期的に実行する別のプロセスを(cronを介して)作成します。
「特別なスクリプト」ツールボックスにこのスニペットがありましたが、失われました(当時はクラウドは一般的ではありませんでした)。ここに戻って投稿してください:
<?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');
?>
実際にいくつかの場所で使用しています。そして、それは完全に理にかなっています:銀行リンクは成功した支払いのリクエストを返しています、そして、私はそれが起こるとき多くのサービスを呼び出して、多くのデータを処理しなければなりません。これには10秒以上かかることがありますが、バンクリンクのタイムアウト期間は固定されています。だから、私は銀行リンクを認め、彼に道を示し、彼がすでにいなくなったときに私の仕事をします。
必要なのはこの種のセットアップです
自分または他のスクリプトに「httpフォーク」を使用できます。私はこのようなことを意味します:
// parent sript, called by user request from browser
// create socket for calling child script
$socketToChild = fsockopen("localhost", 80);
// HTTP-packet building; header first
$msgToChild = "POST /sript.php?¶m=value&<more params> HTTP/1.0\n";
$msgToChild .= "Host: localhost\n";
$postData = "Any data for child as POST-query";
$msgToChild .= "Content-Length: ".strlen($postData)."\n\n";
// header done, glue with data
$msgToChild .= $postData;
// send packet no oneself www-server - new process will be created to handle our query
fwrite($socketToChild, $msgToChild);
// wait and read answer from child
$data = fread($socketToChild, $dataSize);
// close connection to child
fclose($socketToChild);
...
子スクリプト:
// parse HTTP-query somewhere and somehow before this point
// "disable partial output" or
// "enable buffering" to give out all at once later
ob_start();
// "say hello" to client (parent script in this case) disconnection
// before child ends - we need not care about it
ignore_user_abort(1);
// we will work forever
set_time_limit(0);
// we need to say something to parent to stop its waiting
// it could be something useful like client ID or just "OK"
...
echo $reply;
// Push buffer to parent
ob_flush();
// parent gets our answer and disconnects
// but we can work "in background" :)
...
主なアイデアは次のとおりです。
子とやり取りする必要がある場合-DBを「通信媒体」として使用できます。親は子ステータスを読み取り、コマンドを書き込むことができ、子はコマンドを読み取り、ステータスを書き込むことができます。複数の子スクリプトで必要な場合-ユーザー側で子IDを保持してそれらを識別し、各子のステータスを確認するたびにそのIDを親に送信する必要があります。
私はここでそれを見つけました- http://linuxportal.ru/forums/index.php/t/22951/
ファイルサーバーでスクリプトを呼び出して、コマンドラインでトリガーされたかのように実行するのはどうでしょうか。これを行うには、PHPの exec を使用します。
何かを実行するPHP function register-shutdown-function を使用できますafterスクリプトはブラウザとのダイアログを完了しました。
ignore_user_abort も参照してください。ただし、register_shutdown_functionを使用する場合、この関数は必要ありません。同じページで、set_time_limit(0)
はスクリプトのタイムアウトを防ぎます。
キュー、exec、またはcronを使用することは、この単純なタスクに対するやり過ぎです。同じスクリプト内に留まらない理由はありません。この組み合わせは、私にとってはうまくいきました。
ignore_user_abort(true);
$response = "some response";
header("Connection: close");
header("Content-Length: " . mb_strlen($response));
echo $response;
flush(); // releasing the browser from waiting
// continue the script with the slow processing here...
サーバーとサーバー間でhttp要求を作成できます。 (ブラウザは必要ありません)。バックグラウンドHTTP要求を作成する秘密は、非常に小さなタイムアウトを設定することであるため、応答は無視されます。
これは私がその目的のために使用した作業関数です:
5月31日PHP=非同期バックグラウンド要求PHP(バックグラウンドモードのシミュレーション)で非同期要求を作成する別の方法。
/**
* Another way to make asyncronous (o como se escriba asincrono!) request with php
* Con esto se puede simpular un fork en PHP.. nada que envidarle a javita ni C++
* Esta vez usando fsockopen
* @author PHPepe
* @param unknown_type $url
* @param unknown_type $params
*/
function phpepe_async($url, $params = array()) {
$post_params = array();
foreach ($params as $key => &$val) {
if (is_array($val)) $val = implode(',', $val);
$post_params[] = $key.'='.urlencode($val);
}
$post_string = implode('&', $post_params);
$parts=parse_url($url);
$fp = fsockopen($parts['Host'],
isset($parts['port'])?$parts['port']:80,
$errno, $errstr, 30);
$out = "POST ".$parts['path']." HTTP/1.1\r\n";
$out.= "Host: ".$parts['Host']."\r\n";
$out.= "Content-Type: application/x-www-form-urlencoded\r\n";
$out.= "Content-Length: ".strlen($post_string)."\r\n";
$out.= "Connection: Close\r\n\r\n";
if (isset($post_string)) $out.= $post_string;
fwrite($fp, $out);
fclose($fp);
}
// Usage:
phpepe_async("http://192.168.1.110/pepe/feng_scripts/phprequest/fork2.php");
詳細については、 http://www.phpepe.com/2011/05/php-asynchronous-background-request.html をご覧ください。
そのために、非常に短いタイムアウトでcURLを使用することができます。これがメインファイルになります。
_<?php>
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://example.com/processor.php");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 10); //just some very short timeout
curl_exec($ch);
curl_close($ch);
?>
_
そして、これはあなたのプロセッサファイル:
_<?php
ignore_user_abort(true); //very important!
for($x = 0; $x < 10; $x++) //do some very time-consuming task
sleep(10);
?>
_
ご覧のように、上のスクリプトは短時間(この場合は10ミリ秒)後にタイムアウトします。 _CURLOPT_TIMEOUT_MS
_がこのように機能しない可能性があります。この場合、curl_setopt($ch, CURLOPT_TIMEOUT, 1)
と同等です。
そのため、プロセッサファイルにアクセスすると、ユーザー(つまり、呼び出し元のファイル)が接続を中止したかどうかに関係なく、プロセッサファイルはタスクを実行します。
もちろん、ページ間でGETまたはPOSTパラメーターを渡すこともできます。
ああ、あなたの要求を誤解しました。彼らは実際にいるように見えます:
この場合、はい、外部ジョブキューやcronを使用すると機能します。入力が検証されたら、ジョブの詳細をキューに挿入して終了します。その後、別のスクリプトを実行して、キューからジョブの詳細を取得し、より長いプロセスを開始できます。 Alex Howanskyは正しい考えを持っています。
申し訳ありませんが、私は最初に少しスキミングしました。
これらの機能を3つのスクリプトに分割できます。 1.プロセスを開始し、execまたはcommand
経由で2番目のプロセスを呼び出します。これは、http呼び出し経由で実行することもできます。 2. 2番目はデータベース処理を実行し、最後に最後の1つを開始します3.最後の1つは電子メールを送ります
Apacheでphp.ini
構成ファイル、出力バッファリングが無効になっていることを確認します。
output_buffering = off
ユーザーとプロセスを続行するのではなく、最後に新しい非同期リクエストを生成することをお勧めします。
次の回答を使用して、他のリクエストを生成できます。 Asynchronous PHP calls?