別ドメインのjsonデータをXMLHttpRequestでやりとりする
いつのまにやら家から5分の距離に新しくコンビニができたことに気づいたtanakaです。
今日は、別ドメインのjsonデータをXMLHttpRequestで普通に取得するにはどうすればいいか調査したので方法をまとめます。実装としてはもう4年くらい前から使えるようになっていますが、私はまだ使ったことがなかったので。
XMLHttpRequest とクロスドメイン
XMLHttpRequest が Internet Explorerに実装され、それ以外のブラウザに実装された当初は JavaScriptが実行されたページのドメイン以外にリクエストを送りデータを受け取ることはできませんでした。別ドメインに非同期でリクエストするためにいくつかの方法が考えられましたが詳しい説明は省略します。(一旦サーバを経由する・Flashを経由する・JSONPを使うなど)
2009年になりXMLHttpRequest Level 2が各ブラウザでサポートされるようになりました。(Firefox, Chrome, Safari, IE8)XMLHttpRequest Level 2ではクロスドメインへのアクセスに対応しています。ただし無条件にアクセスできるわけではなく、リクエストされるサーバ側で設定が必要です。
参考
- XMLHttpRequest - Wikipedia / クロスドメイン
- XMLHttpRequest Level 2 と wedata バックアップ - os0x.blog
- Ajaxブラウザセキュリティ - 主戦場をDOMに移したXSS 5.8/5.9にJSONP や XHR Level2の解説あり
別ドメインからjsonデータを取得するサンプル(mac)
やり方を理解するには実際にコードを書いてみるしかないのでいくつかのサイトを参考にしながらコードを書きました。今回のコードが動作するのは、Firefox, Safari, Chromeの最新版とIE8以降のブラウザです。参考: XMLHttpRequest level2に対応しているブラウザまとめ ちなみにOperaは11.60でXMLHttpRequest Level 2をサポートしたらしいですが、もうWebKitに移行するとのことなので省略します。
ホスト設定 (Apache のバーチャルホスト)
<VirtualHost *:80>
DocumentRoot "/Users/tanaka/projects/xhrl2/app"
ServerName app.devlocal.com
</VirtualHost>
<VirtualHost *:80>
DocumentRoot "/Users/tanaka/projects/xhrl2/api"
ServerName api.devlocal.com
</VirtualHost>
<Directory "/Users/tanaka/projects/xhrl2">
Options Indexes
DirectoryIndex index.php index.html
AllowOverride All
Order allow,deny
Allow from all
</Directory>
こんな感じで2つのサイトを作ります。今回は app.devlocal.com から api.devlocal.com にXmlHttpRequestでアクセスします。あ、hostsにも以下の行を追加します
/etc/hosts
127.0.0.1 app.devlocal.com
127.0.0.1 api.devlocal.com
つぎに2つのサイトのコードを見ていきます。/Users/tanaka/projects/xhrl2 ディレクトリは次のようになってます。
├── api
│ └── tweets.php
└── app
├── index.html
└── index_ie89.html
まずはリクエストする側のコードを見ていきます。
app.devlocal.com/index_ie89.html
<!DOCTYPE html>
<html>
<head>
<title>XmlHttpRequest level 2 Test</title>
</head>
<body>
<dl id="tweets"></dl>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script type="text/javascript">
$(function() {
var $list = $('#tweets');
var xhr=window.XDomainRequest?new XDomainRequest:new XMLHttpRequest;
try{
xhr.onload=function(){
var data = $.parseJSON(xhr.responseText);
$.each(data, function(index, tweet) {
$list.append($('<dt>').text(tweet.date));
$list.append($('<dd>').text(tweet.message));
});
};
xhr.open("GET","http://api.devlocal.com/tweets.php");
xhr.send();
}catch(e){
alert(e.message);
}
});
</script>
</body>
</html>
IE8,9対応のためjQuery.ajax()を使わず、IE→XDomainRequest その他→XMLHttpRequest を使うコードになってます。( XMLHttpRequest Level 2 と wedata バックアップ - os0x.blogのサンプルコードを利用し、出力をページに埋め込む部分を修正しました。 )
api.devlocal.com/tweets.php
次に呼び出される側のコードを見ます。Twitterのつぶやきのようなデータを返すサンプルです。
<?php
$tweets = array(
array(
'id' => 1,
'message' => 'First Tweet',
'date' => 'yesterday',
),
array(
'id' => 2,
'message' => 'Second Tweet',
'date' => 'today',
),
array(
'id' => 3,
'message' => 'Latest Tweet',
'date' => 'just now',
),
);
header("Access-Control-Allow-Origin: http://app.devlocal.com");
header('Content-type: application/json');
echo json_encode($tweets);
注目すべきは19行目で"Access-Control-Allow-Origin: http://app.devlocal.com" というヘッダを出力している点です。これによりデータをリクエストしたサイト側で受け取って利用できるようになります。このヘッダを出力しないとエラーが発生します。各ブラウザの開発ツールで見てみると次のようになりました。上からChrome(Mac OS X 10.8), Firefox(Firebug)(Mac OS X 10.8), IE10(Windows 7)です。
Firebugのエラーは何が原因なのかちょっとわかりにくいですね。(あと各ウインドウの境界がわかりにくくてすみません。)
さて、無事データが取得でき処理が完了すると表示結果は次のようになります。
まとめ
XMLHttpRequestでクロスドメインについてまとめると
- XMLHttpRequest Level 2に対応したブラウザならできる
- 原則XMLHttpRequestでクロスドメインでのアクセスは禁止されてるのでAccess-Control-Allow-Origin: ヘッダで、どのドメインで読み込みを許可するか制御する
- ただし、このヘッダはXMLHttpRequest以外でのアクセス制御はできない(PHPのfile_get_contentsはヘッダを気にしないでしょう)
- IE8,9では XMLHttpRequest の代わりに XDomainRequest を使う