一、CORS简介
在前一篇博客中,我介绍了利用JSONP实现跨域请求,但是在上篇文章中也指出了用JSONP实现跨域存在的一些缺点, 因此W3C 提出了另外一个跨域的方法:CORS,全称是”跨域资源共享”(Cross-origin resource sharing)。与JSONP相比,CORS更为为先进、方便和可靠:
- JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求。
- 使用CORS,开发者可以使用普通的XMLHttpRequest发起请求和获得数据,比起JSONP有更好的错误处理。
JSONP主要被老的浏览器支持,它们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS。
关于浏览器对CORS的支持情况,读者可以参照网址http://caniuse.com/#feat=cors,该网站中对CORS的支持情况进行了总结(绿色代表支持):
CORS 背后的基本思想,就是使用自定义的 HTTP 头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。比如一个简单的使用 GET 或 POST 发送的请求,它没有自定义的头部,而主体内容是 text/plain。在发送该请求时,需要给它附加一个额外的 Origin 头部,其中包含请求页面的源信息(协议、域名和端口),以便服务器根据这个头部信息来决定是否给予响应。例如我们的
Origin:http://localhost
在服务器端如果认为这个请求可以接受,就在 Access-Control-Allow-Origin 头部中回发相同的源信息(如果是公共资源,可以回发”*”)。例如:
Access-Control-Allow-Origin: http://localhost
二、IE对CORS的实现
微软在 IE8 中引入了 XDR(XDomainRequest)类型。这个对象与 XHR 类似,但能实现安全可靠的跨域通信。 XDR 对象的安全机制部分实现了 W3C 的 CORS 规范。
XDR 对象的使用方法与 XHR 对象非常相似。也是创建一个 XDomainRequest 的实例,调用 open()方法,再调用 send()方法。但与 XHR 对象的 open()方法不同, XDR 对象的 open()方法只接收两个参数:请求的类型和 URL。
所有 XDR 请求都是异步执行的,不能用它来创建同步请求。请求返回之后,会触发 load 事件,响应的数据也会保存在 responseText 属性中,如下是一个简单的示例:
var xdr = new XDomainRequest();
xdr.onload = function(){
console.log(xdr.responseText);
};
xdr.open("get", "http://182.254.146.112/demo.php");
xdr.send(null);
demo.php文件是我放在远程服务器上的文件,文件内容如下:
<?php
header("Access-Control-Allow-Origin: http://localhost");
$info=array("name"=>"sean","age"=>"25","city"=>"Nanjing");
echo json_encode($info);
?>
注意:特别需要注意的是,服务端的文件一定要在header中设置Access-Control-Allow-Origin
。
在IE<11版本浏览器中测试,会得到demo.php中的返回值,如下:
{"name":"sean","age":"25","city":"Nanjing"}
这里需要注意的是:在IE11中测试时会报错“XDomainRequest 未定义”的错误,因此IE11已经不采用该方法了,而是采用下面的标准方法。
三、其他浏览器对CORS的实现
Firefox 3.5+、 Safari 4+、 Chrome、 iOS 版 Safari 和 Android 平台中的 WebKit 都通过 XMLHttpRequest对象实现了对 CORS 的原生支持。在尝试打开不同来源的资源时,无需额外编写代码就可以触发这个行为。 要请求位于另一个域中的资源,使用标准的 XHR 对象并在 open()方法中传入绝对 URL 即可:
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if (xhr.readyState == 4){
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
console.log(xhr.responseText);
} else {
console.log("Request was unsuccessful: " + xhr.status);
}
}
};
xhr.open("get", "http://182.254.146.112/demo.php", true);
xhr.send(null);
在chrome和Firefox浏览器中测试都能得到结果:
{"name":"sean","age":"25","city":"Nanjing"}
IE11也选择了XMLHttpRequest对象实现 CORS ,而抛弃了XDomainRequest,在IE11浏览器中测试,也能得到上述结果。
四、跨浏览器的CORS
即使浏览器对 CORS 的支持程度并不都一样,但所有浏览器都支持简单的(非 Preflight 和不带凭据的)请求,因此有必要实现一个跨浏览器的方案。检测 XHR 是否支持 CORS 的最简单方式,就是检查是否存在 withCredentials 属性。再结合检测 XDomainRequest 对象是否存在,就可以兼顾所有浏览器了。
withCredentials 属性可以指定某个请求是否应该发送凭据,如果withCredentials 属性设置为 true,即请求应该发送凭据。如果服务器接受带凭据的请求,会用下面的 HTTP 头部来响应。
Access-Control-Allow-Credentials: true;
跨浏览器的CORS简单代码如下:
function createCORSRequest(method, url){
var xhr = new XMLHttpRequest();
if ("withCredentials" in xhr){
xhr.open(method, url, true);
} else if (typeof XDomainRequest != "undefined"){
vxhr = new XDomainRequest();
xhr.open(method, url);
} else {
xhr = null;
}
return xhr;
}
var request = createCORSRequest("get", "http://182.254.146.112/demo.php");
if (request){
request.onload = function(){
console.log(request.responseText);
//对 request.responseText 进行处理
};
request.send();
}
上述代码在几大主流浏览器中都能得到很好的支持。另外,Firefox、 Safari 、 Chrome 和 IE11 中的 XMLHttpRequest 对象与 IE<11 中的 XDomainRequest 对象都提供了一些接口,这两个对象共同的属性/方法如下:
方法/属性 | 说明 |
---|---|
abort() | 用于停止正在进行的请求 |
onerror | 用于替代 onreadystatechange 检测错误 |
onload | 用于替代 onreadystatechange 检测成功 |
responseText | 用于取得响应内容 |
send() | 用于发送请求 |
以上成员都包含在 createCORSRequest()函数返回的对象中,在所有浏览器中都能正常使用。我们常用到的就是onload
和onerror
分别代表当请求成功时触发和当请求失败时触发。其基本用法如下:
request.onload = function(){
console.log(request.responseText);
//对 request.responseText 进行处理
};
request.onerror = function(){
alert("There is an error!");
};