更新日時
2005年03月05日
文書ステータス
書きかけ

XMLHttpRequestについて

この文章は書きかけです。今のところ send メソッドによるXMLデータの送信等については触れていません。

このページではMSXML付属のIXMLHTTPRequest、およびGeckoベースのWebブラウザ、KHTMLベースのWebブラウザ、Operaに実装されたXMLHttpRequestについて、ブラウザごとの差異に注目しながら挙動の詳細を解説しています。リファレンス的な内容は一切ありませんのでご注意ください(→参考リンク)。

このページで検証に用いたブラウザとバージョンは以下の通りです。

KHTMLベースのWebブラウザとしてKonquerorを検証対象にしていますが、Safariとは一致しない部分があると思われます。ご容赦ください。

XMLHttpRequestの役割

XMLを読み込むという役割にのみ着目すると、XMLHttpRequestが行う処理は

  1. HTTPリクエストを発行してリソースを取得する
  2. 取得したりソースをXMLとして解析し、DOMツリーを構築する

という2段階に分けて考えることが出来ます。敢えて極端な言い方をすればXMLに関連するのは responseXML プロパティを参照する時だけであり、それ以外の部分では”単なる”HTTPクライアント・コンポーネントと考えて差し支えありません。もちろん制限はあるわけですが、HTTPの基本的な仕組みを理解していればリソースを取得するまでの段階で戸惑うことはないでしょう。いずれのメソッドも名称から予想されるとおりの動作をします(バグさえなければ)。

/* このコードはOperaでは動作しない(→Operaのバグ) */
var url = '/path/to/xml';
var req = createXMLHttpRequest();
    req.onreadystatechange = readyStateChangeHandler;
    req.open("GET", url, true);
    
    /* 例えばUserAgentを書き換える */
    req.setRequestHeader('User-Agent', 'XMLHttpRequest');
    req.send(null);

/* 読み込み状態が変化したときに呼ばれる関数 */
function readyStateChangeHandler() {
   if (req.readyState == 4) {
      if (req.status == 200) {
         alert("正常にリソースが取得できました\n"+
            "メディアタイプ : "+ req.getResponseHeader('Content-Type'));
         alert("XMLのルート要素のタグ名 : "+ 
            req.responseXML.documentElement.tagName);
      } else {
         alert("リソースの取得に失敗しました\n"+ 
            req.status +" "+ req.statusText);
      }
   }
}

/* 新規XMLHttpRequestを生成する関数 */
function createXMLHttpRequest() {
   return this.XMLHttpRequest ? 
      new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
}

openメソッドの第3引数は非同期性のオン/オフです。trueにするとsendメソッドは即座に処理を返し、その後はonreadystatechangeイベントの中で読み込み状況の確認とXML処理を継続することになります。falseにするとsendはリソースを所得し終えるまで処理を返しません。分かりやすいのは後者ですが、"A"jax的には前者が好まれるところでしょうか。

/* 同期読み込みの場合 */
req.open("GET", url, false);
req.send(null);

if (req.status == 200) {
   alert("正常にリソースが取得できました");
   //...
} else {
   alert("リソースの取得に失敗しました);
   //...
}

無事リソースが取得できたら、responseXMLプロパティを参照することでDOMDocumentが取得できます。先述したとおり、この段階に至って初めて新規DOMDocumentの作成とXMLの解析が行われ、パースエラーなどXMLに由来するエラーも発生します。パースエラー時の挙動はブラウザによって異なります。IEとGeckoについては「JavaScriptでXMLを扱う方法」で解説しているものと全く同様の挙動です。すなわち、MSXMLではDOMDocumentのparseErrorプロパティにエラー情報が格納され、Geckoでは特にエラーらしい挙動が起こらない代わりにエラー情報を含む特殊なXMLが読み込まれます。KHTML/Operaでは現在の所パースエラーを検知する方法は存在しないらしく、documentElement プロパティがnullかどうかで判断するよりないようです。

function isParseError (doc) {
   return (doc.parseError!=null && doc.parseError.errorCode!=0) ||
      (doc.documentElement.tagName=='parsererror' 
         && doc.documentElement.namespaceURI==
           'http://www.mozilla.org/newlayout/xml/parsererror.xml') ||
      (doc.documentElement == null);
}

HTTPクライアントとしての挙動

セキュリティ上の配慮から、XMLHttpRequestがリクエストを送信できるのは同一ドメイン内に制限されています。この制限を超えたリクエストを行おうとすると、IE/Geckoではopenメソッド呼び出し時に、Operaではsendメソッド呼び出し時にエラーとなります(throwされるものはブラウザ毎にバラバラですが)。KHTMLでは特にエラーらしい挙動は無いようです。

厳密に言えばIE+MSXMLは『MSXML Client Security』に従い、ローカルファイルからはあらゆるリソースに対してリクエストを発行できます。しかしWindows XP SP2環境ではデフォルトで実行がブロックされるはずです。WSHやHTAから実行する場合には制限はありません。

var req = createXMLHttpRequest();
try {
   req.open("GET", 'http://example.com/', true);
   req.send(null);
}
catch(e) {
   /*
      MSXMLではErrorのインスタンスが、
      Geckoでは単なる文字列が、
      OperaではDOMExceptionのインスタンスがthrowされる
   */
   if(typeof e == 'object') {
      alert(e.message);
   } else {
      alert(e);
   }
}

XMLHttpRequestが送信するHTTPリクエストは、setRequestHeaderメソッドを使って書き換えたり、sendメソッドの引数として値を渡したりしない限り、ブラウザが送信するものと全く同一と言えます。ごく僅かの例外として、MSXMLではIEと比較してAcceptヘッダの内容が * に変わっているとか、MSXMLとOperaではスクリプトを呼び出したページのURLがRefererとして送信されるとか、その程度の違いはありますが、基本的にサーバサイドではブラウザからのアクセスなのかXMLHttpRequestを通したアクセスなのか区別する術はありません。どうしても必要ならsetRequestHeaderなどを使って識別用の値を送信する必要があるでしょう(それとて厳密なものとは言えませんが)。

OperaとKHTMLのバグ

もともとXMLHttpRequestには”標準”はないので、バグというのは少し違うのかも知れませんが…

Operaには(8.0 beta2現在)open/send/abortを除くsetRequestHeader等のメソッドが一切実装されていません。そのためHTTPクライアントとしての機能は非常に制限されたものとなっています。正式版では実装されることを祈りましょう。

またKHTMLのsetRequestHeaderメソッドは、デフォルトで送信されるヘッダを置き換えることが出来ず、付け足す形になってしまうというバグ(?)が存在するようです。

var req = new XMLHttpRequest();
req.setRequestHeader('Accept', 'application/xml');
/*
元々のAcceptヘッダの値:text/html, image/jpeg, image/png, text/*, image/*, */*
本来送信されるべき値:application/xml
実際に送信される値:text/html, image/jpeg, image/png, text/*, image/*, */*, application/xml
*/

responseXMLの制限

リソースがresponseXMLによってDOMDocumentに変換可能かどうかはメディアタイプで判断されます。私が調べた限り、変換可能なメディアタイプは以下のようになります。

IEtext/xml, application/xml
Geckotext/xml, application/xml, application/xhtml+xml
KHTMLtext/xml, application/xml, application/xhtml+xml
Opera無制限

image/gif 等でも変換できるOpera(8.0 beta2現在)を除き、text/html や application/rdf+xml 等にはいずれのブラウザも対応していません。サーバサイドのプログラムで動的にヘッダを送信する場合などには、text/xml や application/xml を用いるのが賢明でしょう。

なお、変換不能なメディアタイプが指定されていた場合、GeckoとKHTMLでは responseXML 自体が null となり、IEでは documentElement が null となります。パースエラーと合わせて次のようにチェックするのが現実的でしょう。

...
   if(req.responseXML == null || isParseError(req.responseXML)) {
      alert('適切なXMLではありません');
   }

responseTextの挙動

responseTextプロパティを参照することで、リソースをテキストとして取得することが出来ます。ただしこのとき用いられる文字エンコーディングはブラウザによって異なります。

IEの場合

マニュアルを参照すると以下のようにあります。

It assumes the default encoding is UTF-8, but can decode any type of UCS-2 (big or little endian) or UCS-4 encoding as long as the server sends the appropriate Unicode byte-order mark. It does not process the <? XML coding declaration.

デフォルトではUTF-8と見なされるが、BOM付きのTF-16LE/BEおよびUTF-32も使用可能、XML宣言のエンコーディング指定は無視される、といったところですが、実際には Content-type ヘッダで適切な charset が指定されていればその他のエンコーディングも使用できます。

Geckoの場合

ルールは少し複雑です。まずContent-type ヘッダで適切な charset が指定されていればそのエンコーディングが使用されます。指定がない場合、メディアタイプがresponseXMLでDOMDocumentに変換可能なものであれば自動判定されます(XML宣言の指定が使用されるわけではない)。そうでない場合はUTF-8が使用されます。

KHTMLの場合

UTF-16LE/BEが使用可能なのは確認できましたが、それ以外はcharsetの指定やメディアタイプに関わらず使用できないようです。これはresponseXMLでも同様です。

Operaの場合

(時々挙動がおかしいこともありますが)いずれの場合においても自動判定されます。

実働サンプル

ボタンを押すとこのサイトのRSSを読み込みます。ただしUTF-8なので、KHTML(Konqueror)では文字化けします。
ソース表示