CORSリクエストについてのまとめ

http://www.html5rocks.com/en/tutorials/cors/#disqus_thread

Author
Monsur Hossain

ほとんどこちらの要約

導入

aaaa.comにはbbbb.comがアクセスしたいデータがあるとしよう。このクロスドメインリクエストは従来same-origin -policy によって禁止されている。解決法としては次の3つ。

JSONPはセキュリティ面に問題があり、カスタムプロキシはセットアップと保守が面倒。ここでは、W3Cの仕様であるCORSを選択する。

CROSリクエストを行うことで、レスポンスに特別なヘッダが付与され、クロスドメインのデータにアクセスすることができるようになる。

CORSリクエストの作成

以下JavaScriptでクロスドメインリクエストを作成する。

対応しているブラウザはこちら Chrome, Firefox, Opera, SafariはすべてXMLHttpRequest2 objectを使う。

イベントハンドラ

XMLHttpRequestには幾つかイベントハンドラが用意されているが、ほとんどの場合onloadonerrorイベントが最低限必要となる。

xhr.onload = function() {
 var responseText = xhr.responseText;
 console.log(responseText);
 // レスポンスの処理
};

xhr.onerror = function() {
  console.log('There was an error!');
};

withCredential

リクエストにクッキーを付与するため.withCredentialsプロパティをtrueにする。

xhr.withCredentials = true:

これが機能するために、サーバーもAccess-Control-Allow-Credentialsレスポンスヘッダを"true"にして、credentialsを可能にする必要がある。

Access-Control-Allow-Credentials: true

リクエストの作成

send()メソッドを呼び出す。リクエストがbodyを持っているなら、引数に取ることができる。

xhr.send();

シンプルリクエスト

JavaScript:

var url = 'http://api.aaaa.com/cors';
var xhr = createCORSRequest('GET', url);
xhr.send();

HTTP Request:

GET /cors HTTP/1.1
Origin: http://api.bbbb.com
Host: api.aaaa.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

有効なCORSリクエストにはOrigin headerが含まれる。ブラウザによって追加され、ユーザーは操作することはできない。

HTTP Response:

Access-Control-Allow-Origin: http://api.bbbb.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8

Access-Control-Allow-Origin (必須) - オリジンリクエストヘッダか、*をとりあらゆるサイトからのアクセスを許可する。

Access-Control-Allow-Credentials (オプション) - クッキーを扱えるようにする。これをtrueにして機能させるには、同時にXMLHttpRequestオブジェクトのwithCredentialsプロパティをtrueにしなければならない。

Access-Control-Expose-Headers (オプション) - XMLHttpRequest 2 オブジェクトは以下のレスポンスヘッダの値を返すgetResponseHeaders()メソッドを持つ。

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

その他のヘッダにクライアントからのアクセスを許可するには、Access-Control-Expose-Headersを用いる。

PUT, DELETE, JSON content

ブラウザはまずpreflightリクエストを行い、サーバーに対し実際のリクエストの許可をもらう。許可が下りて初めて、ブラウザは実際のリクエストを行う。

JavaScript:

var url = 'http://api.aaaa.com/cors';
var xhr = createCORSRequest('PUT', url);
xhr.setRequestHeader(
    'X-Custom-Header', 'value');
xhr.send();

Preflight Request:

OPTIONS /cors HTTP/1.1
Origin: http://api.bbbb.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.aaaa.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

Access-Control-Request-Method - 実際のリクエストのHTTPメソッド

Access-Control-Request-Headers - コンマで区切られた、リクエストに含まれるヘッダのリスト。

HTTPメソッドとヘッダが有効なら、サーバーは次のようなレスポンスを返す。

Preflight Response:

Access-Control-Allow-Origin: http://api.bbbb.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8

Access-Control-Allow-Origin (必須) - シンプルレスポンスと同じく、preflightレスポンスもこのヘッダを含む。

Access-Control-Allow-Methods (必須) - サポートされるすべてのHTTPメソッド

Access-Control-Allow-Headers (リクエストにAccess-Control-Request-Headers headerが含まれる場合、必須) - サポートされるすべてのリクエストヘッダ。

Access-Control-Allow-Credentials (オプション) - シンプルリクエストと同じ。

Access-Control-Max-Age (オプション) - リクエストの度にpreflightリクエストを行うと重くなる。preflightレスポンスは、このヘッダに指定された秒数キャッシュされる。

Actual Request:

PUT /cors HTTP/1.1
Origin: http://api.bbbb.com
Host: api.aaaa.com
X-Custom-Header: value
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

Actual Response:

Access-Control-Allow-Origin: http://api.bbbb.com
Content-Type: text/html; charset=utf-8

サーバーがCORSリクエストを拒否する場合、CORSヘッダを含まないレスポンスが返ってくる。レスポンスにCORSヘッダがないために、ブラウザはリクエストは無効と判断し、実際のリクエストを行わない。

Preflight Request:

OPTIONS /cors HTTP/1.1
Origin: http://api.bbbb.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.aaaa.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

Preflight Response:

// ERROR - 無効なリクエスト
Content-Type: text/html; charset=utf-8

jQuery

$.ajaxメソッドを用いると、XHRとCORSリクエストの両方を使用することができる。

$.ajax({

  //  HTTP メソッド.
  // 'PUT'あるいは'DELETE'で、preflightリクエストをトリガー.
  type: 'GET',

  // リクエストの宛先URL
  url: 'http://api/aaaabbbb.com',

  // デフォルトの'application/x-www-form-urlencoded; charset=UTF-8'
  // では、preflightをトリガーしない。
  // application/x-www-form-urlencoded, multipart/form-data, text/plain
  // 以外のタイプを設定すると、preflightをトリガーする。

  xhrFields: {
    // XMLHttpリクエストについての追加の情報
    withCredentials: false
  },

  headers: {

  },

  success: function() {
    // 成功時のレスポンスの扱いを記述
  },

  error: function() {
    // 失敗時のレスポンスエラーの扱いを記述
  }
});