JSONP によるクロスドメインの通信: 第 1 回 JSONP と jQuery を組み合わせ、強力なマッシュアップを迅速に作成する

現在、Web 上には数多くの Web サービス API が公開されているため、さまざまな Web ソースからコンテンツを取得してマッシュアップを作成することが非常に容易にできるようになっています。しかしそのためには適切な API とツールを利用できなければなりません。この記事では、あまり知られていないクロスドメインの呼び出し手法 (JSONP) と柔軟な JavaScript ライブラリー (jQuery) とを組み合わせ、強力なマッシュアップを驚くほど迅速に作成する方法を学びます。

Seda Özses, IT Specialist, IBM

Photo of Seda OzsesSeda Özses は ibm.com 全体を統括するウェブマスター・チームのメンバーです。彼女は主に ibm.com 全体としての Web ポータルを担当しており、XSL に関する作業と、彼女のチームに必要なさまざまな要件を管理しています。



Salih Ergül, IT Architect, Independent Consultant

Photo of Salih ErgulSalih Ergül は自営の IT アーキテクトであり、電話業界や銀行業界における大規模でミッション・クリティカルなエンタープライズ・アプリケーション統合プロジェクトを専門としています。彼はこれまで Java プログラミングに関して何本かの記事を書いており、現在は銀行業界で業務を行っています。



2009年 2月 24日

はじめに

Ajax (Asynchronous JavaScript and XML) は新世代の Web サイト (よく Web 2.0 サイトと呼ばれます) を駆動する重要な技術です。Ajax を利用すると、Web アプリケーションの表示や動作に影響を与えずにバックグラウンドでデータを取得することができます。データの取得は XMLHttpRequest 関数を使って行います (XMLHttpRequest 関数はクライアント・サイドの JavaScript によってリモート・サーバーと HTTP で接続するための API です)。また Ajax は、複数のソースから得たコンテンツを 1 つの Web アプリケーションに統合する、多くのマッシュアップの原動力でもあります。

しかしこの手法では、ブラウザーによって強制される制約のため、クロスドメイン通信を行うことができません。異なるドメインからデータをリクエストしようとすると、セキュリティー・エラーが発生します。データが存在するリモート・サーバーを制御し、すべてのリクエストが同じドメインに送信されるようにすれば、これらのセキュリティー・エラーは発生しなくなりますが、皆さん自身のサーバーしか使えないとしたら Web アプリケーションの面白さがありません。では、サードパーティーの複数のサーバーからデータを収集する必要がある場合にはどうすればよいのでしょう。

同一生成元ポリシーによる制約を理解する

同一生成元ポリシーは、1 つのドメインからロードされたスクリプトによって別のドメインの文書のプロパティーが取得されたり操作されたりすることを防ぐためのものです。つまり、リクエストされる URL のドメインは現在の Web ページのドメインと同じでなければなりません。これは基本的に、ブラウザーは異なる生成元からのコンテンツを分離し、操作できないように保護するということです。ブラウザーに関するこのポリシーは非常に古く、その歴史は Netscape Navigator 2.0 にまでさかのぼります。

この制約を回避するための比較的単純な方法は、Web ページがそのページの生成元の Web サーバーからデータをリクエストするようにし、その Web サーバーをプロキシーとして動作させて実際のサードパーティーのサーバーにリクエストを中継する方法です。この手法は広く使われていますが、スケーラブルではありません。もう 1 つの方法は、フレーム要素を使って現在の Web ページに新しい領域を作成し、そしてサードパーティーのすべてのコンテンツを GET リクエストを使って取得する方法です。しかしコンテンツを取得した後は、フレーム内のそうしたコンテンツは同一生成元ポリシーの制約を受けることになります。

この制約を回避するための、もっと有望な方法は、動的なスクリプト要素を Web ページに挿入する方法です (このスクリプト要素のソースは、他のドメインにあるサービスの URL を指し、スクリプト自体の中にデータを取得します)。このスクリプトはロードされると実行されます。これが動作する理由は、動的スクリプトを挿入しても同一生成元ポリシーの制約は受けず、その動的スクリプトはあたかもその Web ページを提供しているドメインからロードされたかのように扱われるためです。ただしこのスクリプトが、さらに別のドメインから文書をロードしようとすると、このスクリプトの実行は失敗します。しかし幸いなことに、この手法は JSON (JavaScript Object Notation) を追加すれば改善することができます。

JSON と JSONP

JSON はブラウザーとサーバーとの間で情報を交換するための (XML に比較して) 軽量なデータ・フォーマットです。JavaScript 開発者にとって JSON が魅力的な点は、JSON が JavaScript オブジェクトのストリング表現であるということです (これが JavaScript Object Notation という名前の由来です)。例えば、 symbol と price という 2 つの属性を持つ ticker オブジェクトがあるとします。この ticker オブジェクトを JavaScript で定義する方法は下記のとおりです。

var ticker = {symbol: 'IBM', price: 91.42};

そしてこれを JSON で表現したものが下記です。

{symbol: 'IBM', price: 91.42}

データ交換フォーマットとしての JSON とその使い方については「参考文献」を参照してください。リスト 1 では、呼び出されると IBM の株価を表示する JavaScript 関数を定義しています。(これを Web ページの中に統合する方法の詳細については省略します。)

リスト 1. showPrice 関数を定義する
function showPrice(data) {
    alert("Symbol: " + data.symbol + ", Price: " + data.price);
}

この関数を呼び出すためには JSON データをパラメーターとして渡します。

showPrice({symbol: 'IBM', price: 91.42}); // alerts: Symbol: IBM, Price: 91.42

これで、この 2 つのステップを Web ページに含めるための準備ができました (リスト 2)。

リスト 2. showPrice 関数とパラメーターを Web ページに含める
<script type="text/javascript">
function showPrice(data) {
    alert("Symbol: " + data.symbol + ", Price: " + data.price);
}
</script>
<script type="text/javascript">showPrice({symbol: 'IBM', price: 91.42});</script>

このページをロードすると、図 1 のようなアラートが表示されるはずです。

図 1. IBM ticker
IBM ticker

ここまでの時点では、静的な JSON データをパラメーターに持つ JavaScript 関数を呼び出す方法を説明しました。しかし JSON データを関数呼び出しの中に動的にラップすると、動的なデータを使って関数を呼び出すことができます。これは動的 JavaScript 挿入と呼ばれる手法です。この手法がどのように動作するのかを見るために、下記の行が含まれた ticker.js という JavaScript ファイルを用意します。

showPrice({symbol: 'IBM', price: 91.42});

今度は Web ページの中のスクリプトを変更し、リスト 3 に示すコードのようにします。

リスト 3. 動的 JavaScript 挿入のコード
<script type="text/javascript">
// This is our function to be called with JSON data
function showPrice(data) {
    alert("Symbol: " + data.symbol + ", Price: " + data.price);
}
var url = “ticker.js”; // URL of the external script
// this shows dynamic script insertion
var script = document.createElement('script');
script.setAttribute('src', url);

// load the script
document.getElementsByTagName('head')[0].appendChild(script); 
</script>

リスト 3 の例では、動的に挿入された JavaScript コード (ticker.js の中にある JavaScript コード) は実際の JSON データをパラメーターに使って showPrice() 関数を呼び出します。

先ほど学んだように、動的なスクリプト要素を文書に挿入しても同一生成元ポリシーの制約は受けません。つまり、さまざまなドメインから、JSON データを持った JavaScript を動的に挿入することができるのです。これが実は JSONP (JSON with Padding) なのです。つまり JSONP は関数呼び出しの中に JSON データをラップしています)。ただしこれを行うための注意点として、挿入の時点でコールバック関数 (この例では showPrice()) が Web ページの中に既に定義されている必要があります。

一方で、いわゆる JSONP サービス (つまりリモート JSON サービス) には他にも機能があり、返される JSON データを、ユーザーが指定した関数呼び出しの中にラップすることができます。この手法はリモート・サービスがコールバック関数の名前をリクエスト・パラメーターとして受け付けるという事実を利用しています。次にリモート・サービスはこの関数への呼び出しを生成して JSON データをパラメーターとして渡し、この JSON データがクライアントに受信されると Web ページに挿入されて実行されます。


jQuery による JSONP のサポート

jQuery はバージョン 1.2 から JSONP 呼び出しをネイティブ・サポートしています。JSONP コールバックを指定すると (url?callback=? という構文を使います)、別のドメインにある JSON データをロードすることができます。

jQuery はこの ? を、生成された (呼び出し対象の) 関数名で自動的に置き換えます。リスト 4 はこのコードを示しています。

リスト 4. JSONP コールバックを使う
jQuery.getJSON(url+"&callback=?", function(data) {
    alert("Symbol: " + data.symbol + ", Price: " + data.price);
});

jQuery はこれを行うために、スクリプトが挿入される際に呼び出されるウィンドウ・オブジェクトをグローバル関数にします。この関数は挿入が終わると削除されます。しかも jQuery は、クロスドメインではない呼び出しに対しても最適化を行います。同じドメインに対してリクエストが行われると、jQuery はそのリクエストを通常の Ajax リクエストに変換します。

JSONP をサポートするサービスの例

先ほどの例では静的なファイル (ticker.js) を使って Web ページに JavaScript を動的に挿入しました。この方法では JSONP による応答が返されますが、URL の中でコールバック関数の名前を定義することができません。これでは JSONP サービスではありません。では、これを真の JSONP サービスに変換するためにはどうすればよいのでしょう。それにはさまざまな方法がありますが、ここでは 2 つの例として、PHP を使う方法と Java を使う方法を説明します。

まず、このサービスがリクエスト URL の中で callback というパラメーターを受け付けるものとします。(パラメーターの名前は重要ではありませんが、クライアントとサーバーの両方がパラメーターの名前について合意している必要があります。) また、このサービスに対するリクエストは下記のようなものだとします。

http://www.yourdomain.com/jsonp/ticker?symbol=IBM&callback=showPrice

この場合の symbol はリクエストされたティッカー・シンボルを表すリクエスト・パラメーターであり、また callback は Web アプリケーションの中にあるコールバック関数の名前です。jQuery が JSONP をサポートしていることを利用して、このサービスをリスト 5 のコードを使って呼び出すことができます。

リスト 5. コールバック・サービスを呼び出す
jQuery.getJSON("http://www.yourdomain.com/jsonp/ticker?symbol=IBM&callback=?", 
function(data) {
    alert("Symbol: " + data.symbol + ", Price: " + data.price);
});

コールバック関数の名前として、実際の関数名の代わりに ? を使用していることに注目してください。こうする理由は、jQuery によって ? は生成された関数名で置き換えられるためです (関数名は例えば jsonp1232617941775 などであり、インライン関数を呼び出します)。こうすることによって、showPrice() のような関数を定義する必要がなくなります。

リスト 6 は PHP で実装された JSONP サービスの抜粋です。

リスト 6. PHP による JSONP サービスの抜粋
$jsonData = getDataAsJson($_GET['symbol']);
echo $_GET['callback'] . '(' . $jsonData . ');';
// prints: jsonp1232617941775({"symbol" : "IBM", "price" : "91.42"});

リスト 7 は同じ機能を Java™ サーブレットを使って実現する方法を示したものです。

リスト 7. Java サーブレットによる JSONP サービス
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
  throws ServletException, IOException {
	String jsonData = getDataAsJson(req.getParameter("symbol"));
	String output = req.getParameter("callback") + "(" + jsonData + ");";

	resp.setContentType("text/javascript");
          
	PrintWriter out = resp.getWriter();
	out.println(output);
	// prints: jsonp1232617941775({"symbol" : "IBM", "price" : "91.42"});
}

ではマッシュアップを作成する場合 (つまり 1 つの Web ページ上に表示することを目的として、サードパーティーのサーバーからのコンテンツを統合する場合) にはどうすればよいのでしょう。答えは単純です。サードパーティーの JSONP サービスを使えばよく、そうしたサービスは非常に数多くあるのです。

既製の JSONP サービス

JSONP の使い方は理解できたので、既製の JSONP Web サービスを使って独自のアプリケーションやマッシュアップの作成を開始することができます。以下は皆さんの次期開発プロジェクトのための出発点をいくつか示したものです。(ヒント: 以下に示した URL をコピーしてブラウザーのアドレス・フィールドに貼り付けると、どのような JSONP レスポンスが得られるかを検証することができます)。

Digg API: Digg のトップ・ストーリー

http://services.digg.com/stories/top?appkey=http%3A%2F%2Fmashup.com&type=javascript
&callback=?

Geonames API: 郵便番号に対する位置情報

http://www.geonames.org/postalCodeLookupJSON?postalcode=10504&country=US&callback=?

Flickr API: Flickr に投稿された猫の写真で一番最近のもの

http://api.flickr.com/services/feeds/photos_public.gne?tags=cat&tagmode=any
&format=json&jsoncallback=?

Yahoo Local Search API: 郵便番号が 10504 の場所にあるピザ屋を検索する

http://local.yahooapis.com/LocalSearchService/V3/localSearch?appid=YahooDemo&query=pizza
&zip=10504&results=2&output=json&callback=?

注意事項

JSONP はマッシュアップを作成するための非常に強力な手法ですが、残念ながらクロスドメイン通信が抱えるすべての課題を JSONP によって解決できるわけではありません。JSONP にはいくつかの欠点があり、実際に開発リソースを割り当てる前に、そうした欠点を真剣に考慮する必要があります。何よりもまず、JSONP 呼び出しに対するエラー処理がありません。動的なスクリプト挿入が適切に動作すれば呼び出しが行われますが、動作しなければ何も起こらず、暗黙のうちに失敗します。例えば、サーバーからの 404 エラーをキャッチすることができません。またリクエストをキャンセルすることも再試行することもできません。ただし、適当な時間だけ待った後にタイムアウトすることはできます。(jQuery の今後のバージョンでは JSONP リクエストに対するアボート機能が追加されるかもしれません。)

JSONP のもう 1 つの大きな欠点は、信頼できないサービスと組み合わせて使用すると非常に危険であることです。JSONP サービスは関数呼び出しにラップして JSON レスポンスを返し、ブラウザーがその関数を実行するため、ホスト側の Web アプリケーションはさまざまな攻撃を受けやすくなります。JSONP サービスを使おうとする場合には、そのサービスを利用することによってさらされる脅威を認識しておくことが非常に重要です。(詳しくは「参考文献」を参照してください。)


まとめ

このシリーズ第 1 回目の今回は、JSONP と jQuery とを組み合わせて強力なマッシュアップを迅速に作成する方法を説明しました。以下はこの記事で説明した話題です。

  • ブラウザーの同一生成元ポリシーによる制約と、その回避方法。
  • JSONP がクロスドメイン通信手法として効果的であり、同一生成元ポリシーによる制約を回避できること。
  • Web アプリケーション開発者が迅速にマッシュアップを作成する上で JSONP が有望であること。
  • JSONP サービスとその使い方の例 (Ticker サービス)

このシリーズの次回の記事では、単一エンドポイントの JSONP サービスとして YQL (Yahoo Query Language) を紹介し、Web 全体にわたってのデータの照会、フィルタリング、組み合わせを YQL を利用して行う方法を説明します。また YQL と jQuery を使ってサンプルのマッシュアップ・アプリケーションを作成する方法についても説明します。

参考文献

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development
ArticleID=377828
ArticleTitle=JSONP によるクロスドメインの通信: 第 1 回 JSONP と jQuery を組み合わせ、強力なマッシュアップを迅速に作成する
publish-date=02242009