先の記事「WCF と jQuery AJAX」で書いた ASP.NET がホストする WCF サービスを、クロスドメインで利用する方法について書きます。

Tsuyoshi Matsuzaki さん Blog の記事「JavaScript のクロス ドメイン (Cross-Domain) 問題の回避と諸注意」では以下の 3 つの方法が紹介されています。それぞれの概要が説明されていて分かりやすいので一読されるといいと思います。
-
Cross Document Messaging (XDM)
-
Cross-Origin Resource Sharing (CORS)
-
JSONP
ここでは 2 番目の Cross-Origin Resource Sharing (CORS) という方法を使います。上の画像は CORS でのプリフライトリクエストとそれに対する応答を Fiddler で見たものです。
(最初は jQuery.ajax を利用した JSONP が一番簡単かと思ったのですが、jQuery.ajax では応答のスクリプトにコールバック jQuery1830...26( ... ) で囲んだ JSON 文字列を期待しているのに、WCF サービスは JSON 文字列しか返さないので使えませんでした)
CORS は HTML 5 の仕様なので依然として使えないブラウザがありますが、対応ブラウザが増えるにつれ今後の本命になるだろうということです。(ちなみに、IE9 は対応してませんでした。上に紹介した MSDN Blog の記事によると独自の XDomainRequest オブジェクトを使用するという回避方法はあるそうですが)
まず、この記事を書くに当たって自分が参考にさせていただいた記事へのリンクを張っておきます。私の説明では分からない場合はそちらを読まれるといいと思います。手抜きでスミマセン。(汗)
-
オリジン間リソース共有 (CORS)
-
CORS(Cross-Origin Resource Sharing)について整理してみた
ブラウザが CORS をサポートしていれば、クライアント側の処理はブラウザが自動的に行いますので、開発者はクライアント側の対応は何もする必要がないです。
サーバー側すなわち ASP.NET Web アプリで CORS に必要な応答ヘッダ返せるようにするだけで対応できます。基本的には Global.asax ファイルに以下のコードを記述してやれば可能になります。(厳格なセキュリティを要求せず無条件で応答するというような場合はもっと簡単になります)
protected void Application_BeginRequest(object sender,
EventArgs e)
{
CrossOriginResourceSharingSetting();
}
private void CrossOriginResourceSharingSetting()
{
HttpRequest request = HttpContext.Current.Request;
HttpResponse response = HttpContext.Current.Response;
// プリフライトリクエストの場合
if (request.HttpMethod == "OPTIONS")
{
// 要求ヘッダの origin フィールドを取得
string origin = request.Headers["origin"];
// ここで origin に指定されたドメインのチェックを行った
// 方がいいかもしれないが省略。要求ヘッダの origin フィ
// ールドで送信されてきたものをそのまま返す
// 要求ヘッダに origin フィールドがなければ何もしない
if (!string.IsNullOrEmpty(origin))
{
string method =
request.Headers["Access-Control-Request-Method"];
string header =
request.Headers["Access-Control-Request-Headers"];
// 要求ヘッダに Access-Control-Request-Method および
// Access-Control-Request-Headers がなければ何もしない
if (!string.IsNullOrEmpty(method) &&
!string.IsNullOrEmpty(header))
{
// method が GET または POST 以外は対応しない
// header もチェックした方がいいかもしれない・・・
if (method.Contains("GET") || method.Contains("POST"))
{
response.AddHeader("Access-Control-Allow-Origin",
origin);
response.AddHeader("Access-Control-Allow-Methods",
method);
response.AddHeader("Access-Control-Allow-Headers",
header);
// Access-Control-Max-Age ヘッダも任意で追加できる。
// 指定した時間(秒)プリフライトの結果をキャッシュで
// きるので、実際に運用に移す際は追加する方がよさそう
//response.AddHeader("Access-Control-Max-Age", "600");
response.End();
}
}
}
}
else
{
// 要求ヘッダの origin フィールドを取得
string origin = request.Headers["origin"];
// ここで origin に指定されたドメインのチェックを行った方
// がいいかもしれないが省略。要求ヘッダの origin フィール
// ドで送信されてきたものをそのまま返す
// 要求ヘッダに origin フィールドがなければ何もしない
if (!string.IsNullOrEmpty(origin))
{
response.AddHeader("Access-Control-Allow-Origin",
origin);
}
}
}
上記のコードが何をしているかと言うと、要求が「プリフライトリクエスト」になる場合と「シンプルなリクエスト」になる場合とに処置を分けて、CORS に必要な応答ヘッダを設定しているだけです。詳しくはコード中のコメントに書きましたのでそれを見てください。
CORS をサポートしているブラウザがクロスドメインでどのような処置を行うかは、要求が「シンプルなリクエスト」になるか否かによって異なってきます。要求が「シンプルなリクエスト」になる条件は、上に紹介した記事「HTTP アクセス制御 (CORS)」を読んでください。
例えば、先の記事「WCF と jQuery AJAX」で言うと「(3) WCF AJAX サービスへのアクセス」のコードで getAllCars メソッドを使う方が「シンプルなリクエスト」になります。
getCarsByDoors メソッドを使う方は要求ヘッダに contentType: "application/json; charset=utf-8" が含まれるので「シンプルなリクエスト」にはならず、「プリフライトリクエスト」が事前に行われます。
(1) シンプルなリクエスト
要求が「シンプルなリクエスト」になる場合、CORS をサポートしているブラウザからは要求ヘッダに origin: http://<要求元のドメイン> を追加する以外には普通にサーバーに要求を出します。(ただし、IE9 では要求さえ出ませんので注意)
サーバー側では要求ヘッダに含まれる origin フィールドを見て、
要求元のドメインがクロスドメインアクセスを許可されているか判定し、受け入れの判断をすることができます。
ただし、上記コードは origin フィールドの内容の検証はしていません。origin フィールドの内容(要求元のドメイン)をそのまま応答ヘッダの Access-Control-Allow-Origin に設定して返しています。それだけでも、* を設定するよりはよさそうです。
ブラウザは応答ヘッダの Access-Control-Allow-Origin: http://<求元のドメイン> を見てクロスドメインアクセスに成功したと判断し、応答に含まれる JSON 文字列などのコンテンツを処置します。
(2) プリフライトリクエスト
要求が「シンプルなリクエスト」にならない場合、CORS をサポートしているブラウザからは実際のリクエストを送信しても安全かを確かめるための「プリフライトリクエスト」が事前に行われます。
上の画像はその要求と応答を Fiddler で見たものです。
左下のウィンドウはブラウザがクロスドメインに対する要求でかつ「シンプルなリクエスト」にならないと判定して自動的にプリフライトリクエストを要求した要求ヘッダの内容です。
赤枠で囲った部分を見てください。HTTP メソッドの OPTIONS、要求ヘッダに含まれる origin, Access-Control-Request-Method, Access-Control-Request-Headers に注目です。
サーバーがクロスドメインからのリクエストを受け、それがプリフライトリクエストであるかどうかを判断するにはそれらの情報をチェックします。
そして、プリフライトリクエストであると判断した場合はは上の画像の右下のウィンドウの赤枠で囲ったような応答ヘッダを返します。ここでは、Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers は要求ヘッダで受けたものと同じ内容に設定しています。
ブラウザはそれらの情報を見て実際のリクエストを送信しても安全かを確認し、サーバーに要求を出して(上の画像で #3 がそれ)応答を取得し、コンテンツに含まれる JSON 文字列などの情報を処置します。