これでできる! クロスブラウザJavaScript入門

第11回 JSONP入門

この記事を読むのに必要な時間:およそ 5 分

JSONPの活用例

では,JSONPの使い方を見てみましょう。今回は短縮URLを展開するAPIを利用します。まず,次のような短縮URLがあります。

短縮URL

<ul id="tinyurls"> 
<li><a href="http://tinyurl.com/2b9hf8c">http://tinyurl.com/2b9hf8c</a></li>
<li><a href="http://tinyurl.com/242xed7">http://tinyurl.com/242xed7</a></li>
<li><a href="http://tinyurl.com/yas6fwn">http://tinyurl.com/yas6fwn</a></li>
</ul>

このリンクを展開してみましょう。

JSONPの利用例#1

(function(){
var REURL_API = 'http://ss-o.net/api/reurl.json';
var tinyurls = document.getElementById('tinyurls');
var links = tinyurls.getElementsByTagName('a');
var TEXT = ('textContent' in document.body) ?
            'textContent' : 'innerText';
for (var i = 0, len = links.length;i < len; i++) {
(function(i){
  var a = links[i];
  var url = encodeURIComponent(a.href);
  var script = document.createElement('script');
  // callbackの名前をユニークに
  var callbackName = 'callback' + i;
  script.src = REURL_API + '?url=' + url +
               '&callback=' + callbackName;
  // callbackの名前でグローバル関数を定義
  window[callbackName] = function(data){
    a.title = data.url;
    a[TEXT] = data.url;
  };
  document.body.appendChild(script);
})(i);
}
})();

ここで,for文の中に無名関数を作っていますが,この理由については第5回のクロージャ編で説明していますので,よろしければ参照ください。

さて,上記のコードでも動いてはいますが,グローバル関数を何個も作ってしまうのはよろしくありません。そこで,次のようにグローバルオブジェクトを1つ用意して,そのメソッドとしてコールバック関数を定義,JSONP APIからグローバルオブジェクト.コールバック関数名のようにして呼び出す方法がお薦めです。

JSONPの利用例#2

(function(){
window.ReurlAPI = {}; // グローバルオブジェクトを用意
var REURL_API = 'http://ss-o.net/api/reurl.json';
var tinyurls = document.getElementById('tinyurls');
var links = tinyurls.getElementsByTagName('a');
var TEXT = ('textContent' in document.body) ?
            'textContent' : 'innerText';
for (var i = 0, len = links.length;i < len; i++) {
(function(i){
  var a = links[i];
  var url = encodeURIComponent(a.href);
  var script = document.createElement('script');
  // callbackの名前をユニークに
  var callbackName = 'callback' + i;
  script.src = REURL_API + '?url=' + url +
               '&callback=ReurlAPI.' + callbackName;
  // callbackの名前でグローバル関数を定義
  ReurlAPI[callbackName] = function(data){
    a.title = data.url;
    a[TEXT] = data.url;
  };
  document.body.appendChild(script);
})(i);
}
})();

JSONPのセキュリティ

JSONPはJavaScriptの仕様の穴を突いているといっても過言ではないような技術です。そのため,安全に利用するための仕組みなども用意されていません。その分,利用する側,提供する側が十分に注意を払わなければいけません。

なお,将来的にはクロスオリジンXMLHttpRequestやpostMessageなどのセキュリティコントロールがし易いAPIが使えるようになる予定です。

また,JSONPのセキュリティについては[気になる]JSONPの守り方 - @ITに詳しく解説されています。是非こちらも参照してください。

JSONPを利用する場合

前述の通り,JSONPを利用することはそのAPIの提供元にJavaScriptの実行権限を与えることを意味します。信用できないサービスのJSONP APIを利用しないようにしましょう。

また,JSONPの結果を安易にinnerHTMLなどでHTMLとして解釈した場合もクロスサイトスクリプティングのリスクがあります。なるべくなら上記のサンプルのようにtextContent(IE以外)かinnerText(IE用)を使用するか,createTextNodeでテキストノードとして処理するようにしましょう。

加えて,上記のAPIのようなケースでは取得できるデータがURLであることを期待していますが,実はjavascript:で始まるブックマークレット形式のデータや,データスキームなどのデータが帰ってくることもありえます。それらをリンクのhrefなどに設定してしまうのも避けたほうがよいでしょう。

JSONPを提供する場合

JSONPには特定のサイトからのリクエストに対してのみデータを返すように制御する仕組みなどはありません。JSONPで提供するデータはあらゆるドメインから利用可能になります。当然のことですが機密情報をJSONPのデータに含めてはいけません。

また,これも当然のことですがJSONPとして返すデータに外部からの入力されたデータを含めるのであれば適切なエスケープが必要となります。具体的にはデータ部分が正しいJSON形式となるように注意するとよいでしょう。

さらに,JSONPのコールバック関数に指定できる文字列を自由に設定できるようにしてしまうとその部分にJavaScriptやHTMLを書くことができてしまうので,使用できる文字列は制限したほうがよいでしょう(例えば[a-zA-Z0-9_.\[\]]など⁠⁠。

ちなみにJSONPを提供する場合,そのContent-Typeはtext/javascript; charset=utf-8で返すのが良いでしょう。JSONのContent-Typeはapplication/json; charset=utf-8ですが,JSONPの場合実は単なるJavaScriptであることは前述のとおりです。

まとめ

今回はJSONPの基礎を詳しく解説してみました。JSONPが実は単なるJavaScriptファイルの読み込みに過ぎない実に簡単な仕組みであることは理解頂けたのではないかと思います。

次回はXMLHttpRequestを中心に非同期処理を掘り下げていく予定です。

著者プロフィール

太田昌吾(おおたしょうご,ハンドルネーム:os0x)

1983年生まれ。JavaScriptをメインに,HTML/CSSにFlashなどのクライアントサイドを得意とするウェブエンジニア。2009年12月より、Google Chrome ExtensionsのAPI Expertとして活動を開始。

URLhttp://d.hatena.ne.jp/os0x/