PostHeaderIcon 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」で」

http://blog.flatlabs.net/20110304_231535/

 

で、これを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&param2=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])」

http://stacktrace.jp/jquery/api/ajax/jquery.getjson.html

 

 

もう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

 

 

11 Responses to “JSONPで悩むある程度の人々へ”

  • noname says:

    わかりやすかったです。
    疑問が解決しました。記事ありがとうございます。

    • admin says:

      nonameさん、お役に立ててうれしいです。
      コメントありがとうございました!

      tsujimotter (管理人)

  • yasuoogle says:

    javascript初心者で全部は理解していませんが、この内容、目からウロコでした!

  • king8moon says:

    tsujimotterさん
    とにかく感謝です。これから自分もこのような記事を書けるように頑張ります
    (わたしのはじめてのコメントですが)

  • popcosorn says:

    かれこれ、三週間ほどSOP問題で頭を悩ましていましたがスッキリしました。
    大変有用な記事、ありがとうございます。

    スッキリしたと書いておいてですが、一点質問させてください。

    ■質問:
    JSONPではなく、XMLで結果を返すようなWebAPI(AmazonProductAdvertisingAPI等)は、どのようにSOPを回避すると良いのでしょうか。

    色々調べてはいるのですが、詰まってしまいました。
    お忙しいとは思いますが、お時間あるときにご教授頂ければ幸いです。

    • admin says:

      popcosorn様

      コメントありがとうございます。励みになります。^-^
      XML の Same Origin Problem の回避方法ですが、JavaScript だけを使う方法は、残念ながら私は知りません

      自前のサーバーをお持ちでしたら、以下のサイトのように PHPでクロスドメインのデータを取得するものを作って、それを自分のサーバーにおき、そこから jQuery で間接的に取得してくればよいかと思うのですが・・・ちょっと面倒ですよね。
      http://qiita.com/tsunet111/items/c1a4ce1499e53fce449a

      • popcosorn says:

        返信ありがとうございます。XMLについて質問させて頂いたものです。かなり期間が空いてしまい、申し訳ありません。

        > JavaScript だけを使う方法
        そうですよね。ライブラリとかを使うならまだしも。。。

        > ちょっと面倒ですよね。
        ちょっと面倒でしたが、私もその方法で何とかしましたw

        返信ありがとうございました。今後もイケてる記事頑張ってください^^

Leave a Reply