※本記事は2016年7月15日に掲載した記事の翻訳を一部更新したものです。執筆時点の情報をベースにしており、最新ではない可能性があります。
Webベースのアプリケーションを開発し、権限のないドメインからデータをロードしようとすると、おそらくブラウザーの画面に以下のようなメッセージが表示されているはずです。
XMLHttpRequest cannot load http://external-domain/service. No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://my-domain’ is therefore not allowed access.
(XML HttpRequestはhttp://external-domain/serviceをロードできません。「Access-Control-Allow-Origin」ヘッダーがリクエストされたリソース上に存在します。「http://my-domain」へのアクセスは許可できません)
この記事ではこのエラーの原因を理解するとともに、jQueryやJSONPを使って問題を回避し、クロスドメインAjaxを呼び出す方法を紹介します。
同一生成元ポリシー
Webページは通常、XMLHttpRequestオブジェクトを使ってリモートサーバーからデータを送受信できますが、その機能は同一生成元ポリシー(Same Origin Policy)により制限を受けています。これはブラウザーのセキュリティモデルにおいて重要なことで、ページAのスクリプトがページBのデータにアクセスできる条件として、これら2つのページが同一の生成元を持っていなければなりません。ページのオリジンはプロトコル、ホスト、ポート番号によって定義されます。たとえば、SitePointページのオリジンはプロトコルが「https」、ホストが「www.sitepoint.com」、ポート番号が「80」です。
同一生成元ポリシーは安全装置の役割を果たします。あなたのドメインからデータを読み出したり、データをサーバーに送信したりするのを防ぎます。もしこの同一生成元ポリシーがなかったら、悪意あるWebサイトがほかのWebサイト(たとえばGmailやTwitterなど)へのセッション情報を簡単に入手し、あなたになりすましてアクションを実行できてしまいます。一方で同一生成元ポリシーはまた、残念ながら、上で引用したようなエラーをしばしば発生させ、正当なタスクを達成しようとしている開発者の悩みの種となっています。
失敗例
失敗例を見てみます。たとえば、jQueryのgetJSONメソッドを使ってロードしたいJSONファイルが違うドメインにある場合を考えます。
$.getJSON(
"http://run.plnkr.co/plunks/v8xyYN64V4nqCshgjKms/data-1.json",
function(json) { console.log(json); }
);
ブラウザーのコンソールを開いてみると、先ほどと類似したメッセージが表示されます。このような場合、どうすればよいのでしょうか。
取れる回避策
幸運にも、すべてが同一生成元ポリシーの影響を受けるわけではありません。たとえば、異なるドメインからページへ画像またはスクリプトを読み込むことはできます。たとえば、CDNからjQueryを取り込む場合は、まさにこのプロセスを踏んでいます。
つまり、<script>タグを作成し、src属性にJSONファイルを指定してページに挿入します。
var script = $("<script />", {
src: "http://run.plnkr.co/plunks/v8xyYN64V4nqCshgjKms/data-1.json",
type: "application/json"
}
);
$("head").append(script);
この方法は動作はしますが、ほとんど役に立ちません。というのは、読み込んだファイルに含まれるデータを取得することができないからです。
JSONPを使ってみる
JSONP(パディングを持つJSONを意味する)は上で説明した方法を利用して、返却されたデータにアクセスする方法を提供します。JSONPは、JSONデータ(Pdding)を引数とする関数の呼び出し文をサーバーに返却させ、ブラウザーが解釈します。関数はJSONPのレスポンスを評価するページで定義されていなければなりません。
先ほどの例に当てはめるとどうなるか説明します。JSONファイルを更新し、もとのJSONデータをjsonCallback関数でラップします。
function jsonCallback(json){
console.log(json);
}
$.ajax({
url: "http://run.plnkr.co/plunks/v8xyYN64V4nqCshgjKms/data-2.json",
dataType: "jsonp"
});
上のコードは予想される結果をコンソールに記録します。(制限はあるものの)クロスドメインAjaxを利用できるようになりました。
サードパーティAPI
サードパーティAPIには、リクエストが返ってきた場合に実行されるコールバック関数の名前を指定できるものもあります。そのよい例がGitHub APIです。
以下の例ではJohn Resig(jQueryの開発者)のユーザー情報を入手し、logResultsコールバック関数を使用してコンソールへのレスポンスをログに記録します。
function logResults(json){
console.log(json);
}
$.ajax({
url: "https://api.github.com/users/jeresig",
dataType: "jsonp",
jsonpCallback: "logResults"
});
以下のように書くこともできます。
$.getJSON("https://api.github.com/users/jeresig?callback=?",function(json){
console.log(json);
});
URLの末端部の?はjQueryにJSONではなくJSONPリクエストを使うように命令しています。そのあと、jQueryはリクエストが返された場合に呼び出すコールバック関数を自動的に登録します。
jQueryのgetJSONメソッドの詳細については、Florian Rapplの記事『Ajax/jQuery.getJSON Simple Example』を参照してください。
注意点
もうお分かりかもしれませんが、この手法にはいくつかの欠点があります。
たとえば、JSONPはクロスドメインのGETリクエストしか実行できず、サーバーがサポートしている必要があります。セキュリティ上の懸念がないわけではないので、ほかに良い解決法がないか簡単に説明します。
プロキシの利用
サーバーサイドコードは同一生成元ポリシーの制約を受けず、問題なくCross-Originリクエストを実行できます。そのため、いくつかの種類のプロキシを作り、そのプロキシを使って必要なデータを取得できるはずです。最初の例を参考しながら以下を見てください。
/* proxy.php */
$url = "http://run.plnkr.co/plunks/v8xyYN64V4nqCshgjKms/data-1.json";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec ($ch);
curl_close ($ch);
echo $result;
クライアントサイドでは以下のようになります。
$.getJSON("http://my-domain.com/proxy.php", function(json) {
console.log(json);
})
しかし、この手法にもデメリットがあります。たとえば、他社のWebサイトが認証にクッキーを利用している場合、うまく動作しません。
CORS
Cross-Origin Resource Sharing(CORS)はブラウザーからのクロスドメイン通信を可能にするW3Cの仕様です。レスポンスに新規のAccess-Control-Allow-OriginHTTPヘッダーを埋め込めば実行できます。
最初の例を参考にして、以下のコードを.htaccessファイル(Apacheと仮定)に追加し、異なったオリジンページからのリクエストを許可します。
Header add Access-Control-Allow-Origin "http://my-domain.com"
(サーバーをApache以外で実行している場合は、次のURLを参照してください:http://enable-cors.org/server.html)
CORSについてもっと知りたい人は最近SitePointに掲載された記事『An In-depth Look at CORS』を参照してください。
最後に
JSONPを使えば同一生成元ポリシーを回避し、ある程度までクロスドメインAjaxを呼び出せます。確実にそうできるわけではありませんし、問題も確かにありますが、異なった生成元からデータを取ってくる場合には非常に貴重な手法となります。
また、JSONPを使えば、異なったサービスからさまざまなコンテンツを入手できます。優れたWebサイト(たとえばFlickr)のほとんどはJSONPサービスを提供しており、ユーザーはあらかじめ定義されたAPIを介してコンテンツを利用できます。また、Programmable Web API directoryにJSONPサービス総覧があります。
(原文:jQuery’s JSONP Explained with Examples)
[翻訳:中村文也/編集:Livit]