本文作者:IMWeb 何璇 原文出处:IMWeb社区 未经同意,禁止转载
页面中常常会有需要跨域通信的需求实现,我们知道浏览器的同源策略是不允许不同域之间的相互通信的(这里不深究域的定义及如何才算跨域),比如a.com有b.com想要的数据,那么在b.com页面中发送ajax请求到a.com是不允许的,相信大家都知道一些跨域通信的实现方法:
跨域资源共享(Cross-Origin Resource Sharing)是W3C的一项规定,它规定了在浏览器中,基于XMLHttpRequest对象的跨域请求通信的原理,基本上保持了原有对象的用法。
CORS需要服务器端及客户端双方面的更改支持。本文主要介绍如何发起一个跨域请求和如何在服务器端支持CORS。
兼容性:
function get_CORS_XHR(method, url) {
var xhr = new XMLHttpRequest();
if ("withCredentials" in xhr) {
// "withCredentials"属性只存在于XMLHttpRequest2对象中
// Chrome, Firefox, Opera and Safari
xhr.open(method, url, true);
} else if (typeof XDomainRequest != "undefined") {
// XDomainRequest对象,兼容IE
xhr = new XDomainRequest():
xhr.open(method, url);
} else {
xhr = null;
}
return xhr;
}
var xhr = get_CORS_XHR('GET', 'http://example.com');
XMLHttpRequest2对象新增了许多事件类型,原先的对象只支持onreadystatechange,新增事件有: (*星号代表IE不支持)
// 通常我们处理最多的就是
xhr.onload = function() {
var responseText = xhr.responseText;
console.log(responseText);
// ...
};
xhr.onerror = function() {...};
withCredentials
& 发送默认情况下,标准的CORS请求是不会发送任何cookie信息的。如果你想让请求带上对应域的cookies信息(需要server支持),那么你需要:
xhr.withCredentials = true;
// 响应报文头部加上 Access-Control-Allow-Credentials: true
// handlers ...
xhr.send();
可以给跨域请求分个类:
符合下列要求的请求可以说是简单请求:
- HTTP Method
- HEAD
- GET
- POST
- HTTP Headers
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
不符合(1)中的条件的请求
浏览器如Chrome, Firefox等会在不太简单的CORS请求发送前,为安全性考虑先发送一条”preflighted”OPTIONS
请求
服务器端的处理根据请求的复杂程度处理方式有所不同。
让我们举个栗子,CORS指定头部为粗体: Javascript:
var url = 'http://api.alice.com/cors';
var xhr = get_CORS_XHR('GET', url);
xhr.send();
HTTP 请求:
GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
值得注意的是CORS请求中必定包含Origin
头部,但是包含此头部不一定意味着这个请求就是CORS请求。一个成功的跨域请求的响应报文可以是:
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
所有CORS相关的头部均以Access-Control-
开头,下面是关于各个头部的细节:
Access-Control-Allow-Origin
(required) 此头部必须添加到响应报文中 ,不然缺省值会导致CORS请求失败。你可以设置*
值让所有站点都可以访问你的数据,但最好还是控制一下Access-Control-Allow-Credentials
(optional) 设置此头部的值为true,如果你想要请求附带cookies。与上文提到的withCredentials
属性协作。若此头部值为true而withCredentials属性为false,会导致请求失败,反之亦然Access-Control-Allow-Expose-Headers
(optional) XMLHttpRequest2对象存在getResponseHeader
方法,允许访问一些简单的响应头部如:Content-Type,Cache-Control等等。如果想暴露一些特殊的头部,可以在此头部的值设置以逗号分隔的头部名称如上文所说,处理不太简单的请求时,浏览器会先发出一次preflighted的请求,得到服务器允许后才执行真正的跨域请求,preflighted请求的结果会被缓存,多条请求同一服务器的跨域请求只会发送一次preflighted请求。举个栗子: Javascript:
var url = 'http://api.alice.com/cors';
var xhr = createCORSRequest('PUT', url);
xhr.setRequestHeader(
'X-Custom-Header', 'value');
xhr.send();
Preflight请求:
OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
Access-Control-Request-Method
真实的请求方法,在此为PUT。所有的Preflight请求都应该包含此头部Access-Control-Request-Headers
值是以逗号分隔的头部名称,代表请求附带的其余头部Preflight响应:
Access-Control-Allow-Origin: http://api.bob.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
(同上文)Access-Control-Allow-Credentials
(同上文)Access-Control-Allow-Methods
(required) 允许跨域请求的请求类型,以逗号分隔。由于preflight响应可能被缓存,所以此头部设置会有所帮助Access-Control-Allow-Headers
当请求中有Access-Control-Request-Headers头部时,此响应头说明服务器支持的头部,以逗号分隔Access-Control-Max-Age
(required) 指定preflight响应可以被缓存的时间,单位秒真实的请求跟响应就可以正常发送接收了。如果服务器对preflight请求直接返回HTTP 200,不包含任何CORS指定的头部,那么这个跨域请求就会失败,触发onerror事件。控制台中会输出类似一下的报错信息:
XMLHttpRequest cannot load http://api.alice.com. Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.
注: 最好在服务器端的Access-Control-Allow-Methods头部加上OPTIONS方法
Access-Control-Allow-Methods: GET,POST,OPTIONS
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://qunedu.oa.com
本文基本上翻译自http://www.html5rocks.com/en/tutorials/cors/,感谢Monsur Hossain。