先日、Ajaxでのクロスドメインリクエストの解決策について質問され、JSONPというものがあることは知っていましたが、その具体的な実装方法については明確に説明できませんでした。この問題を解決するため、多くの資料を調査し、JSONPの仕組みを理解しました。
なぜJSONPについて長い間漠然としたままでしたか?——それはインターネット上の解説資料が複雑すぎるからです!
可能な限りシンプルに、10分程度で理解できるように解説します。
- 同一オリジンポリシー =======
Ajaxが「クロスドメイン」を必要とするのは、ブラウザの同一オリジンポリシーが原因です。つまり、あるページのAjaxは、そのページと同じオリジン(ドメイン)のデータしか取得できません。
「同一オリジン」とは何でしょうか?——プロトコル、ドメイン、ポート番号がすべて同じである必要があります。例えば:
http://example.com と https://example.com はプロトコルが異なるため異なるオリジンです; http://localhost:8080 と http://localhost:1000 はポート番号が異なるため異なるオリジンです; http://localhost:8080 と https://example.com はプロトコル、ドメイン、ポート番号がすべて異なるため、全く別のオリジンです。
同一オリジンポリシーにより、自分が作成した http://localhost:8080/test.html ページは、Ajaxを直接使って http://google.com のデータを取得することはできません。
例えば、Ajaxで異なるドメインのページにアクセスすると、以下のようなエラーが発生します:
考えてみれば、これは理にかなっています。同一オリジンポリシーがなければ、誰でもAjaxを使って他のサイトの情報を自由に取得でき、インターネットは混乱するでしょう。自分の作成した検索インターフェースからAjaxで百度のデータを直接取得するようなことができれば、それはまるで盗みです。
しかし、クロスドメインアクセスは不可欠です。例えば、mail.example.comのウェブページがnews.example.comドメインからニュース情報を取得する必要がある場合、どうすればよいのでしょうか?——私たちのクロスドメインの旅を始めましょう。(もちろんiframeでも実現可能です)
- 「リンク盗用」から始める ==========
インターネット上の多くのサイト間で画像が相互にリンク盗用されることがあります。Aサイトのウェブページのimg.srcが直接Bサイトの画像アドレスを指すのは、よくあることです。「リンク盗用」と聞いて、多くの人がまずリンク盗用を防ぐ方法を思い浮かべるでしょうが、今日はその話はしません。
「リンク盗用」と「同一オリジンポリシー」という2つの言葉の間に何か関係があると考えてみてください。——はい、矛盾です!「同一オリジンポリシー」があるのになぜ「リンク盗用」が可能なのでしょうか?
世界のすべてには矛盾があり、矛盾があっても共存は可能です。必ずしもあなた死ね私生きという関係ではありません。
重要なポイント:<img>のsrc(画像取得)、<link>のhref(CSS取得)、<script>のsrc(JavaScript取得)のこの3つは同一オリジンポリシーに従わず、クロスドメインでデータを取得できます。そのため、CDNからjQueryを直接取得でき、自分のサイトの画像もいつでも他人に盗用される可能性があります。そのため、透かしを入れるのが最善です!
そして今日の主人公であるJSONPは、<script>のsrcが同一オリジンポリシーに従わないことから生まれました。
- JSONP ========
例えば、ドメインa.comにはa.com/test.htmlページがあり、ドメインb.comにはb.com/data.htmlページとb.com/alert.jsファイルがあるとします。
ステップ1:簡単なJSの参照
b.com/alert.jsを以下のように作成します:
alert(123);
a.com/test.htmlを以下のように作成します:
<script type='text/javascript' src='http://b.com/alert.js'/>
a.com/test.htmlを実行すると、結果は明らかに【123】というアラートです。
ステップ2:JS参照によるデータ取得
b.com/alert.jsを以下のように変更します:
myFn(100);
a.com/test.htmlを以下のように変更します:
<script>
function myFn ( data ) {
alert( data + 'px' );
}
</script>
<script type='text/javascript' src='http://b.com/alert.js'/>
a.com/test.htmlを実行すると、【 100px 】というアラートが表示されます。これも疑問はないでしょう。
ステップ3:クロスドメインの成功!
ステップ2で、dataつまり100が、b.comでクロスドメインとして取得したいデータであれば、私たちはすでにクロスドメインリクエストを実現していることになります!
このプロセスをもう一度明確に整理しましょう:
- <script>のsrcは同一オリジンポリシーに従いません;
- 跨域のファイルのURLを<script>のsrcに設定します(必ずしもjsファイルとは限りません)、このファイルが返す文字列はブラウザによってJavaScriptとして解析されます;
- このJavaScriptには、私たちが必要とするクロスドメインのサーバー側データが含まれています;
- 最後に、私たちはデータを表示するためにmyFn関数をページ上で定義し、このJavaScript内でmyFn関数を直接呼び出します;
ステップ4:HTML形式の参照
<script>のsrcは単にJavaScriptファイルを指すだけでなく、任意のアドレスを指すことができます。例えば:
a.com/test.htmlを以下のように変更します:
<script>
function myFn ( data ) {
alert( data + 'px' );
}
</script>
<script type='text/javascript' src='http://b.com/data.html'/>
b.com/data.htmlを以下のように作成します:(注意、data.htmlには以下のコード一行だけを記述します)
myFn(100);
a.com/test.htmlを実行しても、結果は依然として【 100px 】です。
ここで「100」が私たちがクロスドメインで取得したいデータです。
ステップ5:動的データ
取得したいデータが動的な場合は、動的ページで記述する必要があります。
では、a.com/test.htmlが動的なaspxページを呼び出すようにしましょう:
<script>
function myFn ( data ) {
alert( data + 'px' );
}
</script>
<script type='text/javascript' src='http://b.com/data.aspx?callback=myFn'/>
注意点として、srcアドレスに「?callback=myFn」を追加しました。これはデータを表示する関数も動的に渡すことを意味し、ステップ2とステップ4は呼び出されたファイルに静的に記述されていました。
callbackパラメータをサーバー側でどのように受信し、使用するかについては、以下のように実装します:
b.comにb.com/data.aspxページを追加し、サーバーサイドコードを以下のように記述します:
protected void Page_Load(object sender, EventArgs e)
{
if (this.IsPostBack == false)
{
string callback = "";
if (Request["callback"] != null)
{
callback = Request["callback"];
// サーバー側で返すデータ
string data = "1024";
Response.Write(callback + "(" + data + ")");
}
}
}
コードは非常にシンプルで、callbackパラメータを取得し、関数の形式で返します。「b.com/data.aspx?callback=myFn」が呼び出された場合、返されるのは" myFn(1024) "です。
返されるデータが動的になりました(「1024」)、フロントエンドページでデータを表示する関数も動的になりました(「callback=myFn」)、しかし形式は基本的に同じです。
ステップ6:呼び出しのカプセル化
a.com/test.htmlに、静的に配置されている<script>タグが一つだけあり、一度実行されたらそれっきりです。
実際の状況では、a.com/test.htmlではユーザーの操作に応じて複数回の呼び出しが発生する可能性があります。どうすればよいのでしょうか?——動的に追加すればよいのです。
function addScriptTag(src) {
var script = document.createElement("script");
script.setAttribute("type", "text/javascript");
script.src = src;
document.body.appendChild(script);
}
function myFn (data) {
alert(data + 'px');
}
// 呼び出す必要がある場合:
//addScriptTag('b.com/data.aspx?callback=myFn');
- まとめ =====
以上の説明がJSONPのすべてです。定義を暗記する必要はなく、上記の内容を理解すればすべて理解できます。
**重要なポイントは:**同一オリジンポリシー + <script>のsrcが同一オリジンポリシーに属さない + <script>のsrcが指すファイルからサーバー側のデータを返すことです。
これで終わりです!