AJAX
ブラウザ戦争が過去のものとなるまで10年かかった。IE3とネスケは互いの独自拡張を非難し合っていたものだが、ネスケはCookieやJavaScript、SSLといった現代的なウェブに必要不可欠なテクノロジーを独自拡張したし、IEはActiveXによるMSHTMLの動的な呼び出し、XMLHTTPRequestオブジェクトや、DynamicHTMLと呼ばれた動的コンテンツの書き換えを独自拡張した。いずれも、いまでは業界標準であり、XMLHTTPRequestオブジェクトを除けば、W3C仕様にも追認されている。
あの戦争の当時、ブラウザは混迷しており、JavaScriptを嫌悪するユーザも沢山いたし、むしろ、スキルの高いユーザほどJavaScriptを避けていたように思う。ましては、MS謹製のActiveXなどご法度ものだった。ウェブを提供するものとして、だから、JavaScriptやActiveXに依存することは好ましいものとは考えられなかった。現在でもW3Cはスクリプトの使用を推奨こそしていない。
だが時代は変わったようだ。すでにブラウザ戦争は過去のものとなりつつある。残された遺産は避けるべき負の遺産ではなく、積極的に活用すべき宝の山に変わったらしいのだ。
AJAXの正当性は、業界標準で、ネスケやIEといった特定のベンダーにディペンドせず、且つ、それがユーザにとって何ら不都合がない、という点につきるのであろう。
しかし、いままでタブーだったものを自由な発想のもと天秤に計り、実際に「使える技術である」として再評価し解禁したGoogleラボの聡明さには感服する。
むしろ、AJAXよりも、その聡明さを見習いたいように思う。一度下した評価でも時代が変われば、変わるのだ。そのことに気づかされる。
動機: ActiveX修正プログラムへの対応として
さて、最近、MSは1992年に出願されたらしいHTMLのインタラクティブなプラグイン技術に関する特許問題で負けが込んできたのか(あるいは、実際に負けたのか、)知らないが、ユーザ・インタラクティブなActiveXに対してワンクリックするまでイベントに反応しない、というフィルタをかけるActiveX修正プログラムを配布することになった。
実際、これは今年の2月から配布されているが、エンタープライズ用途で例えばJava AppletやFlashなどを業務用画面に使っていると、ページをロードするたびにワンクリックが発生して使いづらいこと、このうえない。そこでMSは6月までの猶予期間を与えることにしたらしいのだ。
この特許の回避方法も、かなりセコイものである。
静的なHTMLに埋め込まれたタグから云々、という条件があるらしく、外部のJavaScriptで動的に生成されたタグである場合は何ら特許に抵触しないらしい。
つまり、
しかし、(当たり前ではあるが)DynamicHTMLを駆使したインタラクティブなページであっても、たとえばAJAXのような場合には特許に抵触しないようである。
アプレットからAJAXへの乗り換え
そこで、AJAXを試してみることにした。
うまい具合に、ちょうど手持ちのアプリケーションでJava Appletを使っているページが1つあったので、AJAXに変更してみることにした。
このアプレットはページに表示されているデータをもとに定期的にサーバに問い合わせて状態の変化があるか監視するものであり、状態に変化があればページにメッセージを表示するか、もしくはリロードする、というだけの簡単なものである。
本格的なAJAX対応であればページのリロードなしに更新があった項目だけを動的に表示変更させるようなことになるのであろうが、最初のAJAXの例としては適当ではないかと思われる。
また、このウェブページは、社内のみで使われるシステムであり、対象となるブラウザもIE5、5.5、6、もしくはFirefoxをターゲットにするだけでよいので話は簡単である。
ページをロードしたときにbodyタグのonloadイベントハンドラでタイマーをセットし、定期的にファンクションを呼び出す。このファンクションはMSXMLのXMLHTTPRequestオブジェクトを構築して、サーバに現在表示されているページのパラメータを渡して、そのレスポンスをテキストとして受け取る。テキストの中身を解析して、必要があればページをリロードする。
それだけのものである。
AJAXの入門サイトを検索した結果、以下のようなコードを書いておけば、IE5/5.5/6/Firefoxに対応できるようである。(後ろ2つはFirefox1.5/IE5.0いずれも呼び出された形跡がないようだが。)
function createXMLHTTPRequest() { if (window.ActiveXObject){ try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { return new ActiveXObject("Microsoft.XMLHTTP"); } } else if (window.XMLHttpRequest) { return new XMLHttpRequest(); } throw new Error(0, "ブラウザがXMLHTTPRequestをサポートしていません。"); }
このファンクションはブラウザが対応していなければ例外を返すし、対応しているがActiveXの生成を禁止している場合も例外を返すので、例外なく成功した場合にかぎり、XMLHTTPRequestオブジェクトが取得されていることになる。
XMLHTTPRequestオブジェクトは、かなりプレーンなHTTPプロトコルに則っており、セッションやクッキーの管理もしないし、送出されるデータや受信されるデータのフォーマットも特に規定していない。
var requestObj = createXMLHTTPRequest(); requestObj.open("POST", "サーブレットへのパス", false); requestObj.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); requestObj.send("送信するデータ");
こんな感じでサーバにデータを送る。
比較的大きなデータをサーバに送りたいのでPOSTメソッドを用いる。
そこで、Conent-Typeを「application/x-www-form-urlencoded」にしておく。
送出するデータは、フォームの送信と同じく、「名前1=値1&名前2=値2...」のような形式とし、データそのものはURLエンコーディングされている必要がある。(当然、UTF-8でもある。)
(ただし、URLエンコーディングはJavaScriptにあるEscape関数を使うことはできない。これはIEとネスケで挙動が違うし、「+」のような記号をエンコーディングした際に問題となるであろう。)
要求先のURLは実際のところMSHTMLのバージョンによって制限がことなる。MSDNライブラリによると、旧MSXMLでは相対パスは指定できないことになっている。
Openの最後の引数のfalseは、サーバからのレスポンスを待機する、という意味であり、省略時はtrueで、非同期的にレスポンスが戻ってきた時点でコールバックが呼び出される。
今回はタイマーで起動され、タイマーそのものが非同期処理である以上、さらに非同期処理をする意味がないのでfalseとしている。
セッションIDなどが必要な場合には、JavaScriptで現在のページのCookieを取得したのちに、 setRequestHeaderで明示的にCookieヘッダを送信する必要があるだろう。
このあたりは実際のところ、Java Appletからサーバへリクエストを投げる場合も同じであり、むしろ、JavaScript/AJAXのほうが簡単かもしれない。
サーブレットはブラウザからのPOSTメソッドの要求としてデータを受信し、HttpServletRequestオブジェクトのパラメータに、これらの値をデコードして展開してくれるので、サーブレットは適当に処理する。
サーブレットは任意の形式でレスポンスを返すことができ、XMLHTTPRequestオブジェクトは、それらを生データとして受信することが可能である。
このオブジェクトは、XMLという名前がついているにも関わらず、ただのプレーンテキストを受け取ることができる。(おそらく、バイナリも可能であろう。)
当初、AJAXという名前から、サーバからXMLを返してクライアントで解析するのか、と考えていたが、AJAXにはJSON(JavaScript Object Notation)という簡易なBNFで表現されるデータを返すことが一般的らしいとも聞いていた。
その理由はXMLよりも軽量で構文解析も手間じゃないからかな、程度に思っていたが、そうではなく、むしろ、ここがキモだったようだ。
このJSONは、本当に単純な構文であり、JavaScriptで簡単にパーサが書けそうに見える。
http://www.json.org/
たとえば、こんな感じである。
{ NAME1 : “VALUE1”, NAME2 : false, NAME3 : 1234 }
ところが、この構文、JavaScriptのオブジェクトの初期化構文と同じなのである。
JavaScriptでは、
var obj = { a :1, b:2, c:3 };
のように記述することで、objというインスタンスにa、b、cという3つのプロパティを設定して初期化することができる。
そして、JavaScriptには、たいていのスクリプト処理系がそうであるように、動的にJavaScriptを評価するためのeval()という関数が用意されている。
サーバ側で前述のようなテキストを返した場合、
eval(“var response=” + responseText);
とすると、responseというインスタンスにプロパティが自動的に設定されるわけである。
先の例では
サーバが「{ NAME1 : “VALUE1”, NAME2 : false, NAME3 : 1234 }」というテキストを返すと、
response.NAME1 = “VALUE1”; response.NAME2 = false; response.NAME3 = 1234;
というプロパティが設定されていることになる。
つまり、JSONをレスポンスで返しeval()すると、パーサの必要もなく、XMLのドキュメントツリーをウォークする必要もなくなるのである。
一気にクライアント側の処理が簡略化される。
率直に、これは妙案じゃないか、と感心した。
サーバからレスポンスを受け取るのも簡単である。
if (requestObj.status < 200 || requestObj.status >= 300) { throw new Error(requestObj.status, requestObj.statusText); } var responseText = requestObj.responseText;
HTTPのステータスコードが200番台にない場合は、とりあえずエラーとみなしている。
この処理がないと、サーバが500番台で死んでいたりしても、エラーメッセージのレスポンスをテキストとして取得してeval()してしまうので、これは除外する必要がある。
レスポンスコードに対するステータスの文字列表記も取得できるので、エラー時には、これを例外にして送出している。
XMLHTTPRequestオブジェクトが300番台を自動的にリダイレクト処理してくれるのか、あるいは400番台の認証要求があった場合に処理するためのハンドラがあるのかは不明だが、たぶん、ない。多分、自前で処理しなければならないだろうが、要求先のサーブレットも自分で管理しているならば、このあたりは省略できるだろう。
注意することは、レスポンスのエンコーディングの方法である。
IE6の環境ではレスポンスのContent-Typeに従って適切にエンコーディングしてくれるようだが、IE5の場合はUTF-8決めうちのような感触だった。
今回は日本語を返す必要性がまったく無いので深く検証しなかったが、日本語のレスポンスを返す場合には注意が必要かもしれない。
結論
ポイントはデータの送出でURLエンコーディングすること、レスポンスはJSON形式にしてevalすること、の2点ぐらいであろうか。
実際のところ、AJAXの敷居は、ずいぶんと低い、という印象を受けた。
もちろん、不特定多数の一般コンシューマ向けのサイトでは、環境もいろいろあり、これほど簡単にはゆかないだろうが、それでも躊躇うほど難しいということもないだろう。
巷に溢れるAJAX系の本では、さらに複雑なテクニックを紹介しているものと思われるが、基本的には、DynamicHTMLを知っている人ならば、あらたに必要な知識は微々たるもののようである。むしろ、DynamicHTMLを、いかにマルチプラットフォームで効率よく扱うか、という技術になるのだろう。
また、Java Appletのようなリッチクライアントを躊躇無く導入できるエンタープライズ環境であっても、この軽量さは魅力的なのではないか、と感じた。
アプレットのロードにはブラウザセッションごとにJavaVMのロードが必要であり、それも数十メガの単位でメモリを消費してゆく。これが無くなるというだけで、クライアントは、かなり爽快に動くようになるのではないだろうか?貧弱なマシンであるほど、立ち上がりが遅いからブラウザを立ち上げっぱなしにして、立ち上げたままなのでメモリが有効利用されずクライアントがいつも遅い、などという悪循環もなくなるだろう。
もしかすると、2003年ごろに、いろいろ検討されていたリッチクライアントの本命は、実は2000年前には出揃っていたAJAX、それも、ただのブラウザであったのかもしれない。