November 19, 2005

[Ajax] If-Modified-Sinceヘッダを利用してWebページのキャッシュを行うXMLHttpRequestラッパー

以前書いたエントリ「[Ajax tips] XMLHttpRequest と If-Modified-Since」への一つの対応として、XMLHttpRequest にキャッシュ機能を付加するラッパーを作成してみた。

コード : xmlhttprequestwithcache.js

各種ブラウザ側でWebリソースの更新確認のマネージメントをきちんとしてくれるようになれば、このライブラリは不要になるので、それまでの一時対応的な位置づけ。

■ 使い方
 [prototype.jsを使用している場合]
 HTML上で、prototype.js を読み込んだあとに読み込むだけで、Ajax.Request() のGETリクエストがキャッシュつきになる。
 --------------------------------------------------------
 <script type="text/javascript" src="/pathtoscript/prototype.js"></script>
 <script type="text/javascript" src="/pathtoscript/xmlhttprequestwithcache.js"></script>
 --------------------------------------------------------

 [IEでXMLHTTPを使用している場合]
 var req = new ActiveXObject('Msxml2.XMLHTTP');
 var creq = new XMLHttpRequestWithCache(req);
 // 以降、creq を、XMLHttpRequest と同様に使用する。

 [IE以外でXMLHttpRequestを使用している場合]
 var req = new XMLHttpRequest();
 var creq = new XMLHttpRequestWithCache(req);
 // 以降、creq を、XMLHttpRequest と同様に使用する。

■サンプル
こちら

■テストしたブラウザとprototype.js
 Firefox 1.0.7
 Internet Explorer 6 SP1
 prototype 1.3.1

■ライセンス
ライセンスはGPL。
XMLHttpRequestのラッピング方法については、
最速インターフェース研究会の XMLHttpRequest for IE を参考にさせていただいた(こちらもGPL)。

■ 特徴
・XMLHttpRequest(あるいはMicrosoftのXMLHTTP) オブジェクトのラッパー。
 XMLHttpRequestと同じプロパティ/メソッドを持つ。
・一度GETしたWebコンテンツをキャッシュする。
・再度GETする際には、If-Modified-Since ヘッダを付与する。
 Webサーバ側から304 Not Modified が返る場合には、キャッシュしたデータをresponseText, responseXMLにセットする。
・キャッシュのアルゴリズムはデフォルトでLRUを採用(FIFOも使用可能)
prototype.js の後にインクルードするだけで、prototype.jsのAjax.Request()にキャッシュ機能が付加されるようになる。

■注意点 、Tipsなど
(1) HTTPステータスコードについて
prototype.js では、HTTPステータスコード304はfail扱いになってしまう。そこで、XMLHttpRequestと同じ使い勝手にするため、XMLHttpRequestWithCacheではHTTPステータスコード改変を行っている。
具体的には、実際のHTTPステータスコードが304であり、かつ、対象URLのデータがキャッシュ上に存在する場合、statusプロパティを200に、statusTextプロパティを'OK'に設定している
ただし、HTTPレスポンスヘッダーは改変しないので、この点については注意が必要。

(2) デフォルト If-Modified-Since
初回のアクセス(キャッシュがない場合のアクセス)では、If-Modified-Sinceに「01 Jan 1970 00:00:00 GMT」がセットされる。
ただし、If-Modified-Since は XMLHttpRequestWithCache呼出し側のプログラムにて、setRequestHeader()を用いて上書きできる。

(3) キャッシュの操作
ブラウザのアドレスバーに以下のように入力することで、キャッシュを操作することが可能(もちろん、Javascriptコード内でも可能)
 ・キャッシュの中身を見る(注意:キャッシュに大量のデータが入っている時にはブラウザが不安定になるかもしれません。)
  javascript:alert(XMLHttpRequestWithCache.cache)

 ・キャッシュのサイズの変更(デフォルトは100コンテンツ)
  javascript:alert(XMLHttpRequestWithCache.resizeCache(20))

 ・キャッシュアルゴリズムをFIFOにする。
  javascript:alert(XMLHttpRequestWithCache.cacheAlgorithm('FIFO'))

投稿者 msano : 04:55 PM | コメント (0)

April 07, 2005

[Ajax tips] XMLHttpRequest と If-Modified-Since

RSSリーダーについてさんざん言及されたように、Webコンテンツを取得するアプリケーションでは、 HTTPリクエストに If-Modified-Since ヘッダ をつけるなどして対象コンテンツの更新時刻をチェックし、過剰なデータ取得を避けるのがマナーであるとされている。

同じことがAjaxについても当てはまると考える。Ajaxでは、ユーザーのアクションと非同期にHTTPリクエストを行うため、RSSリーダーと同様に人為操作を超えるトラフィックを発せさせる可能性があるからだ。

そこで、素朴な疑問。
 ・ブラウザは、ユーザーが意識しないでも更新時刻チェックをやってくれる。
 ・Ajaxの主なエンジンとなる Javascript は、ブラウザに組み込まれたものである。
 ・Javascript の XMLHttpRequestを使用すると、プログラマが明示的にコーディングしなくても、更新時刻チェックを実行してくれるのではないか。

というわけで、簡単な実験をしてみた。概要は次のとおり。
 1) XMLHttpRequestで適当なWebページを数回GET
 2) Webサーバのログをみて、更新時刻チェックしているかチェック
なお、対象Webページは、「ブラウザで以前アクセスしたことがあるが、最終更新日時以降はアクセスしていない」状態である。
また、テスト内容は完全なものではなく、あらゆる環境および状況で同様の結果が得られることを保証するものではないのであしからず。

以下、詳細と結果。
まず、次のようなテスト用Webページを設置。
index.htmlというHTMLファイル(なんでもいい)を、XMLHttpRequestでGETするだけのJavascriptスクリプトだ。
------------------------------------------------------------------------------
<html>
<head>
<script type="text/javascript">
function testfunc() {
 var xmlhttp = false;
 if (typeof XMLHttpRequest!='undefined')
  xmlhttp = new XMLHttpRequest();
 else
  xmlhttp = new ActiveXObject("MSXML2.XMLHTTP");

 xmlhttp.open("GET", 'index.html', false);
 xmlhttp.send(null);

 document.getElementById("r").innerHTML=xmlhttp.getAllResponseHeaders();
 document.getElementById("c").innerHTML=xmlhttp.responseText;
}
</script>
<title>ajax If-Modified-Since test</title>
</head>
<body>
<button onclick="testfunc()">click!</button>
<hr />
<pre id="r"></pre>
<hr />
<div id="c"></div>
</body>
</html>
------------------------------------------------------------------------------

このWebページのボタンを数回クリックして、Webサーバーのログを見たところ、次の結果となった。
【結果1:If-Modified-Since未コーディング】
・firefox1.0.2の場合
 1回目のアクセス→ステータスコード200でGET
 2回目以降のアクセス→ステータスコード200でGET
・IE6 SP1の場合
 1回目のアクセス→ロギングされない
 2回目以降のアクセス→ロギングされない

firefoxでは、更新時刻チェックを行ってくれないようだ。
IEでは、なぜかWebサーバにアクセスしていないようだ。しかし、XMLHttpRequest.responseTextには値が入っている。ブラウザのローカルキャッシュをそのまま入れているように見受けられる。ブラウザのローカルキャッシュ(インターネット一時ファイル)をいったん削除して、再度テストを行うと、1回目だけは200でGETしてくるが、2回目以降はやはりWebサーバにアクセスしない。ここまではいいが、Webページを編集して更新時刻を変更してから再度テストしても、Webサーバにアクセスせずにローカルのキャッシュを取得し続けてしまう。テストの仕方が悪いのだろうか。この動きはAjaxアプリケーション作成時にやっかいだ。

そこで、こんどは明示的にIf-Modified-Sinceによる更新時刻処理をコーディングしてみた。Webページのスクリプト部分を次のように変更して、再度テストを実施。
(ロジックはきれいじゃないが。。。)
------------------------------------------------------------------------------
<script type="text/javascript">
var last_modified = null; /*← この行を追加*/
var cached_content = ''; /*← この行を追加*/
function testfunc() {
 var xmlhttp = false;
 if (typeof XMLHttpRequest!='undefined')
  xmlhttp = new XMLHttpRequest();
 else
  xmlhttp = new ActiveXObject("MSXML2.XMLHTTP");

 xmlhttp.open("GET", 'index.html', false);
 if(last_modified) /*← この行を追加*/
  xmlhttp.setRequestHeader("If-Modified-Since", last_modified); /*← この行を追加*/
 xmlhttp.send(null);

 document.getElementById("r").innerHTML=xmlhttp.getAllResponseHeaders();
 document.getElementById("c").innerHTML=xmlhttp.responseText;
 if(xmlhttp.getAllResponseHeaders().match("Last-Modified")) /*← この行を追加*/
  last_modified = xmlhttp.getResponseHeader("Last-Modified"); /*← この行を追加*/
 if(xmlhttp.responseText.length == 0) /*← この行を追加*/
  document.getElementById("c").innerHTML = cached_content; /*← この行を追加*/
 else /*← この行を追加*/
  cached_content = xmlhttp.responseText; /*← この行を追加*/
}
</script>
------------------------------------------------------------------------------
結果は、次のとおり。
【結果2:If-Modified-Sinceチェックを明示的にコーディング】
・firefox1.0.2の場合
 1回目のアクセス→ステータスコード200でGET
 2回目以降のアクセス→ステータスコード304でGET
・IE6 SP1の場合
 1回目のアクセス→ロギングされない
 2回目のアクセス→ステータスコード200でGET
 3回目以降のアクセス→ステータスコード304でGET

firefoxでは、それなりの動きになった。
IEでは、firefoxよりワンテンポ遅れたような動きになっている。1回目のアクセスではやはりローカルキャッシュを見ているのだろう。
結果1と結果2から総合すると、IE6SP1の動きは、「If-Modified-Sinceを付加しない場合、たとえWebページが更新されていても、何度アクセスしてもローカルキャッシュを見に行く。」ように見える。

最後に、スクリプトの「var last_modified = null;」の部分を「var last_modified = "Thu, 01 Jun 1970 00:00:00 GMT";」と変更して再度テストを試みた。
結果は、次のとおり。
【結果3:1回目からIf-Modified-Sinceをつける】
・IE6 SP1の場合
 1回目のアクセス→ステータスコード200でGET
 2回目以降のアクセス→ステータスコード304でGET

上記のとおり、firefoxと同じ動きになった。


結論としては、
「ブラウザ(Javascript)に任せず、If-Modified-Sinceの処理は自分で明示的にコーディングすべし」
となる(当たり前?)。

追記:If-Modified-Sinceを使用してキャッシュを行うXMLHttpRequestラッパーを書いてみた。


補遺
Ajaxアプリケーション作成の際、結果3の方法は十分ではない。それは、結果2においてfirefoxを「それなりの動き」と表現した理由でもある。
上記のスクリプトでは、単純なHTTPリクエストのほかに次のことを実施している。
 1) Last-Modified情報の保存(次にIf-Modified-Sinceに利用するため)
 2) Webページの内容のキャッシュ
これに加えて、本来は、
 3) 1)と2)で保存した内容の永続化(ブラウザを再起動しても前回の内容が保存されている)
が必要だ。
上記のスクリプトでは、3) ができていないので、「ブラウザをリロードしたらJavascriptが再スタートし、保存しておいたLast-Modified情報がリセットされてしまう」問題を抱えている。この問題を解決するには、次の2つが必要だ。
 i) Javascriptからブラウザのローカルキャッシュ情報(Last-Modifiedなど)にアクセスできること
 ii) XMLHttpRequestでWebコンテンツを取得した場合でも、ブラウザのローカルキャッシュに追加されること
この面については、IEの動きはある意味firefoxより優れている気がする。しかし、対象Webページが更新されていてもIf-Modified-Sinceをつけないかぎりローカルキャッシュを常に見るという動きはなんとかしてほしいところだ。

Ajaxアプリケーションが普及するためには、以上のような処理を、プログラマが意識することなくブラウザ(Javascriptインタプリタ)が調整してくれることが望ましい。何かやり方があるのだろうか。

投稿者 msano : 11:09 PM | コメント (6) | トラックバック

March 08, 2005

XML-RPCのJavascript実装

最近ajaxというアーキテクチャがはやっている。
それはそれはやたらはやっている。

ajaxの肝の一つに、クライアント(ブラウザ)とサーバーの非同期通信というのがある。そうなると、通信プロトコルをどうするか、といった話が盛り上がってもよさそうだ。(独自路線をつっぱしる人はそれはそれでよいと思う。なんせ、XMLHttpRequestオブジェクトは、ブラウザによっては自分自身のサーバーとしか通信できないらしいから)。

で、ここ数年のblogの盛り上がりから、やっぱり、まず検討すべきは XML-RPC だと考えている。
そこで、誰かライブラリ(XML-RPCのjavascript実装)を作っていないか探してみた。google://javascript+xml-rpc
どうも、ぱっとしたのがない。??
ふと思い立って worldwideでgoogle://javascript+xml-rpc
してみたら、、、いろいろでてきた。日本はだいぶおくれているのか?私の探し方が悪いのか?

一通り機能がそろっていて使えそうなのは、次の2つ。
1) vcXMLRPC
  ライセンス:GPL
  Copyright が2000-2002と古い。
  一つのファイルにまとめられていてコンパクト。
  IEとfirefoxは大丈夫そう。safariはどうだろう?
    
2) JavaScript O Lait
 ライセンス:LGPL
  Copyrightは 2003-2004で、結構あたらしい
  8つのライブラリファイルに分かれている。(XML-RPCを使いたければ、xmlrpc.js, xml.js, urllib.js が必要。)
  中身をさらっと見た感じでは、どのブラウザでも使えるようにつくってある気はする。(実際につかってないので、なんともいえない。)

vcXMLRPC をちょっと加工して使ってみようかな。
JavaScript O Lait は品質が高そうなんだけど、ちょっと大掛かりなので。

ところで、JSONってなんなんだろう。。。

投稿者 msano : 01:13 AM | コメント (0) | トラックバック

February 23, 2005

javascript の XMLHttpRequest

http://www.scss.com.au/family/andrew/webdesign/xmlhttprequest/

javascript で、サーバーと通信できるプログラム。
しかも、Cross-Browser。

これをRNAのテンプレートに入れていろいろやってみたいが、ライセンスが 「帰属、非営利、同一条件許諾」なので、デフォルトテンプレートに入れるにはちょっと制限がきつい気がする。

投稿者 msano : 10:08 PM | コメント (0) | トラックバック