ブラウザがクロスオリジンサーバーに対して XMLHttpRequest
リクエストを行うことができるため、AJAX は同じオリジンからのみ使用できるという制限が克服されます。
この記事では、CORS の内部メカニズムを詳しく紹介します。
(写真の説明: アラブ首長国連邦、アル・アインのオアシスパークで撮影)
1. はじめに
CORS にはブラウザとサーバーの両方のサポートが必要です。現在、すべてのブラウザがこの機能をサポートしており、IE ブラウザは IE10 よりも古いものにすることはできません。
CORS 通信プロセス全体はブラウザによって自動的に完了し、ユーザーの参加は必要ありません。開発者にとって、同じソースからの CORS 通信と AJAX 通信に違いはなく、コードはまったく同じです。ブラウザーが AJAX リクエストがクロスオリジンであることを検出すると、追加のヘッダー情報が自動的に追加され、追加のリクエストが行われることもありますが、ユーザーはそれを感じません。
したがって、CORS 通信を実現するための鍵はサーバーです。サーバーが CORS インターフェイスを実装している限り、クロスオリジン通信が可能です。
2. 2 種類のリクエスト
ブラウザは、CORS リクエストを 2 つのカテゴリ (単純なリクエストとそれほど単純ではないリクエスト) に分類します。
以下の2つの条件を同時に満たしていれば簡単なリクエストです。
(1)リクエスト方法は以下の3つの方法のいずれかです。
ヘッドポスト
(2) HTTP ヘッダー情報は次のフィールドを超えません:
AcceptAccept-LanguageContent-LanguageLast-Event-IDContent-Type: 3 つの値に制限application/x-www-form-urlencoded
、multipart/form-data
、text/plain
上記 2 つの条件を同時に満たさないリクエストは、非単純リクエストとみなされます。
ブラウザはこれら 2 つのリクエストを異なる方法で処理します。
3. 簡単なリクエスト 3.1 基本的なプロセス
単純なリクエストの場合、ブラウザは CORS リクエストを直接発行します。具体的にはヘッダ情報にOrigin
フィールドを追加することです。
以下は例です。ブラウザは、このクロスオリジン AJAX リクエストが単純なリクエストであることを認識し、ヘッダー情報に Origin
フィールドを自動的に追加します。
GET /cors HTTP/1.1Origin: http://api.bob.comHost: api.alice.comAccept-Language: en-USConnection: keep-aliveUser-Agent: Mozilla/5.0...
上記のヘッダー情報の Origin
フィールドは、このリクエストの送信元 (プロトコル + ドメイン名 + ポート) を示すために使用されます。サーバーは、この値に基づいてリクエストに同意するかどうかを決定します。
Origin
で指定されたソースが許可範囲内にない場合、サーバーは通常の HTTP 応答を返します。ブラウザは、この応答のヘッダー情報に Access-Control-Allow-Origin
フィールドが含まれていないことを検出すると (詳細は以下を参照)、何か問題が発生したことを認識してエラーをスローします。これは、XMLHttpRequest コールバック関数によってキャッチされます。 >。 HTTP 応答のステータス コードが 200 である可能性があるため、このエラーはステータス コードでは特定できないことに注意してください。 onerror
で指定されたドメイン名が許可の範囲内にある場合、サーバーから返される応答にはさらにいくつかのヘッダー情報フィールドが含まれます。 Origin
Access-Control-Allow-Origin: http://api.bob.comAccess-Control-Allow-Credentials: trueAccess-Control-Expose-Headers: FooBarContent-Type: text/html; charset=utf-8
上記のヘッダー情報の中には、CORS リクエストに関連する 3 つのフィールドがあり、すべて
で始まります。 Access-Control-
このフィールドは必須です。その値は、リクエスト中の
フィールドの値、または任意のドメイン名からのリクエストが受け入れられることを示す Origin
のいずれかです。 *
このフィールドはオプションです。その値は、Cookie の送信を許可するかどうかを示すブール値です。デフォルトでは、Cookie は CORS リクエストに含まれません。
に設定すると、サーバーがリクエストに Cookie を含めてサーバーに送信することを明示的に許可します。この値は true
にのみ設定できます。サーバーがブラウザーに Cookie を送信させたくない場合は、このフィールドを削除してください。 true
このフィールドはオプションです。 CORS リクエストを行う場合、
オブジェクトの XMLHttpRequest
メソッドは、getResponseHeader()
、Cache-Control
、Content-Language
、Content-Type
、Expires
、Last-Modified
の 6 つの基本フィールドのみを取得できます。他のフィールドを取得したい場合は、Pragma
で指定する必要があります。上の例では、Access-Control-Expose-Headers
が getResponseHeader('FooBar')
フィールドの値を返すことができることを指定しています。 FooBar
3.2 withCredentials 属性
上で述べたように、CORS リクエストはデフォルトでは Cookie と HTTP 認証情報を送信しません。サーバーに Cookie を送信する場合は、サーバーの同意が必要で、 フィールドを指定します。 Access-Control-Allow-Credentials
<code>Access-Control-Allow-Credentials: true</code>
一方、開発者は AJAX リクエストの
属性をオンにする必要があります。 withCredentials
<code>var xhr = new XMLHttpRequest();xhr.withCredentials = true;</code>
否则,即使服务器同意发送Cookie,浏览器也不会发送。或者,服务器要求设置Cookie,浏览器也不会处理。
但是,如果省略withCredentials
设置,有的浏览器还是会一起发送Cookie。这时,可以显式关闭withCredentials
。
<code>xhr.withCredentials = false;</code>
需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin
就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie
也无法读取服务器域名下的Cookie。
四、非简单请求4.1 预检请求
非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT
或DELETE
,或者Content-Type
字段的类型是application/json
。
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest
请求,否则就报错。
下面是一段浏览器的JavaScript脚本。
var url = 'http://api.alice.com/cors';var xhr = new XMLHttpRequest();xhr.open('PUT', url, true);xhr.setRequestHeader('X-Custom-Header', 'value');xhr.send();
上面代码中,HTTP请求的方法是PUT
,并且发送一个自定义头信息X-Custom-Header
。
浏览器发现,这是一个非简单请求,就自动发出一个"预检"请求,要求服务器确认可以这样请求。下面是这个"预检"请求的HTTP头信息。
<code>OPTIONS /cors HTTP/1.1Origin: http://api.bob.comAccess-Control-Request-Method: PUTAccess-Control-Request-Headers: X-Custom-HeaderHost: api.alice.comAccept-Language: en-USConnection: keep-aliveUser-Agent: Mozilla/5.0...</code>
"预检"请求用的请求方法是OPTIONS
,表示这个请求是用来询问的。头信息里面,关键字段是Origin
,表示请求来自哪个源。
除了Origin
字段,"预检"请求的头信息包括两个特殊字段。
(1)Access-Control-Request-Method
该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT
。
(2)Access-Control-Request-Headers
该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header
。
4.2 预检请求的回应
服务器收到"预检"请求以后,检查了Origin
、Access-Control-Request-Method
和Access-Control-Request-Headers
字段以后,确认允许跨源请求,就可以做出回应。
HTTP/1.1 200 OKDate: Mon, 01 Dec 2008 01:15:39 GMTServer: Apache/2.0.61 (Unix)Access-Control-Allow-Origin: http://api.bob.comAccess-Control-Allow-Methods: GET, POST, PUTAccess-Control-Allow-Headers: X-Custom-HeaderContent-Type: text/html; charset=utf-8Content-Encoding: gzipContent-Length: 0Keep-Alive: timeout=2, max=100Connection: Keep-AliveContent-Type: text/plain
上面的HTTP回应中,关键的是Access-Control-Allow-Origin
字段,表示<a href="http://api.bob.com">http://api.bob.com</a>
可以请求数据。该字段也可以设为星号,表示同意任意跨源请求。
Access-Control-Allow-Origin: *
如果浏览器否定了"预检"请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest
对象的onerror
回调函数捕获。控制台会打印出如下的报错信息。
XMLHttpRequest cannot load http://api.alice.com.Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.
服务器回应的其他CORS相关字段如下。
<code>Access-Control-Allow-Methods: GET, POST, PUTAccess-Control-Allow-Headers: X-Custom-HeaderAccess-Control-Allow-Credentials: trueAccess-Control-Max-Age: 1728000</code>
(1)Access-Control-Allow-Methods
该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。
(2)Access-Control-Allow-Headers
如果浏览器请求包括Access-Control-Request-Headers
字段,则Access-Control-Allow-Headers
字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。
(3)Access-Control-Allow-Credentials
该字段与简单请求时的含义相同。
(4)Access-Control-Max-Age
このフィールドはオプションであり、このプリフライトリクエストの有効期間を秒単位で指定するために使用されます。上記の結果では、有効期間は 20 日 (1728000 秒) です。これは、応答を 1728000 秒 (20 日間) キャッシュできることを意味します。この期間中は、別のプリフライト リクエストを発行する必要はありません。
4.3 ブラウザの通常のリクエストとレスポンス
サーバーが「プリフライト」リクエストを渡すと、ブラウザからの通常の CORS リクエストはすべて単純なリクエストと同じになり、Origin
ヘッダー情報フィールドが存在します。サーバーの応答には Access-Control-Allow-Origin
ヘッダー情報フィールドもあります。
以下は、「プリフライト」リクエスト後のブラウザの通常の CORS リクエストです。
PUT /cors HTTP/1.1Origin: http://api.bob.comHost: api.alice.comX-Custom-Header: valueAccept-Language: en-USConnection: keep-aliveUser-Agent: Mozilla/5.0...
上記のヘッダー情報の Origin
フィールドは、ブラウザによって自動的に追加されます。
以下はサーバーからの通常の応答です。
Access-Control-Allow-Origin: http://api.bob.comContent-Type: text/html; charset=utf-8
上記のヘッダー情報では、Access-Control-Allow-Origin
フィールドをすべての応答に含める必要があります。
5. JSONPとの比較
CORS は JSONP と同じ目的で使用されますが、JSONP よりも強力です。
JSONP は GET
リクエストのみをサポートしますが、CORS はすべての種類の HTTP リクエストをサポートします。 JSONP の利点は、古いブラウザをサポートし、CORS をサポートしていない Web サイトからデータを要求できることです。
(終了)