web-dev-qa-db-ja.com

接続を早期に終了するにはどうすればよいですか?

私はかなり長いプロセスを開始するAJAX呼び出し(JQuery経由)を実行しようとしています。スクリプトがプロセスが開始したことを示す応答を送信するだけですが、JQuery PHPスクリプトの実行が完了するまで、応答を返しません。

これを「近い」ヘッダー(下)と出力バッファリングで試しました。どちらも機能していないようです。推測はありますか?またはこれは私がJQueryで行う必要があるものですか?

<?php

echo( "We'll email you as soon as this is done." );

header( "Connection: Close" );

// do some stuff that will take a while

mail( '[email protected]', "okay I'm done", 'Yup, all done.' );

?>
92
Eric_WVGG

次のPHPマニュアルページ(ユーザーノートを含む)は、TCPスクリプトを終了せずにブラウザへのPHP接続を閉じる方法に関する複数の指示を示しています。

おそらく、近いヘッダーを送信するよりも少し多く必要です。


OPはそれから確認します:うん、これはトリックでした:ユーザーノート#71172(2006年11月)を指す ここにコピー:

[PHP] 4.1からregister_shutdown_function()の動作が変更され、ユーザー接続が自動的に閉じられないため、PHPスクリプトを実行したままユーザーブラウザー接続を閉じることは問題でした。

メールドットxubionドットhuのsts元のソリューションを投稿しました:

_<?php
header("Connection: close");
ob_start();
phpinfo();
$size = ob_get_length();
header("Content-Length: $size");
ob_end_flush();
flush();
sleep(13);
error_log("do something in the background");
?>
_

phpinfo()echo('text I want user to see');に置き換えるまで正常に機能します。この場合、ヘッダーは送信されません。

解決策は、ヘッダー情報を送信する前に、出力バッファリングを明示的にオフにし、バッファをクリアすることです。例:

_<?php
ob_end_clean();
header("Connection: close");
ignore_user_abort(true); // just to be safe
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 !
// Do processing here 
sleep(30);
echo('Text user will never see');
?>
_

これを理解しようとして3時間を費やしただけで、誰かの助けになることを願っています:)

テスト済み:

  • IE 7.5730.11
  • Mozilla Firefox 1.81

後で2010年7月に 関連する回答Arctic Fire さらに2つのユーザーメモをリンク上記のフォローアップ:

83
Joeri Sebrechts

次の2つのヘッダーを送信する必要があります。

Connection: close
Content-Length: n (n = size of output in bytes )

出力のサイズを知る必要があるため、出力をバッファリングしてからブラウザにフラッシュする必要があります。

// buffer all upcoming output
ob_start();
echo "We'll email you as soon as this is done.";

// get the size of the output
$size = ob_get_length();

// send headers to tell the browser to close the connection
header("Content-Length: $size");
header('Connection: close');

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

// if you're using sessions, this prevents subsequent requests
// from hanging while the background process executes
if (session_id()) session_write_close();

/******** background process starts here ********/

また、Webサーバーが出力で自動gzip圧縮を使用している場合(つまり、mod_deflateを使用したApache)、出力の実際のサイズが変更され、Content-Lengthが正確ではなくなるため、これは機能しません。特定のスクリプトのgzip圧縮を無効にします。

詳細については、 http://www.zulius.com/how-to/close-browser-connection-continue-execution をご覧ください。

54
Timbo White

PHP-FPMでFast-CGIを使用して、 fastcgi_end_request() function を使用できます。このようにして、応答が既にクライアントに送信されている間に、いくつかの処理を続行できます。

これは、PHPマニュアル: FastCGI Process Manager(FPM) ;にありますが、その機能はマニュアルに詳しく記載されていません。ここからの抜粋は- PHP-FPM:PHP FastCGI Process Manager Wiki


fastcgi_finish_request()

スコープ:php関数カテゴリ:最適化

この機能により、いくつかのphpクエリの実装を高速化できます。スクリプトの実行中にサーバーの応答に影響を与えないアクションがある場合、高速化が可能です。たとえば、セッションがmemcachedに保存されるのは、ページが作成されてWebサーバーに渡された後です。 fastcgi_finish_request()は、応答出力を停止するphp機能です。 Webサーバーはすぐに応答を「ゆっくりと悲しい」クライアントに転送し始め、同時にphpはセッションの保存、ダウンロードしたビデオの変換、あらゆる種類の処理など、クエリのコンテキストで多くの便利なことを実行できます統計などの.

fastcgi_finish_request()は、シャットダウン関数の実行を呼び出すことができます。

16

完全版:

ignore_user_abort(true);//avoid Apache to kill the php running
ob_start();//start buffer output

echo "show something to user";
session_write_close();//close session file on server side to avoid blocking other requests

header("Content-Encoding: none");//send header to avoid the browser side to take content as gzip format
header("Content-Length: ".ob_get_length());//send length header
header("Connection: close");//or redirect to some url: header('Location: http://www.google.com');
ob_end_flush();flush();//really send content, can't change the order:1.ob buffer to normal buffer, 2.normal buffer to output

//continue do something on server side
ob_start();
sleep(5);//the user won't wait for the 5 seconds
echo 'for diyism';//user can't see this
file_put_contents('/tmp/process.log', ob_get_contents());
ob_end_clean();
15
diyism

以下は、gzip圧縮で動作するTimboのコードの修正です。

// buffer all upcoming output
if(!ob_start("ob_gzhandler")){
    define('NO_GZ_BUFFER', true);
    ob_start();
}
echo "We'll email you as soon as this is done.";

//Flush here before getting content length if ob_gzhandler was used.
if(!defined('NO_GZ_BUFFER')){
    ob_end_flush();
}

// get the size of the output
$size = ob_get_length();

// send headers to tell the browser to close the connection
header("Content-Length: $size");
header('Connection: close');

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

// if you're using sessions, this prevents subsequent requests
// from hanging while the background process executes
if (session_id()) session_write_close();

/******** background process starts here ********/
4
AWinter

より良い解決策は、バックグラウンドプロセスをフォークすることです。 unix/linuxではかなり簡単です。

<?php
echo "We'll email you as soon as this is done.";
system("php somestuff.php [email protected] >/dev/null &");
?>

より良い例については、この質問をご覧ください。

PHPはバックグラウンドプロセスを実行します

4
Salman A

Linuxサーバーとルートアクセスがあると仮定して、これを試してください。それは私が見つけた最も簡単な解決策です。

次のファイル用に新しいディレクトリを作成し、完全な権限を付与します。 (後でより安全にすることができます。)

mkdir test
chmod -R 777 test
cd test

これをbgpingというファイルに入れます。

echo starting bgping
ping -c 15 www.google.com > dump.txt &
echo ending bgping

&に注意してください。 pingコマンドはバックグラウンドで実行され、現在のプロセスはechoコマンドに移行します。 www.google.comに15回pingを送信します。これには約15秒かかります。

実行可能にします。

chmod 777 bgping

これをbgtest.phpというファイルに入れてください。

<?php

echo "start bgtest.php\n";
exec('./bgping', $output, $result)."\n";
echo "output:".print_r($output,true)."\n";
echo "result:".print_r($result,true)."\n";
echo "end bgtest.php\n";

?>

ブラウザでbgtest.phpをリクエストすると、pingコマンドが完了するまで約15秒待たずに、次の応答をすばやく取得する必要があります。

start bgtest.php
output:Array
(
    [0] => starting bgping
    [1] => ending bgping
)

result:0
end bgtest.php

これで、pingコマンドがサーバーで実行されているはずです。 pingコマンドの代わりに、PHPスクリプトを実行できます。

php -n -f largejob.php > dump.txt &

お役に立てれば!

4
Liam

私は共有ホスト上にあり、fastcgi_finish_requestはスクリプトを完全に終了するように設定されています。 connection: closeソリューションも好きではありません。これを使用すると、後続の要求に対して別の接続が強制され、追加のサーバーリソースが消費されます。 Transfer-Encoding: cunkedWikipedia Article を読みましたが、0\r\n\r\nが応答を終了することがわかりました。ブラウザのバージョンとデバイス間でこれを完全にテストしたことはありませんが、現在の4つのブラウザすべてで動作します。

// Disable automatic compression
// @ini_set('zlib.output_compression', 'Off');
// @ini_set('output_buffering', 'Off');
// @ini_set('output_handler', '');
// @Apache_setenv('no-gzip', 1);

// Chunked Transfer-Encoding & Gzip Content-Encoding
function ob_chunked_gzhandler($buffer, $phase) {
    if (!headers_sent()) header('Transfer-Encoding: chunked');
    $buffer = ob_gzhandler($buffer, $phase);
    return dechex(strlen($buffer))."\r\n$buffer\r\n";
}

ob_start('ob_chunked_gzhandler');

// First Chunk
echo "Hello World";
ob_flush();

// Second Chunk
echo ", Grand World";
ob_flush();

ob_end_clean();

// Terminating Chunk
echo "\x30\r\n\r\n";
ob_flush();
flush();

// Post Processing should not be displayed
for($i=0; $i<10; $i++) {
    print("Post-Processing");
    sleep(1);
}
3
skibulk

マルチスレッド化を試みることができます。

スクリプトとしてPHPバイナリを呼び出してパラメーターとして作業を行うシステムコール( Shell_exec を使用)を実行するスクリプトを作成できます。しかし、私はそれが最も安全な方法だとは思いません。たぶん、PHPプロセスと他のものをchrootすることによって、ものを強化することができます

あるいは、phpclassesにそれを行うクラスがあります http://www.phpclasses.org/browse/package/3953.html 。しかし、私は実装の詳細を知りません

2
paan

TL; DR回答:

ignore_user_abort(true); //Safety measure so that the user doesn't stop the script too early.

$content = 'Hello World!'; //The content that will be sent to the browser.

header('Content-Length: ' . strlen($content)); //The browser will close the connection when the size of the content reaches "Content-Length", in this case, immediately.

ob_start(); //Content past this point...

echo $content;

//...will be sent to the browser (the output buffer gets flushed) when this code executes.
ob_end_flush();
ob_flush();
flush();

if(session_id())
{
    session_write_close(); //Closes writing to the output buffer.
}

//Anything past this point will be ran without involving the browser.

関数の答え:

ignore_user_abort(true);

function sendAndAbort($content)
{
    header('Content-Length: ' . strlen($content));

    ob_start();

    echo $content;

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

sendAndAbort('Hello World!');

//Anything past this point will be ran without involving the browser.
2

PHPで並列プログラミングを行うことで問題を解決できます。数週間前にここで質問しました: どのようにPHP applications

そして素晴らしい答えを得ました。特にとても気に入った。ライターはリファレンスを作成しました PHPでの簡単な並列処理(2008年9月; johnlimによる)チュートリアル 数日前に出てきた同様の問題に対処するためにすでにそれを使用したので、問題は非常にうまくいきました。

1
Steve Obbayi

Joeri Sebrechts 'answer は近いですが、切断する前にバッファリングされている既存のコンテンツをすべて破棄します。 ignore_user_abortが正しく呼び出されないため、スクリプトが途中で終了する可能性があります。 diyismの答え は良いですが、一般的には適用できません。例えば。その答えが処理しない出力バッファーが多い場合も少ない場合もあるため、状況によっては機能しない場合があり、その理由はわかりません。

この機能を使用すると、いつでも接続を切断でき(ヘッダーがまだ送信されていない限り)、これまでに生成したコンテンツを保持します。追加の処理時間はデフォルトで無制限です。

function disconnect_continue_processing($time_limit = null) {
    ignore_user_abort(true);
    session_write_close();
    set_time_limit((int) $time_limit);//defaults to no limit
    while (ob_get_level() > 1) {//only keep the last buffer if nested
        ob_end_flush();
    }
    $last_buffer = ob_get_level();
    $length = $last_buffer ? ob_get_length() : 0;
    header("Content-Length: $length");
    header('Connection: close');
    if ($last_buffer) {
        ob_end_flush();
    }
    flush();
}

余分なメモリも必要な場合は、この関数を呼び出す前に割り当ててください。

1
Walf

これは私のために働いた

//avoid Apache to kill the php running
ignore_user_abort(true);
//start buffer output
ob_start();

echo "show something to user1";
//close session file on server side to avoid blocking other requests
session_write_close();

//send length header
header("Content-Length: ".ob_get_length());
header("Connection: close");
//really send content, can't change the order:
//1.ob buffer to normal buffer,
//2.normal buffer to output
ob_end_flush();
flush();
//continue do something on server side
ob_start();
//replace it with the background task
sleep(20);
1
Williem

mod_fcgidユーザーへの注意(ご自身の責任で使用してください)

クイックソリューション

Joeri Sebrechts の受け入れられた答えは確かに機能的です。ただし、mod_fcgidを使用すると、このソリューションが単独では機能しないことがあります。つまり、flush関数が呼び出されると、クライアントへの接続は閉じられません。

mod_fcgidFcgidOutputBufferSize構成パラメーターが原因である可能性があります。このヒントを見つけました:

  1. Travers Carterのこの返信 および
  2. Seumas Mackinnonのこのブログ投稿

上記を読んだ後、簡単な解決策は行を追加することであるという結論に達するかもしれません(最後の「仮想ホストの例」を参照)。

FcgidOutputBufferSize 0

apache構成ファイル(例:httpd.conf)、FCGI構成ファイル(例:fcgid.conf)、または仮想ホストファイル(例:httpd-vhosts.conf)。

上記(1)には、「OutputBufferSize」という名前の変数が記載されています。これは、(2)で言及されているFcgidOutputBufferSizeの古い名前です(mod_fcgidのApache Webページの アップグレードノートを参照 )。

詳細と2番目のソリューション

上記のソリューションは、サーバー全体または特定の仮想ホストのいずれかに対してmod_fcgidによって実行されるバッファリングを無効にします。これにより、Webサイトのパフォーマンスが低下する可能性があります。一方、PHPは独自にバッファリングを実行するため、これは当てはまらないかもしれません。

mod_fcgidのバッファリングを無効にしたくない場合は、別の解決策があります...このバッファを強制的にフラッシュすることができます

以下のコードは、Joeri Sebrechtsが提案したソリューションに基づいて作成したものです。

<?php
    ob_end_clean();
    header("Connection: close");
    ignore_user_abort(true); // just to be safe
    ob_start();
    echo('Text the user will see');

    echo(str_repeat(' ', 65537)); // [+] Line added: Fill up mod_fcgi's buffer.

    $size = ob_get_length();
    header("Content-Length: $size");
    ob_end_flush(); // Strange behaviour, will not work
    flush(); // Unless both are called !
    // Do processing here 
    sleep(30);
    echo('Text user will never see');
?>

追加されたコード行が本質的に行うことは、mod_fcgiのバッファーをいっぱいにすることで、強制的にフラッシュします。 対応するディレクティブのApache Webページ で説明されているように、FcgidOutputBufferSize変数のデフォルト値が "65536"であるため、数字 "65537"が選択されました。したがって、環境に別の値が設定されている場合は、それに応じてこの値を調整する必要があります。

私の環境

  • WampServer 2.5
  • Apache 2.4.9
  • PHP 5.5.19 VC11、x86、非スレッドセーフ
  • mod_fcgid/2.3.9
  • Windows 7 Professional x64

仮想ホストの例

<VirtualHost *:80>
    DocumentRoot "d:/wamp/www/example"
    ServerName example.local

    FcgidOutputBufferSize 0

    <Directory "d:/wamp/www/example">
        Require all granted
    </Directory>
</VirtualHost>
1
Tasos

flush()関数が機能しない場合。次のオプションをphp.iniのように設定する必要があります:

output_buffering = Off  
zlib.output_compression = Off  
0
Nir O.

それでは、jQueryがXHRリクエストを行う方法と同じように、各onreadystatechangeで関数を実行できないため、ob_flushメソッドも機能しません。 jQueryは状態をチェックし、実行する適切なアクション(完了、エラー、成功、タイムアウト)を選択します。また、リファレンスを見つけることができませんでしたが、これはすべてのXHR実装で機能するとは限らないと聞いたことを思い出します。私があなたのために働くべきだと思う方法は、ob_flushとforever-frameポーリングのクロスです。

<?php
 function wrap($str)
 {
  return "<script>{$str}</script>";
 };

 ob_start(); // begin buffering output
 echo wrap("console.log('test1');");
 ob_flush(); // Push current buffer
 flush(); // this flush actually pushed to the browser
 $t = time();
 while($t > (time() - 3)) {} // wait 3 seconds
 echo wrap("console.log('test2');");
?>

<html>
 <body>
  <iframe src="ob.php"></iframe>
 </body>
</html>

また、バッファーがフラッシュされると、スクリプトがインラインで実行されるため、実行が可能になります。これを有効にするには、console.logを、メインスクリプトのセットアップで定義されたコールバックメソッドに変更して、データを受信し、それに対処します。お役に立てれば。乾杯、モーガン。

0

別の解決策は、ジョブをキューに追加し、新しいジョブをチェックして実行するcronスクリプトを作成することです。

私は最近、共有ホストによって課せられた制限を回避するためにそのようにしなければなりませんでした-exec()などはPHP Webサーバーによって実行されますが、シェルスクリプトで実行できます。

0
Ole Helgesen

最新のソリューション

    // client can see outputs if any
    ignore_user_abort(true);
    ob_start();
    echo "success";
    $buffer_size = ob_get_length();
    session_write_close();
    header("Content-Encoding: none");
    header("Content-Length: $buffer_size");
    header("Connection: close");
    ob_end_flush();
    ob_flush();
    flush();

    sleep(2);
    ob_start();
    // client cannot see the result of code below

このスレッドからさまざまな解決策を試した後(どれも役に立たなかった後)、PHP.netの公式ページで解決策を見つけました。

function sendResponse($response) {
    ob_end_clean();
    header("Connection: close\r\n");
    header("Content-Encoding: none\r\n");
    ignore_user_abort(true);
    ob_start();

    echo $response; // Actual response that will be sent to the user

    $size = ob_get_length();
    header("Content-Length: $size");
    ob_end_flush();
    flush();
    if (ob_get_contents()) {
        ob_end_clean();
    }
}
0
WhiteAngel