PHPとJavaScriptでAjaxストリーミングを実装中
2011/09/16
IE以外のブラウザ用PHPコード ajax-stream-test.php
header("Content-type: text/event-stream; charset=utf-8");
header("Transfer-encoding: chunked");
ob_end_flush();
ob_start("mb_output_handler");
for ($i = 0; $i < 10; $i++)
{
if (!connection_aborted())
{
$chunk = $i * 10 ."件の処理を完了しました
";
echo sprintf("%x\r\n", strlen($chunk));
echo $chunk . "\r\n";
ob_flush();
flush();
sleep(1);
}
}
echo "0\r\n\r\n";
header(“Content-type: text/event-stream; charset=utf-8”);
header(“Transfer-encoding: chunked”);
この二つがキモとなる部分で、XmlHttpRequestでストリーミング受信するためにサーバー側でこのように設定します。ストリーミング送信を使うと、ブラウザによってデータにある程度のサイズがないと出力されないという仕様があり、データの前に何キロバイトかの空白をつけたりして対策するようですが、Content-typeをtext/event-streamやapplication/octet-streamなどとすればいいようです。そして、Transfer-encodingをchunkedとすることで、XmlHttpRequestのreadyStateが3のとき、つまりデータ送信中の時点でもデータを受け取ることができるようになります(通常のAjaxではreadyStateが4のときに処理をします)。また、チャンクごとのデータはある一定の順序で出力する必要があり、始めに個々のデータサイズを、次にデータ内容をそれぞれ改行文字(CRLF \r\n)をつけて記述します。データ出力がすべて終了したら、チャンクの区切りを表す”0″と改行文字を出力して終わりです。
次にこれを受け取るクライアント側のコードを書いてみます。簡略化のためにライブラリのPrototype.jsを使用しています。また、HTMLコードはbody要素内に<div id=”console”></div>が記述されていれば確認できます。
IE以外のブラウザ用JavaScriptコード
new Ajax.Request(
'http://wiz-code.digick.jp/test/ajax-stream/php/ajax-stream-test.php',
{
method: 'get',
onInteractive: function (response) {
$('console').update('<div>' + response.responseText + '</div>');
}.bind(this),
onSuccess: function (response) {
$('console').insert('Finish!');
}.bind(this)
}
);
通常readyStateが3のときであるInteractiveな状態では、ブラウザによって実装が異なるので、その使用は非推奨とされていました。しかし、今回はストリーミング中のデータ受信を扱っているので、Interactiveな状態のときにデータを受け取れるonInteractiveコールバック関数に処理を書いています。
⇒サンプルページ(FirefoxかChromeでご覧下さい)
ここでOperaですが、他のブラウザと異なりreadyStateが3のときにInteractiveイベントが発生しないことが明らかになっています。しかし、XHRレスポンスはしっかりとストリーミング的に返すので、setIntervalなどのタイマーで定期的にレスポンスにアクセスしていくことで対処が可能です。Opera対策はここでは割愛しますが、PHPとJavaScriptでHTTPストリーミングする話 – id:anatooのブログで具体的なコードが挙げられています。
それでは、Internet Explorerのコードを見ていきます。現段階ではストリーミングが可能なバージョン8以上を対象にしています。IEはバージョン7以上からすでにXmlHttpRequestクラスを実装しているものの、Interactive状態でレスポンスを返さないので、ストリーミングにXmlHttpRequestクラスを使用することができません。代わりにIE8の独自実装であるXDomainRequestクラスを使うと、ストリーミング通信ができます。
ただ、現段階ではローカルのみでテストしています。というのも、自分のローカル環境ではしっかり動作するものの、サーバーに置いてみるとうまく動作しないためです。自分はレンタルサーバーを使用しているので、おそらくPHP.iniや.htaccessなどの設定がローカルとサーバーで違うのが原因だと思います。コード自体は間違っていないと思うので、とりあえずPHPとJavaScriptコードを載せておきます。
IE8-9専用PHPコード ajax-stream-test-for-ie.php
header("Access-Control-Allow-Origin: *");
header("Content-type: text/plain; charset=utf-8");
ob_end_flush();
ob_start('mb_output_handler');
echo str_repeat(' ', 2048);
for ($i = 0; $i < 10; $i++)
{
$string = ($i + 1) * 10 ."件の処理を完了しました
\n";
echo $string;
ob_flush();
flush();
sleep(1);
}
header(“Access-Control-Allow-Origin: *”);
header(“Content-type: text/plain; charset=utf-8”);
IEでキモとなるのがこのAccess-Control-Allow-Originです。XDomainRequestはもともとクロスドメイン間の通信をクライアント側に可能とする技術で、サーバー側でAccess-Control-Allow-Originに許可するドメインを割り当てることで、XHR level 1では無理だったクロスドメイン通信を実現できます(あまり使われてないような気がしますが)。ここではクロスドメイン通信するわけではないですが、XDomainRequestを使用するのに必要な設定のようです。また、このときContent-typeはtext/plainとする必要があります。英語サイトですがこちらのページにtext/plainにしなさいみたいなことが何となく書かれています。それとは別に同じサイトのこのページにも有用なことが書かれていますので参考にしたいです。その他、バッファのデータサイズが2KB(2048バイト?)以上ないと出力されない仕様のため、2KB分の空白を前もってくっつける形にしています。
IE用のJavaScriptコードではまずXDomainRequestオブジェクトが存在するか確認してから処理を始めます。レスポンス内容はXHRと同じくresponseTextプロパティに入ります。
ajax-stream-test-for-ie.js
if (window.XDomainRequest) {
var xdr = new XDomainRequest();
if (xdr) {
xdr.onprogress = function () {
$('console').update('<div>' + xdr.responseText + '</div>');
}.bind(this);
xdr.onload = function () {
$('console').insert('Finish!');
}.bind(this);
url = 'http://wiz-code.digick.jp/test/ajax-stream/php/ajax-stream-test-for-ie.php?cache=' + new Date().getTime();
xdr.open('get', url);
xdr.send(null);
}
}
IEバージョンについては今後サーバーでも動くように設定を見直す予定で、それができ次第報告します。
