It allows the browser to make XMLHttpRequest
requests to cross-origin servers, thereby overcoming the limitation that AJAX can only be used from the same origin.
This article introduces the internal mechanism of CORS in detail.
(Picture description: Taken at Oasis Park in Al Ain, United Arab Emirates)
1. Introduction
CORS requires both browser and server support. Currently, all browsers support this function, and IE browser cannot be lower than IE10.
The entire CORS communication process is automatically completed by the browser and does not require user participation. For developers, there is no difference between CORS communication and AJAX communication from the same source, and the code is exactly the same. Once the browser discovers that the AJAX request is cross-origin, it will automatically add some additional header information, and sometimes an additional request will be made, but the user will not feel it.
Therefore, the key to achieving CORS communication is the server. As long as the server implements the CORS interface, cross-origin communication is possible.
2. Two kinds of requests
Browsers divide CORS requests into two categories: simple requests and not-so-simple requests.
As long as the following two conditions are met at the same time, it is a simple request.
(1) The request method is one of the following three methods:
HEADGETPOST
(2) HTTP header information does not exceed the following fields:
AcceptAccept-LanguageContent-LanguageLast-Event-IDContent-Type: limited to three values application/x-www-form-urlencoded
, multipart/form-data
, text/plain
Any request that does not meet the above two conditions at the same time is a non-simple request.
Browsers handle these two requests differently.
3. Simple request 3.1 Basic process
For simple requests, the browser issues CORS requests directly. Specifically, it is to add a Origin
field to the header information.
The following is an example. The browser finds that this cross-origin AJAX request is a simple request, and automatically adds a Origin
field to the header information.
GET /cors HTTP/1.1Origin: http://api.bob.comHost: api.alice.comAccept-Language: en-USConnection: keep-aliveUser-Agent: Mozilla/5.0...
In the above header information, the Origin
field is used to indicate which source this request comes from (protocol + domain name + port). The server decides whether to agree to the request based on this value.
If the source specified by Origin
is not within the permission range, the server will return a normal HTTP response. When the browser discovers that the header information of this response does not contain the Access-Control-Allow-Origin
field (see below for details), it knows that something went wrong and throws an error, which is caught by the XMLHttpRequest
callback function of onerror
. Note that this error cannot be identified by the status code, because the HTTP response status code may be 200.
If the domain name specified by Origin
is within the permission range, the response returned by the server will have several more header information fields.
Access-Control-Allow-Origin: http://api.bob.comAccess-Control-Allow-Credentials: trueAccess-Control-Expose-Headers: FooBarContent-Type: text/html; charset=utf-8
Among the header information above, there are three fields related to CORS requests, all starting with Access-Control-
.
(1) Access-Control-Allow-Origin
This field is required. Its value is either the value of the Origin
field during the request, or a *
, indicating that requests from any domain name are accepted.
(2) Access-Control-Allow-Credentials
This field is optional. Its value is a Boolean value indicating whether to allow sending cookies. By default, cookies are not included in CORS requests. Set to true
, which means that the server explicitly allows the cookie to be included in the request and sent to the server. This value can only be set to true
. If the server does not want the browser to send cookies, just delete this field.
(3) Access-Control-Expose-Headers
This field is optional. When making a CORS request, the XMLHttpRequest
method of the getResponseHeader()
object can only get 6 basic fields: Cache-Control
, Content-Language
, Content-Type
, Expires
, Last-Modified
, Pragma
. If you want to get other fields, you must specify them in Access-Control-Expose-Headers
. The above example specifies that getResponseHeader('FooBar')
can return the value of the FooBar
field.
3.2 withCredentials attribute
As mentioned above, CORS requests do not send Cookie and HTTP authentication information by default. If you want to send cookies to the server, you need the server's consent and specify the Access-Control-Allow-Credentials
field.
<code>Access-Control-Allow-Credentials: true</code>
On the other hand, developers must turn on the withCredentials
attribute in AJAX requests.
<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
This field is optional and is used to specify the validity period of this preflight request, in seconds. In the above result, the validity period is 20 days (1728000 seconds), which means the response is allowed to be cached for 1728000 seconds (20 days). During this period, there is no need to issue another preflight request.
4.3 Normal request and response of browser
Once the server passes the "preflight" request, every normal CORS request from the browser will be the same as a simple request, and there will be a Origin
header information field. The server's response will also have a Access-Control-Allow-Origin
header information field.
The following is the browser's normal CORS request after the "preflight" request.
PUT /cors HTTP/1.1Origin: http://api.bob.comHost: api.alice.comX-Custom-Header: valueAccept-Language: en-USConnection: keep-aliveUser-Agent: Mozilla/5.0...
The Origin
field in the header information above is automatically added by the browser.
The following is a normal response from the server.
Access-Control-Allow-Origin: http://api.bob.comContent-Type: text/html; charset=utf-8
In the header information above, the Access-Control-Allow-Origin
field must be included in every response.
5. Comparison with JSONP
CORS is used for the same purpose as JSONP, but is more powerful than JSONP.
JSONP only supports GET
requests, CORS supports all types of HTTP requests. The advantage of JSONP is that it supports older browsers and can request data from websites that do not support CORS.
(End)