2010年08月17日 06:45 [Edit]
Ajax - Goodbye, JSONP. Hello, Access-Control-Allow-Origin
もうそろそろ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));
};
クロスブラザーですしいいことづくめに思えるこの手法ですが、問題が少なくとも二つあります
- コールバック関数名を指定しなければならない
おかげでリクエストURLも長くなりますし、呼び出し方もそれぞれのWebサービスごとに異なりますし、極めつけに、コールバック関数はグローバルスコープで見えるようにしておかなければなりません。無名関数は使えない、少なくともグローバル変数に代入しておかなければならないわけです。
- 入力検証のしようがない
実行は問答無用。レスポンスが期待通り
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なJSONはIE8を除いたモダンブラウザーは標準装備していますIE8のCompatibility Modeを除けば標準で有効になっていますし、その場合にも上記のように「なければjson2.jsをロードする」のは簡単です。evaる、いやJSONPのようにevaらせる必要はこれでなくなるわけです。
というわけでWeb APIプロバイダー各位におかれては、Access-Control-Allow-Origin: *をHTTP Responseに加えていただけるとありがたいというお話でした。
Dan the Cross-Site JavaScripter