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


99

かなり長いプロセスを開始する(JQueryを介して)AJAX呼び出しを実行しようとしています。スクリプトがプロセスが開始したことを示す応答を送信するだけでいいのですが、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( 'dude@thatplace.com', "okay I'm done", 'Yup, all done.' );

?>

ob_flush()を使用して出力バッファーをフラッシュしましたが、機能しませんでしたか?
Vinko Vrsalovic 2008

回答:


87

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

おそらく、それは閉じるヘッダーを送るよりも少し多くを必要とします。


OPは確認します。うん、これでうまく いきました。ここにコピーされたユーザーノート#71172(2006年11月)を指します。

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

sts at mail dot xubion dot hu元のソリューションを投稿しました:

<?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つのユーザーノートを上記のものにリンクしました。



1
著者と@Timbo White、コンテンツのサイズを知らなくても、接続を早期に閉じることは可能ですか?IE、終了前にコンテンツをキャプチャする必要はありません。
skibulk 2014

3
ハッカーや安っぽいWebブラウザーは、connection-close HTTPヘッダーを無視して、残りの出力を取得することができます。おそらくob_start(); すべてを抑圧する:p
hanshenrik

3
fastcgi_finish_request();を追加する 上記が機能しない場合、接続を正常に閉じると言われています。ただし、私の場合、スクリプトの実行を続行できなかったため、注意して使用してください。
EricDubé16年

@RichardSmith Connection: closeヘッダーはスタック内の他のソフトウェア、たとえば、CGIの場合のリバースプロキシによって上書きされる可能性があるため(nginxでその動作を観察しました)。それについては@hanshenrikからの答えを見てください。通常、Connection: closeはクライアント側で実行され、この質問への回答と見なすべきではありません。接続はサーバー側から閉じる必要があります
7heo.tk

56

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

詳細については、http://www.zulius.com/how-to/close-browser-connection-continue-executionにアクセスしてください


16
サーバーが出力を圧縮する場合は、header("Content-Encoding: none\r\n");Apacheが出力を圧縮しないようにして無効にすることができます 。
GDmac

1
@GDmacありがとうございます!これをしばらく動作させることができませんでしたが、圧縮を無効にするとうまくいきました。
Reactgular 2013

これob_flush()は必要ではなく、実際には通知が表示されfailed to flush bufferます。私はそれを取り出し、これはうまくいきました。
Levi

2
ob_flush()ライン必要だとわかりました。
Deebster 2014年

21

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

これは、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() シャットダウン機能の実行を呼び出すことができます。


注: fastcgi_finish_request()持っているの呼び出しflushprintまたはecho早期のスクリプトを終了しますが。

この問題を回避するignore_user_abort(true)には、fastcgi_finish_request通話の直前または直後に電話をかけます。

ignore_user_abort(true);
fastcgi_finish_request();

3
これは実際の答えです!
Kirill Titov 2015

2
php-fpmを使用している場合-この関数を使用してください-ヘッダーやその他すべてを忘れてください。時間を大幅に節約できました。
ロス

17

完全版:

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

どの意味で完全ですか?承認された回答スクリプト(どのスクリプトですか)を完了する必要があるのはどの問題ですか。また、どの構成の違いによりこれが必要になりましたか?
hakre 2013

4
この行:header( "Content-Encoding:none"); ->非常に重要です。
ボビーテーブル

2
おかげで、これはこのページで唯一の有効なソリューションです。これは答えとして承認されるべきです。

6

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

<?php
echo "We'll email you as soon as this is done.";
system("php somestuff.php dude@thatplace.com >/dev/null &");
?>

より良い例については、この質問を見てください:

PHPがバックグラウンドプロセスを実行する


4

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

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

mkdir test
chmod -R 777 test
cd test

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

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

に注意してください&。現在のプロセスがechoコマンドに進む間、pingコマンドはバックグラウンドで実行されます。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

これは、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 ********/

あなたは神です。私はこれを試すために2日間働いてきました。それは私のローカル開発者では機能しましたが、ホストでは機能しませんでした。私はうんざりしました。あなたは私を救いました。ありがとうございました!!!!
チャドコールドウェル

3

私は共有ホスト上にいて、fastcgi_finish_requestスクリプトを完全に終了するように設定されています。connection: close解決策も好きではありません。それを使用すると、後続のリクエストに対して個別の接続が強制され、追加のサーバーリソースが消費されます。私はTransfer-Encoding: cunked ウィキペディアの記事を読み、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);
}

あなたの良い答えのおかげで、私は接続を使用することがどれほど愚かであるか(そして不必要であるか)を理解しました。一部のユーザーは、サーバーの基本に精通していないと思います。
ジャスティン

@Justin私はずっと前にこれを書いた。もう一度見てみると、チャンクを4KBにパディングする必要があるかもしれないことに注意する必要があります。一部のサーバーは、その最小値に達するまでフラッシュしないことを覚えているようです。
Skibulk 2018年

2

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

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

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


また、プロセスが完了するのを待たない場合は、&キャラクターを使用してバックグラウンドでプロセスを実行します。
リアム

2

TL; DR Answer:

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.

1

あなたの問題は、phpで並列プログラミングを行うことで解決できます。私は数週間前にここでそれについて質問しました:PHPアプリケーションでマルチスレッドをどのように使用できますか

そして素晴らしい答えを得ました。特にひとつ好きでした。筆者は、PHPでのEasy Parallel Processing(2008年9月; johnlim)のチュートリアルを参照しました。このチュートリアルは、数日前に発生した同様の問題に対処するために既に使用しているので、実際に問題を解決できます。


1

Joeri Sebrechtsの答えは近いですが、切断する前にバッファリングされている可能性のある既存のコンテンツはすべて破棄されます。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

mod_fcgidユーザー向けの注意(自己責任で使用してください)。

迅速な解決策

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

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

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

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

FcgidOutputBufferSize 0

Apache構成ファイル(httpd.confなど)、FCGI構成ファイル(fcgid.confなど)、または仮想ホストファイル(httpd-vhosts.confなど)のいずれかにあります。

上記(1)では、「OutputBufferSize」という変数について言及しています。これは、FcgidOutputBufferSize(2)で述べた古い名前です(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>

私は多くの解決策を試しました。そして、これが私にとってmod_fcgidで機能する唯一のソリューションです。
ツォナベ

1

これは私のために働いた

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

0

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

<?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()などは、Webサーバーによって実行されるPHPでは無効にされていましたが、シェルスクリプトで実行できました。


0

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

output_buffering = Off  
zlib.output_compression = Off  

0

最新の作業ソリューション

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

0

このスレッドから多くの異なる解決策を試した後(それらのどれも私のために機能しなかった後)、私は公式の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();
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.