WIZ-CODE.blog

JavaScriptやAjaxをテーマとしたブログです。

*

PHPとJavaScriptでAjaxストリーミングを実装中

      2011/09/16

今回はAjaxストリーミングの実装を具体的に見ていこうと思います。ストリーミングの方法はブラウザによって違いがあり、それがかなり厄介でした。特にInternet Explorerは、バージョン8でようやくストリーミングが可能になったばかりで、それより前のバージョン(6、7)ではひとつのHTTPコネクションで複数のレスポンスを得るといったことができず、ロングポーリング(Comet)といった手法で代替するしかありません。IEを除くと、FirefoxChromeの最新バージョンは問題ありません。ただし、これらもバージョンが古いと正常に動作しないかもしれません。このようにAjaxストリーミングのクロスブラウザ化はかなり困難さを伴うものであるようです。 まず、ひとつの簡単なサンプルを作ることにしました。サーバーサイド(PHP5)でデータを小出しにするコードを書いて、それをクライアントサイドのAjaxで逐次受け取るというものです。データは1秒ごとに何件の処理を完了したかを出力する単純なものです。

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-typetext/event-streamapplication/octet-streamなどとすればいいようです。そして、Transfer-encodingchunkedとすることで、XmlHttpRequestreadyState3のとき、つまりデータ送信中の時点でもデータを受け取ることができるようになります(通常の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)
	}
);
 

通常readyState3のときであるInteractiveな状態では、ブラウザによって実装が異なるので、その使用は非推奨とされていました。しかし、今回はストリーミング中のデータ受信を扱っているので、Interactiveな状態のときにデータを受け取れるonInteractiveコールバック関数に処理を書いています。 ⇒サンプルページFirefoxChromeでご覧下さい) ここでOperaですが、他のブラウザと異なりreadyState3のときにInteractiveイベントが発生しないことが明らかになっています。しかし、XHRレスポンスはしっかりとストリーミング的に返すので、setIntervalなどのタイマーで定期的にレスポンスにアクセスしていくことで対処が可能です。Opera対策はここでは割愛しますが、PHPとJavaScriptでHTTPストリーミングする話 – id:anatooのブログで具体的なコードが挙げられています。 それでは、Internet Explorerのコードを見ていきます。現段階ではストリーミングが可能なバージョン8以上を対象にしています。IEはバージョン7以上からすでにXmlHttpRequestクラスを実装しているものの、Interactive状態でレスポンスを返さないので、ストリーミングにXmlHttpRequestクラスを使用することができません。代わりにIE8の独自実装であるXDomainRequestクラスを使うと、ストリーミング通信ができます。 ただ、現段階ではローカルのみでテストしています。というのも、自分のローカル環境ではしっかり動作するものの、サーバーに置いてみるとうまく動作しないためです。自分はレンタルサーバーを使用しているので、おそらくPHP.ini.htaccessなどの設定がローカルとサーバーで違うのが原因だと思います。コード自体は間違っていないと思うので、とりあえずPHPJavaScriptコードを載せておきます。

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-typetext/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バージョンについては今後サーバーでも動くように設定を見直す予定で、それができ次第報告します。

 - JavaScript/Ajax , , , , ,