もうそろそろJSONPとはお別れできるのではないかと思い立ったので。

XMLHttpRequestとその問題

AjaxといえばXHRの愛称で親しまれているXMLHttpRequestですが、これには一つ重大な欠点がありました。

これを発行するDHTMLページのドメインが、Request先のドメインと一致する必要があったのです。いわゆる Same Origin Policy というやつです。おかげでサイトをまたがって使えなかったのです。これではマッシュアップできない。どうしよう。

JSONPとその問題

そこで生まれたのが、JSONPという手法です。

これは、scriptノードを追加した時に、単にノードを追加するのではなくsrcアトリビュートで指定されたスクリプトが実行されるというブラウザーの仕様を利用したもので、こんな感じで動きます。

loadJS = function(url){
  var script = document.createElement('script');
  script.charset = 'UTF-8';
  script.src = url;
  document.lastChild.appendChild(script);
};

if (! window.JSON ) loadJS('http://blog.livedoor.jp/dankogai/js/json2.js');

JSONPcallback = function(json){
  alert(JSON.stringify(json));
};

クロスブラザーですしいいことづくめに思えるこの手法ですが、問題が少なくとも二つあります

  1. コールバック関数名を指定しなければならない

    おかげでリクエストURLも長くなりますし、呼び出し方もそれぞれのWebサービスごとに異なりますし、極めつけに、コールバック関数はグローバルスコープで見えるようにしておかなければなりません。無名関数は使えない、少なくともグローバル変数に代入しておかなければならないわけです。

  2. 入力検証のしようがない

    実行は問答無用。レスポンスが期待通り JSONPcallback({...}); だったらいいのですが、これが location.href = 'http://www.example.com/'; だったとしても、ユーザーは指をくわえて見ていることしか出来ません。

この手法は本blogでもさんざん多用してきました。というよりblogであるという本サイトの仕様上、マッシュアップしようとすればそうするしかなかったのです。

Access-Control-Allow-Origin ヘッダー

そこで登場したのが、こちらです。

要はAccess-Control-Allow-Originヘッダーにアクセス元のドメインが入っているか、*でワイルドカード指定されているかすれば、Same Origin Policy は適用されないよ、というわけです。

IE8でも、XDomainRequestはこのヘッダーに対応しています。

ということは、以下のような関数を一個用意すれば、クロスブラウザーかつクロスサイトなAjaxが簡単に実現できるというわけです。

getURL = (function(){
  var xhr;
  if (window.XDomainRequest){
    xhr = new XDomainRequest();
    return function(url, callback){
      xhr.onload = function(){ callback(xhr.responseText, xhr.contentType) };
      xhr.open('GET', url);
      xhr.send();
    };
  }
  else{
    xhr = new XMLHttpRequest();
    return function(url, callback){
      xhr.onreadystatechange = function(){
        if (xhr.readyState === 4) 
          callback(xhr.responseText, xhr.getResponseHeader("Content-Type"));
      };
      xhr.open('GET', url, true);  
      xhr.send();
    };
  }
})();

実際にやってみましょう。

手元で試した結果、Opera 10.61 を除いてうまく行きました。IE8でもFirefox 3.6でもChrome 5でもSafari 5でもiPhoneでもiPadでも。

本来のXHRの実力

これを使うと、こういうことも簡単に出来ます。

fetch = function(){
  var url = 'http://api.dan.co.jp/get/' + document.getElementById('url').value;
  getURL(url, function(content, type){
    var node = document.getElementById('got');
    if (node.textContent !== undefined) node.textContent = content;
    else                                node.innerText   = content;
  });
};

Access-Control-Allow-Originで許可さえされていれば、何を取って来てもいいわけです。現時点ではそれほどポピュラーなヘッダーではないので、ここではAccess-Control-Allow-Origin: *を加えるだけの以下のような簡単なproxyを通していますが、これさえあれば、サーバーの助けをなしに別のWebページを解析したりといった作業が簡単にできるということです。

あと、この方式だと当然JSONも生textのまま受け取るわけですが、nativeなJSONIE8を除いたモダンブラウザーは標準装備していますIE8のCompatibility Modeを除けば標準で有効になっていますし、その場合にも上記のように「なければjson2.jsをロードする」のは簡単です。evaる、いやJSONPのようにevaらせる必要はこれでなくなるわけです。

というわけでWeb APIプロバイダー各位におかれては、Access-Control-Allow-Origin: *をHTTP Responseに加えていただけるとありがたいというお話でした。

Dan the Cross-Site JavaScripter