« March 2005 | メイン | September 2005 »

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) | トラックバック

April 05, 2005

RSSにどこまで書くか

RSS(Atom)にサマリーのみ書くか全文まで書くかという議論はずっと昔からあるような気がする。

RSS(Atom)を利用する代表的な目的はWeb情報チェックの効率化だけど、それは次のように分解できる。
 a) 「更新チェック」を効率化したい人
   更新があったら対象Webページを直接見に行く人。
   この人にとってはRSSはtitleとlinkとdateだけで事足りるのではないだろうか。
   glucoseなど、Webサイトを直接ブラウズできるリーダーを使うとよい。
 b) 「内容をチェックするどうかの判断」も効率化したい人
   この人に必要なのはtitleとサマリー(<description>や<summary>)だ。
   サマリーをざっとみてさらに詳しく見たければ直接Webページをブラウズする。
   重要な点は、まともなサマリーがちゃんと書かれているかということ。
   先頭n文字で切っているだけの<description>はサマリーとはいえないと思う(文章のうまい人は先頭n文字が要約になるような書き方をするのかもしれないけど)。   
 c) 「内容チェック」までも効率化したい人
   この人にとってはRSSに全文が含まれていることが望ましい。
   サマリーでも代替になるだろうが、内容を取得元Webサイトに確認しにいく必要の無いほどの、秀逸なサマリーが常に配信されている必要がある。

RSSにどこまで書くかという話は、RSS利用者側が何を必要としているかという問題になる。同じ人でも、スポーツニュースはサマリーでよいが、政治ニュースは詳細を知りたいかもしれない。

たとえば、次のように詳細度の異なるRSS(Atom)フィードを3種類おいておく方法が考えられる。
 1) title, link, date のみのフィード
 2) 1) に加えて記事のサマリーが記載されているフィード
 3) 2) に加えて記事の全文が記載されているフィード
RSS利用者側が自分のライフスタイルに合わせて利用するフィードを選べば、帯域消費緩和にもつながるかもしれないし、RSS提供側も悩まなくてすむ。
本当に3つファイルを作成したら今度はストレージの消費が問題になるというなら、実体のファイルは1つにしてWebサーバーのプラグインなどで複数バリエーションのフィード配信を実現すればよい。リーダーから見えるRSSが同じであれば使い勝手は変わらない。ただし、Webサーバーの負荷は逆に高まってしまうことになる。アプローチとしては、VFZ(今のPixelLive)に似ていると思う。VFZは、色深度という観点で画像データを分解して再結合した形式で、利用者のニーズに応じて特定の色深度レベルのデータのみ配信することができる。また、そのようなデータの再構成により、結果的に(おそらくbitmapと比較して)データサイズが小さくなっている。RSSなどのテキスト系メタデータの分野でもそのようなことができないだろうか。サイズが減るという現象は(gzipなどの圧縮の適用を除いて)考えにくいが、自然言語文章から意味の要約を抽出する優秀なアルゴリズムがあれば要約のためにストレージを消費する必要は無くなるかもしれない。

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

RNA nightly build 050405

以下の条件で文字化けが発生するケースについて修正。

条件 : perl 5.8.x 使用時
対象 : UTF-8以外の文字コードを使用しているRSS
修正方法:
 nightly buildでは修正済み。
 ver2.0b1の場合の修正方法は次のとおり。
 (1) lib/RNA.pm(522行目あたり)を次のように編集する。
   [修正前]
   $new_cache_data->{$uri} = {content=>\$str,

   [修正後(1行増えています)]
   my $cachestr = $str;
   $new_cache_data->{$uri} = {content=>\$cachestr,

 (2) cache/site/ ディレクトリの中のファイルをすべて消す。


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