複数の Web サイトが協調して動作する必要のある場合が増えています。例えばオンラインで賃貸物件を紹介する Web サイトの場合、特定の賃貸物件の場所を示すために Google マップをサポートする必要があります。そうしたニーズに対応するため、さまざまな種類のマッシュアップが登場しています。マッシュアップは、さまざまなプロバイダーのデータやコンポーネントを統合し、より実用的な Web アプリケーションへとカスタマイズしたものです。マッシュアップ、つまりコラボレーション機能は、Web 2.0 の重要な部分と考えられています。
驚いたことに、Ajax (Asynchronous JavaScript and XML) とマッシュアップとの組み合わせは容易ではありません。ブラウザーによって課せられるセキュリティー制約により、ページ上のさまざまなウィジェット同士を互いに通信させることも簡単ではありません。この問題を解決する方法として以前から、サーバー・サイドにプロキシーを構築する方法がありますが、この方法はスケーラブルではありません。この記事では、これとは別のソリューションとして、クライアント・サイドでクロスドメインの通信やデータ転送を行う方法をいくつか学びます。
SOP (Same Origin Policy: 同一生成元ポリシー) は、1 つの生成元からロードしたスクリプトによって別の生成元の文書内のプロパティーやメソッドが取得または操作されるのを防止するためのものです。origin (生成元) という用語は、ドメイン名、アプリケーションのプロトコル、スクリプトを実行する文書のポートの組み合わせを表します。しかし、SOP の概念については誤解されている場合があります。SOP は、単にサイト A がサイト B から情報を取得することができない、というだけではありません。SOP 制約の下で何ができ、何ができないかを理解する必要があります。
例えば、生成元 A の Web ページでは以下の内容を実行することができます。
- 生成元 B のスクリプト、CSS スタイルシート、または画像を取得する
- 生成元 B のページを指す iframe/frame を含める
- HTML 要素 (
iframeやimg) のsrc属性を使用して生成元 B に何らかの情報を送信する
生成元 A の Web ページでは、以下の内容を実行することはできません。
- 生成元 B に対して Ajax 呼び出しを行う
- ページ B を指す iframe/frame のコンテンツを読み取ったり、操作したりする
なぜこのような制約があるかというと、それは主にユーザーの重要な情報を保護するためです。SOP の前提となっているのは、ユーザーがプロバイダーとやり取りする場合、そのユーザーはそのサイトに送信された情報が他のサイトに漏れてしまうことを望まない、という考え方なのです。この種の制約により、異なる Web サイト間で行う協調型作業は制限を受けますが、ユーザーは潜在する有害な攻撃から保護されます。
この問題に対処するためのソリューションは数多くあります。例えば、JSONP は Web ページが任意のソースから動的にスクリプトをロードできるという事実を利用しています。ただし JSONP には主な制約として、Ajax 呼び出しのようなエラー処理メカニズムがないこと、そして script タグを要求するリクエストのメソッドは GET であるため、長さに制約があること、の 2 つが挙げられます (JSONP については「参考文献」セクションを参照)。
以下のセクションでは、クロスドメインの通信やデータ転送を行うための、クライアント・サイドのソリューションについて説明します。各ソリューションには長所と短所があり、どの方法を選択するかはアプリケーションのシナリオに大きく依存します。
生成元 A と生成元 B のスーパー・ドメインが共通の場合、document.domain プロパティーを変更すると 2
つの文書同士が容易に互いの文書にアクセスできるようになります。document.domain は HTML
仕様に規定された読み取り専用のプロパティーです。最近のほとんどのブラウザーでは、document.domain
をスーパー・ドメイン (最上位レベルではありません) に設定できるようになっています。例えば URL が www.myapp.com/index.html
の文書はその文書自身のドメインを myapp.com
と設定する一方、sample.myapp.com/index2.html にある別の文書もその文書自身のドメインを myapp.com と設定することができます。図 1 は document.domain がどのように機能するかを示しています。
図 1. document.domain
このクロスサブドメインによるソリューションを使用すると、サブドメインが異なる生成元同士が同じスーパー・ドメインの下で通信することができ、SOP の制約を受けません。しかし厳密に言えば、クロスサブドメインによるソリューションが最も適しているのはイントラネット・アプリケーションです。
URLのハッシュ (フラグメント id) によるソリューション
URL は下記の図 2 のようにいくつかの部分で構成されています。
図 2. URL の構成要素
通常は URL を少しでも変更すると新しい Web ページがロードされます。例外はハッシュ値を変更した場合です。URL のハッシュをどのように変更しても、ページが更新されることはありません。ハッシュは、既に多くの Web 2.0 サイトで部分的にページを更新する際に各ステップにブックマークを付けるために広く使用されています。クロスドメインの通信でも、ハッシュは貴重な資産です。生成元の異なる文書同士が互いのハッシュ値を取得する際には制約がありますが、その文書同士が (ハッシュ値を含めて) 互いの URL を設定することができます。ハッシュを使用すると、生成元の異なる文書同士が互いにメッセージを送信することができます。図 3 はその一例を示しています。
図 3. URL のハッシュ (フラグメント id) を使用した通信
図 3 の場合、A が B にメッセージを送信しようとする際には A が B のハッシュ値を変更します (リスト 1)。
リスト 1. URL のハッシュによってメッセージを送信する
function sendMsg(originURL, msg){
var data = {from:originURL, msg:msg};
var src = originURL + “#” + dojo.toJson(data);
document.getElementById('domainB').src=src;
}
|
B の関数は B 自身のハッシュ値をポーリングし、A が何を送信したかを検出します。同じ方法で B は A に応答することができます。A がそのメッセージを受信したい場合には、A も A 自身のハッシュ値をポーリングする必要があります。
リスト 2. URL のハッシュをモニタリングし、そのハッシュからメッセージを受信する
window.oldHash="";
checkMessage = function(){
var newHash = window.location.hash;
if(newHash.length > 1){
newHash = newHash.substring(1,newHash.length);
if(newHash != oldHash){
oldHash = newHash;
var msgs = dojo.fromJson(newHash);
var origin = msgs.from;
var msg = msgs.msg;
sendMessage(origin, "Hello document A");
}
}
}
window.setInterval(checkMessage, 1000);
sendMessage = function(target, msg){
var hash = "msg="+ msg;
parent.location.href= target + “#” + hash;
}
|
JSONP の場合と同様、この方法にも長さの制約がありますが、この方法のほうが JSONP の場合よりも適切にエラーを処理することができます。一部の特殊文字 (疑問符 (?) など) は URL 用として予約されているため、最初にエンコードする必要があります。
リスト 3. 特殊文字を含むメッセージを URL のハッシュを使用して送信する
function sendMsg(originURL, msg){
…
var src = originURL + “#” + encodeURI (dojo.toJson(data));
…
}
|
そして受信する場合には最初に特殊文字をデコードする必要があります。
リスト 4. 特殊文字を含むメッセージを受信する
function checkMsg(){
…
var msgs = decodeURI(dojo.fromJson(newHash));
…
}
|
ハッシュは既に多くの Web サイトで他の目的に使用されているため、それらの Web サイトでクロスドメインの通信やデータ転送のために URL のハッシュ (フラグメント id) による手法を使おうとすると複雑になります。クロスフレームのメッセージングはフラグメント id によるメッセージングといくらか似ています。図 4 はクロスフラグメントによる手法の動作を示しています。
図 4. クロスフラグメントによる手法
上記の図で A が IFrame B と通信しようとする場合、A はまず自分自身の中に IFrame を作成します。この IFrame は B と同じドメインにあるプロキシー C を指します。C はプロキシーの URL の中に、B のパラメーターまたはデータ、さらには B のフレーム識別子を含んでいます。
リスト 5. クロスフラグメントによる手法でメッセージを送信する
function sendMsg(msg){
var frame = document.createElement(“iframe”);
var baseProxy = “http://www.otherapp.com/proxy.html”;
var request = {frameName:’otherApp’,data:msg};
frame.src = baseProxy+”#”+encodeURI (dojo.toJson(request));
frame.style.display=”none”;
document.body.appendChild(frame);
}
|
C がロードされると、C は A のリクエストとデータを取得し、対応する B のメソッドを呼び出します。B と C は同じドメインにあるため、相手のウィンドウのハンドラーによって相手のメソッドを直接呼び出すことができます。その結果、A は B に適切にメッセージを送信することができ、また B は同じ方法で応答することができます。
リスト 6. クロスフラグメントによる手法でメッセージを受信する
window.onLoad = function(){
var hash = window.location.hash;
if(hash && hash.length>1){
var request = hash.substring(1,hash.length);
var obj = dojo.fromJson(decodeURI (request));
var data = obj.data;
//process data
parent.frames[obj.frameName].getData(…);// getData in a function defined in B
}
}
|
OpenAjax には、フラグメント id とクロスフレームによる手法に基づいてクロスドメインの通信やデータ転送をサポートするマネージド・ハブ・モジュールが用意されています。マネージド・ハブ・モジュールにはマネージャー・サイドとクライアント・サイドが含まれています。マネージド・ハブには、メッセージを格納するためのメッセージ・ハブが含まれています。他と通信しようとする各ウィジェットは、そのウィジェット自身の中にハブ・クライアントを構築し、そのハブ・クライアントに接続するための対応するコンテナーも構築します。コンテナーはクライアントに代わってマネージド・ハブとやり取りします。クライアント・サイドはパブリッシュ/サブスクライブ・メカニズムを使用してメッセージを送受信することができます。OpenAjax の基本的な動作フローを示したものが図 5 です。
図 5. OpenAjax のメイン動作フロー
window.name
は、クロスドメインの通信やデータ転送を行うための、扱いに注意が必要なソリューションです。通常、window.name は以下のように使用されます。
window.frames[windowName]を使用して子ウィンドウを取得する- リンク要素の中でターゲット属性として window.name を設定する
window.name
には、生成元が異なる文書同士の「橋渡し」に適した、注目に値する性質があるにもかかわらず、このプロパティーは、頻繁には使用されてはいません。どのようなページをロードする場合であれ、window.name の値は同じままです。では SOP 制約の下で window.name をどのように使用することができるのでしょう?図 6 は window.name を使用してクロスドメインの通信を行う方法を示しています。
図 6. window.name とクロスドメインの通信
ページ A が別の生成元のリソースや Web サービスを取得しようとする場合、ページ A はその外部リソースや外部サービスをターゲットとする新しい隠し IFrame B
をページ A 内に追加します。サーバーは HTML ファイルで応答するため、データには window.name プロパティーが設定されます。ページ A と IFrame B は同じドメインではないので、相変わらず A は B の name プロパティーを取得することはできません。B がデータを取得した後、B を A と同じドメインの任意のページに戻すと、A から name プロパティーにアクセスできるようになります。A がデータを取得した後は、いつでも B を破棄することができます。
dojox.io.windowName を使用してクロスドメインの通信を行う
Dojo には window.name
を使用してクロスドメインの通信を行うための手段が用意されています。そのための唯一の API は dojox.io.windowName.send(method, args) です。この API は dojo.xhrGet/dojo.xhrPost と似ています。メソッドは GET
または POST であり、args は dojo.xhr の args と似ています。例えば、リスト 7 のようにしてクライアント・サイドでクロスドメインのリクエストを送信することができます。
リスト 7. window.name によってメッセージを送信する
var args = {
url: "http://www.sample.com/testServlet?windowName=true",
load: function(data){
alert("You've got the data from server " + data);
},
error: function(error){
alert("Error occurred: " + error);
}
}
dojox.io.windowName.send("GET",args);
|
dojox.io.windowName の使い方は dojo.xhr の使い方と同じです。サーバー・サイドで windowname
トランスポートによってリソースやサービスを利用できるようにしたい場合には、リクエストの中にある windowName
パラメーターをチェックします。リクエストに windowName パラメーターが含まれている場合、サーバーが応答する HTML 文書の window.name にはクライアントに送信する必要のあるデータが設定されている必要があります。リスト 8 のコードはその一例を示しています。
リスト 8. window.name によるメッセージ送信手法をバックエンドでサポートする
testServlet.java:
protected void doGet(HttpServletRequest request,HttpServletResponse response){
//process request
String returnData = ...;
String isWindowNameReq = request.getParameter(“windowName”);
if(null !=isWindowNameReq && Boolean.parseBoolean(isWindowNameReq)){
returnData = getCrossDomainStr(returnData);
}
response.getOutputStream().print(returnData);
}
private String getCrossDomainStr(String data){
StringBuffer returnStr = new StringBuffer();
returnStr.append("<html><head><script type=\"text/javascript\">window.name='");
returnStr.append(data);
returnStr.append("'</script></head><body></body></html>");
return returnStr.toString();
}
|
生成元ドメインの任意のページにフレームを戻す際には、そのページがそのドメイン内に存在していることを確認する必要があります。ページが存在しない場合、Internet
Explorer で問題が発生します。Firefox では単純に blank.html
を使用することができます。Dojo の場合、どのページに戻るかを dojo.dojoBlankHtmlUrl プロパティーによって指定することができます。デフォルトで、そのページは Dojo ライブラリーの下にある dojo/resources/blank.html に設定されます。
window.name を使用して転送されるデータ量は URL
のハッシュを使用する場合よりもはるかに大量です。最近のほとんどのブラウザーでは、window.name を使用して 16M を超えるデータを転送することができます。
HTML5 仕様のドラフトには、クロスドメインの通信を安全に行うための新しいメソッド、window.postMessage(message, targetOrigin) があります。このメソッドが呼び出されると、メッセージ・イベントがディスパッチされ、ウィンドウがそのメッセージ・イベントをリッスンしていると、そのウィンドウはメッセージとメッセージの生成元をイベントから取得することができます。図 7 はその一例を示しています。
図 7. HTML5 でのクロスドメインの通信
図 7 で IFrame のロードが成功した場合に、IFrame がそのことを別の生成元の親ウィンドウに通知したいときには、IFrame は window.postMessage によってメッセージを送信します。それと同時に、IFrame はフィードバック・メッセージをモニタリングします (リスト 9)。
リスト 9. HTML5 の新しいメソッドによってメッセージを送信する
http://www.otherapp.com/index.html
function postMessage(msg){
var targetWindow = parent.window;
targetWindow.postMessage(msg,"*");
}
function handleReceive(msg){
var object = dojo.fromJson(msg);
if(object.status == “ok”){
//continue to do other things
……
}else{
//retry sending msg
……
}
}
window.addEventListener("message", handleReceive, false);
window.onLoad = function(){
postMessage("already loaded");
}
|
親文書はメッセージ・イベントをリッスンします。メッセージを受信すると、まずそのメッセージが www.otherapp.com から送信されたものかどうかがチェックされてから、受信確認メッセージが返されます。
リスト 10. HTML5 の新しいメソッドによってメッセージを受信する
http://www.myapp.com/index.html
function handleReceive(event){
if(event.origin != "http://www.otherapp.com")
return;
//process data
……
var otherAppFrame = document.getElementById(“otherApp”)
otherAppFrame.postMessage(“{status:’ok’}”,”http://www.otherapp.com”);
}
window.addEventListener("message", handleReceive, false);
|
リスト 10 のサンプル・コードは、Firefox 3 またはそれ以降、Internet Explorer 8、Google Chrome 2、Opera 9 またはそれ以降、Safari 4 で実行することができます。このコードにより、生成元が異なる文書間で通信を行うことが容易にできるようになります。また、皆さん自身の文書に他の文書からのメッセージを受信させたくない場合には、メッセージ・イベント・リスナーを追加してはならず、またすべてのメッセージを削除する必要があります。
学ぶために
- 「OpenAjax
Hub 2.0 Specification: Managed Hub Overview」(OpenAjax Alliance、2009年) を読み、OpenAjax Hub の概要を知り、また Managed Hubについて学んでください。
- 「OpenAjax
Hub 2.0 Specification: Managed Hub APIs」(OpenAjax Alliance、2009年) を読み、独自のクロスドメイン実装を作成する方法を学んでください。
- dojox.io.windowName を読み、Dojo で windowName を使用してクロスドメイン通信を行う方法を学んでください。
- 「JSONP によるクロスドメインの通信: 第 1
回 JSONP と jQuery を組み合わせ、強力なマッシュアップを迅速に作成する」(developerWorks、2009年2月) を読み、あまり知られていないクロスドメインの呼び出し手法 (JSONP) と柔軟な JavaScript ライブラリー (jQuery) とを組み合わせ、強力なマッシュアップを驚くほど迅速に作成する方法を理解してください。
- HTML
Living Standard の資料 (WHATWG.org、2011年6月) で Communication: Cross-document messagingの項目を読み、HTML5 のクロスドメイン・メッセージング標準について学んでください。
- developerWorks の Web development ゾーンには Web ベースのさまざまなソリューションを解説した記事が豊富に用意されています。
- developerWorks の
Technical events and webcasts で最新情報を入手してください。
- Twitter で developerWorks をフォローしてください。
製品や技術を入手するために
- Dojo ツールキットのサイトから Dojo (Dojo
1.6) をダウンロードしてください。
- OpenAjax
Hub ライブラリーから OpenAjax Hub を入手してください。
- IBM ソフトウェアを無料で試してみてください。試用版をダウンロードする方法、オンラインの試用版にログインする方法、サンドボックス環境で製品を操作する方法、あるいはクラウド上で製品にアクセスする方法があります。100 種類を超える IBM 製品の試用版から自由に選択することができます。
議論するために
- 今すぐ developerWorks
プロフィールを作成し、Ajax に関するウォッチ・リストを設定してください。developerWorks
コミュニティーとずっとつながっていられます。
- Web
開発に関心を持つ他の developerWorks メンバーを見つけてください。
- Web
の話題に焦点を絞った developerWorks のグループの 1 つに参加し、皆さんの知識を共有してください。
- Roland Barcia が彼のブログの中で Web
2.0 とミドルウェアについて語っています。
- developerWorks のメンバーが Web
のトピックに関して共有するブックマークを調べてみてください。
- 即座に答えを得るために、Web 2.0 Apps
フォーラムを訪れてください。
- 即座に答えを得るために、Ajax フォーラムを訪れてください。