JSONPで悩むある程度の人々へ
JSONPって、クロスドメインでデータをとってこれて、Web APIとかはこれで実装されているんでしょ。
なんとなくわかる気がするんだけど、自分で作ってみるとなんかうまく動かない。
あるいはその手前で、どういう風に実装していいかわからない。
とくに自分がAPIを提供する側になると、よけいよくわからない。
Wikipediaの解説なんか、わけがわからないよ。
こんな感じの方はいませんか。
というか、ちょっと前の自分はこんな感じでした。
いろんなサイトを調べまくって、ある程度わかってきた気がしますので、後のためにここに残しておきます。
ああ、あのころの自分に教えてあげたかった。
まずJSONって何さ?
JSONPにたどり着いた人はJSONのことは知っていると思いますから、簡単に。
こんな感じの「テキスト」のことですよね。
{ "key1": "value1", "key2": "value2" }
cgi等で出力するときのちょっとした注意としては、
content-type: application/json; charset=utf-8 { "key1": "value1", "key2": "value2" }
とすることぐらいでしょうか。
Content-Typeをつけないと、ajaxで受け取れない場合があります。
ブラウザによっては、charsetを付けないとうまく動作しなことも(OperaとかOperaとかOperaとか・・・)
このあたりの事情はこのサイトが詳しいです。
「JSONのContent-Typeは「 text/javascript 」でなく「application/json」で」
で、これをJavascriptで受け取るための関数がjQueryとかで用意されているわけですね。
たとえば、$.getJSONなんかいかがでしょう。使い方は次の通り。
// 同じドメインの test.cgi から JSONデータを取得して表示 $.getJSON("test.cgi", function(data){ alert("key1:" + data.key1); alert("key2:" + data.key1); });
これで alert が 2つ現れるはずです。
getJSONの引数に入る無名関数 function() は、コールバック関数と言って、
ajaxのレスポンスを受け取ると、呼び出される関数です。
この関数には、基本的になんでも入れられるのですが、一つ注意があるとすればクラスオブジェクトのメンバ関数はいれることができません。
(メンバ関数を無理やり入れたい時は、thisポインタごとグローバル変数にしてしまって、無理やり入れる方法があります。)
GETのパラメータを渡したい時は、2番目の引数を入れて次のようにすればよいでしょう。
// 同じドメインの test.cgi に test.cgi?param1=value1¶m2=value2 としてリクエストをかけ、 // JSONデータを取得して表示 $.getJSON("test.cgi", { param1: "value1", param2: "value2" }, function(data){ alert("key1:" + data.key1); alert("key2:" + data.key1); });
じゃあ、JSONPって?
おまたせしました、ようやくJSONPです。
JSONPはJSON with Paddingの略称で、次の形式で書くことが出来ます。
callback({ "key1": "value1", "key2": "value2" })
callbackで挟んだ以外は、ぱっと見て先ほどと変わりないですね。 読み込むときは次のようにすればよいでしょう。
// クロスドメインの http://hoge.com/test.cgi からJSONPを取得 $.getJSON("http://hoge.com/test.cgi?callback=?", function(data){ alert("key1:" + data.key1); alert("key2:" + data.key1); });
callback=?ってなんだそら?ってのは後で説明するとして、 ここで疑問になるのは、何でJSONPだとクロスドメインで読めるの?って話です。
まず、勘違いしやすいのは(というより私が勘違いしていたのは)、JSONPを「JSON『テキスト』に毛が生えたもの」、と思ってしまうこと。
断言しましょう!JSONPはテキストではなく「script」です!
そもそも、JSONはどうやって読んでいたのでしょう。
jQueryだと中身をラップしていてわからないので、中の仕組みに戻ってみましょう。
JSONを取得するときはXmlHttpRequestというメソッドで取得していました。
正確ではありませんが、ざっくりと書くと次のようです。
// 同じドメインの test.cgi から JSONデータを取得して表示 var req = new XMLHttpRequest(); req.open('GET', 'test.cgi', false); req.send(null); if(req.status == 200) { var json = req.responseText; var data = eval(json); alert("key1:" + data.key1); alert("key2:" + data.key1); }
この場合だと、XmlHttpRequestの制約でドメインの異なる場所からはデータを取得することが出来ません。 (一種の安全策ですね。) 一方でJSONPの場合は、取得方法が全く異なります!
// クロスドメインの http://hoge.com/test.cgi からJSONPを取得 var hoge = function(data) { alert("key1:" + data.key1); alert("key2:" + data.key1); } var script = document.createElement('script'); script.src = 'http://hoge.com/test.cgi?callback=hoge'; document.head.appendChild(script);
え!!wwww
ぜんぜんちゃうやん。。。
どういう仕組かというと、Javascriptのコードはクロスドメインから読むことが出来ますよね。
たとえば、jQueryの1.8.3を読むときなんかに、google (クロスドメイン) からこんなコードで読んできたりしますよね。
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
で、スクリプトが読み込まれると、そのスクリプトが自動で実行されるわけです。
さらに、javascriptには、動的にスクリプトをロードする機能があります。
これらの特徴を生かしてデータをクロスドメインから受け取る手法がJSONPです。
事前にこちらのスクリプトで、コールバック関数を用意しておき、
そのコールバック関数と同名の関数の引数にJSONをつっこんだ(Padding)したスクリプトを返すようクロスドメインのサーバーに要求し、
動的にそのスクリプトを読み込んだ瞬間にこちらのコールバック関数が引数にJSONをつっこんだ状態で呼び出される。
そのコールバック関数内でJSONのデータを使って好きな処理をしてあげれば一丁上がり!
これがJSONPの仕組みです。
参考: 「セミコロンが必要ない: jsonp」
http://blog.livedoor.jp/tempdog/archives/482314.html
「気になったもの JSONP他」
http://fukenkoh.blog.shinobi.jp/Entry/12/
これがわかってくると、$.getJSON()の処理もわかってきます。
// クロスドメインの http://hoge.com/test.cgi からJSONPを取得 (再掲) $.getJSON("http://hoge.com/test.cgi?callback=?", function(data){ alert("key1:" + data.key1); alert("key2:" + data.key1); });
これはjQueryの仕様なので覚えるしかありませんが、
callback=?という部分の?にjQueryの独自の関数名を埋め込んで呼び出されます。
そして同名の関数をサーバーからロードし、function(data)が実行されます。
実際、通信の様子を見てみると、
http://hoge.com/test.cgi?callback=jQuery183002421796810813248_1357198175025
のようなリクエストをかけていることがわかります。
参考 「jQueryとJSONPと$.ajax()と無名関数」
http://d.hatena.ne.jp/littlebuddha/20100223/1266928860
jQueryのバージョンにも注意!
jQueryが1.4とかだと、XmlHttpRequestしか対応していなかったりします。
そのせいで、クロスドメインで呼んだらだめよ~、ってエラーが出ます。
cgi側でも注意。
content-type: application/json; charset=utf-8
ではなく、
content-type: application/javascript; charset=utf-8
とすること。
あくまで「スクリプト」なのです。
参考
「JSONとJSONPのContent-typeに書くMIMEタイプ」
http://d.hatena.ne.jp/kanonji/20110406/1302065168
「JSONとJSONPの違い」
http://taiju.hatenablog.com/entry/20090902/1251851867
「jQuery.getJSON(url, [data], [handler])」
もう1つは、クエリパラメータとしてcallback=functionNameが与えられたとき、
functionName({ "key1": "value1", "key2": "value2" })
とコレスポンスのスクリプトでコールバック関数の名称を合わせてあげる必要があります。
関数名が固定だと困るのです。
たとえば、jQueryの例でいうと、先ほどあげたように、
callback=jQuery183002421796810813248_1357198175025
のような謎の文字列でコールバック関数を要求してきます。
これに対応できないと、データを受け取ってもコールバックが実行されないのです。
細かいことですが、JSONPのデータにセミコロンはいりません!
こんなにも落とし穴がたくさんあるのにもかかわらず、
なんでちゃんとした解説がほとんどないのよ!!
というわけで、
あのころの自分に解説してあげたかった解説を同じような境遇の何人かの方に贈ります。
ちょっと関係ないけど
「Content-type:text/htmlのcharset指定はUTF-8,utf-8,utf8どれが正しいの?調べてみた。」
http://dqn.sakusakutto.jp/2011/08/content-type-texthtml-charset-utf8.html
わかりやすかったです。
疑問が解決しました。記事ありがとうございます。
nonameさん、お役に立ててうれしいです。
コメントありがとうございました!
tsujimotter (管理人)
javascript初心者で全部は理解していませんが、この内容、目からウロコでした!
yasuoogleさん、ありがとうございます!
そのお言葉大変励みになります!
tsujimotter (管理人)
tsujimotterさん
とにかく感謝です。これから自分もこのような記事を書けるように頑張ります
(わたしのはじめてのコメントですが)
かれこれ、三週間ほどSOP問題で頭を悩ましていましたがスッキリしました。
大変有用な記事、ありがとうございます。
スッキリしたと書いておいてですが、一点質問させてください。
■質問:
JSONPではなく、XMLで結果を返すようなWebAPI(AmazonProductAdvertisingAPI等)は、どのようにSOPを回避すると良いのでしょうか。
色々調べてはいるのですが、詰まってしまいました。
お忙しいとは思いますが、お時間あるときにご教授頂ければ幸いです。
popcosorn様
コメントありがとうございます。励みになります。^-^
XML の Same Origin Problem の回避方法ですが、JavaScript だけを使う方法は、残念ながら私は知りません
自前のサーバーをお持ちでしたら、以下のサイトのように PHPでクロスドメインのデータを取得するものを作って、それを自分のサーバーにおき、そこから jQuery で間接的に取得してくればよいかと思うのですが・・・ちょっと面倒ですよね。
http://qiita.com/tsunet111/items/c1a4ce1499e53fce449a
返信ありがとうございます。XMLについて質問させて頂いたものです。かなり期間が空いてしまい、申し訳ありません。
> JavaScript だけを使う方法
そうですよね。ライブラリとかを使うならまだしも。。。
> ちょっと面倒ですよね。
ちょっと面倒でしたが、私もその方法で何とかしましたw
返信ありがとうございました。今後もイケてる記事頑張ってください^^
疑問が解決しました、ありがとうございます。
ざっくりとわかりやすく説明されていて大変助かりました。
(特にセミコロンの有無がよくわからず、最後に念押しされていてありがたかったですw)