JSONPをごりごり実装するときのポイント


Ajaxの代替手段として有名なJSONP(JSON with Padding)の実装手順を勉強がてらまとめておきます。
jQueryなどを使うとあまり仕組みを意識せずに利用はできますが、動きを理解するにはゴリゴリ書いてみるのが一番です。

JSONPとは?

Javascriptにて非同期でデータを取得する手段の一つです。
非同期通信というとAjax(XMLHttpRequestを使った通信)が有名ですが、これにはクロスドメイン制約があり、別ドメインからデータを取得することができませんでした。そこで生まれたハック的な代替手段です。最近ではクロスドメイン制約を回避できるXMLHttpRequest2も出てきましたが、直接JSONを扱える手軽さからまだまだ現役(たぶん)です。

まず仕組みをざっくり

ポイントは2点。

  • scriptタグのsrcに、処理させたいサーバ側のファイルを指定する
  • サーバ側はjson形式に整形して受け渡す

わかってしまえば簡単なのですが、scriptのソース指定はクロスドメインを飛び越えられること、jsファイル以外でも指定できること、サーバ側であたかもjsファイルを返したかのような動きをさせること、で通信を実現しています。

クライアント側の実装

上記の例ではスクリプトタグに書いているため、任意のタイミングで通信を実行できません。スクリプトタグの生成と実行、またデータの受け取りをクライアント側のJavascriptで行うことで任意のタイミングでの非同期処理を実現します。

サーバ側を呼び出す部分

ボタンが押されたタイミングで通信開始するようにしてみます。

呼び出し部分

window.onload=function(){
    //ボタンにクリックイベントを追加
    var btnGetData = document.getElementById("btnGetData");
    btnGetData.addEventListener("click",function(){
        //スクリプトタグ生成
        var sc = document.createElement("script");
        sc.type = 'text/javascript';
        //アクセス先を指定
        sc.src = "//example.com/api/getData.php?key=xxx&callback=callbackFunc";
        //生成したスクリプトタグを追加、実行
        var parent = document.getElementsByTagName("script")[0];
        parent.parentNode.insertBefore(sc,parent);
    },false);
}

通信先がいくつもある場合は関数化しておくとよさそうです。
また、アクセス先urlの"?"以降にデータを付け足すことでデータを送信できます。手軽ですね。

データを受け取る部分

データが帰ってきたときに実行する関数を用意しておきます。
ただ、非同期のためいつデータが帰ってくるかわかりません。うまく実行される仕組みは後述で。

ポイントは引数でJSONを受け取れるようにしておくところです。

コールバック関数
callbackFunc = function(jsonData){
    //リストを書き換えるイメージ
    var view = document.getElementById('viewList');
    //要素をすべて消す
    while (view.firstChild) view.removeChild(view.firstChild);

    //要素追加のイメージです。実装方法はjsonの形式次第です。
    for(var i=0;i<jsonData.length;i++){
        var li = document.createElement("li")
        li.innerHTML= jsonData[i].text;
        view.appendChild(li);
    }
};

サーバ側の実装

サーバ側では何かしら処理をした結果をJSONで返すわけですが、ここで一工夫あります。
JSONそのものを返すのではなく、JSONを引数にとった関数自体をjsデータとして返してしまいます。
つまり、クライアント側で定義してある関数をサーバ側から呼び出しているイメージです。
ということで、クライアント側からは呼び出してほしい関数名を先に渡しておくわけです。

コールバック関数名
//callbackFuncという名前を一緒に送っている。
sc.src = "//example.com/api/getData.php?key=xxx&callback=callbackFunc";

サーバ側はPHPで書いた例です。

サーバ側の処理
<?php
//コールバック関数名を取得しておきます
$callback = "jsonCallback";
if(isset($_GET['callback'])){
    $callback=$_GET['callback'];
}
//情報を受け取ります
$key = "デフォルト";
if(isset($_GET['key'])){
    $key=$_GET['key'];
}

//検索などの処理 ダミーです
$result = getData($key)

//エスケープ等の処理をしておきます
$json = json_encode($result, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);

//jsonではなくjavascript指定です。
header("Content-type: application/x-javascript");
print <<<END
$callback($json);
END;

//変数が展開されて
// callbackFunc([{"key":"\u6666\u5678",・・・}]) みたいになります。

これで、結果が帰ってきたタイミングでcallbackFuncが実行され、非同期処理ができます。

まとめ

やってること自体は難しいことではないのですが、なかなかに面白い仕組みだと思いました。
ポイントとしては

  • クライアント側:動的にscriptタグを生成
  • クライアント側:コールバック関数を準備
  • クライアント側:コールバック関数名をurlにのっけて渡す
  • サーバ側:content-typeはjsonではなくjavascriptを返す
  • サーバ側:コールバック関数の引数にjsonを入れて投げ返す

といったところかと思います。

あとはうまく関数化するところを考えていきたい。
jQuery使えという話もありますが…。