17.2 跨域请求
通过 XHR 实现 Ajax 通信的一个主要限制,来源于跨域安全策略。默认情况下,XHR 对象只能访问与包含它的页面位于同一个域中的资源。这种安全策略可以预防某些恶意行为。但是,实现合理的跨域请求对开发某些浏览器应用程序也是至关重要的。
实现跨域请求的解决方案可能会有所差异,但它们的目标是类似的。首先,就是要确保不会在请求和响应中携带 cookie,因为 cookie 被窃取是主要的安全隐患。其次,就是要确保未经授权无法访问相应的资源。简言之,如果某个域的资源并没有允许访问,JavaScript 就不应该能够请求该域中的资源。为此,IE 和 Firefox 已经实现了各自的跨域解决方案。
17.2.1 XDomainReuqest 对象
微软在 IE8 中引入了 XDR (XDomainRequest) 类型。这种对象与 XHR 类似,但能实现安全可靠的跨域通信。XDR 对象的安全机制中部分实现了 W3C 的跨站点请求访问控制规范(Access Control for Cross-Site Request) 。以下是 XDR 与 XHR 的一些不同之处。
- cookie 不会随请求发送,不会随响应返回。
- 只能设置请求头部信息中的 Content-Type 字段。
- 不能访问响应头部信息。
- 只支持 GET 和 POST 请求。
- XDR 只能访问 Access-Control-Allow-Origin 头部字段设置为 * 的资源。
XDR 对象的使用方法与 XHR 对象非常相似。也是创建一个 XDomainRequest 的实例,调用 open() 方法,再调用 send() 方法。但与 XHR 对象的 open() 方法不同,XDR 对象的 open() 方法只接受两个参数:请求的类型和 URL 。
所有 XDR 请求都是异步执行的,不能用它来创建同步请求。请求返回之后,会触发 load 事件,响应的数据也会保存在 responseText 属性中,如下所示:
var xdr = new XDomainRequest();
xdr.onload = function(){
alert(xdr.responseText);
};
xdr.open("get", "http://www.somewhere-else.com/page/");
xdr.send(null);
在接收到响应后,你只能访问响应的原始文本;没有办法确定响应的状态代码。而且,只要响应有效就会触发 load 事件,如果失败 (包括响应中缺少 Access-Control-Allow-Origin 头部) 就会触发 error 事件。遗憾的是,出了错误本身之外,没有其他信息可用,因此唯一能够确定的就只有未成功了。要检测错误,可以像下面这样指定一个 onerror 事件处理程序;
var xdr = new XDomainRequest();
xdr.onload = function(){
alert(xdr.responseText);
};
xdr.onerror = function(){
alert("An error occurred.");
};
xdr.open("get", "http://www.somewhere-else.com/page/");
xdr.send(null);
鉴于导致 XDR 请求失败的因素很多,因此建议你不要忘记通过 onerror 事件处理程序来捕获该事件;否则,即使请求失败也不会有任何提示。
在请求返回前调用 abort() 方法可以终止请求:
xdr.abort(); // 终止请求
与 XHR 一样,XDR对象也支持 timeout 属性以及 ontimeout 事件处理程序。下面是一个例子:
<span style="white-space:pre"> </span>var xdr = new XDomainRequest();
xdr.onload = function(){
alert(xdr.responseText);
};
xdr.onerror = function(){
alert("An error occurred.");
};
xdr.timeout = 1000;
xdr.ontimeout = function(){
alert("Request took too long.");
};
xdr.open("get", "http://www.somewhere-else.com/page/");
xdr.send(null);
为支持 POST 请求,XDR 对象提供了 contentType 属性,用来表示发送数据的格式,如下面的例子所示:
var xdr = new XDomainRequest();
xdr.onload = function(){
alert(xdr.responseText);
};
xdr.onerror = function(){
alert("An error occurred.");
};
xdr.open("post", "http://www.somewhere-else.com/page/");
xdr.contentType = "application/x-www-form-urlencoded";
xdr.send("name1=value1 & name2=value2");
这个属性是通过 XDR 对象影响头部信息的唯一方式。
XDR 是 IE 率先在主流浏览器中引入的跨域请求解决方案。虽然要充分利用 XDR 对象还需要开发人员不断地实践,但人们对它的期望则是能够访问 RSS 和 Atom 这样的数据流,以便应用到 Web 开发当中。
17.2.2 跨域 XHR
在 Firefox 3 中,Mozilla 添加了自己的跨域 Ajax 请求解决方案,同样也是基于 W3C 的 "跨站点请求访问控制规范"。最初,这个解决方案是适用于 Web内容的,但发布之后却只能应用于特权脚本和浏览器扩展。不管怎样,人们普遍相信只要有了适当的安全措施,同样的功能最终还会适用于 Web 内容的。本节主要介绍 Firefox 3 中跨域 XHR 的当前状态。
与 IE 的方案类似,W3C 的 "跨站点请求访问控制规范" 要求远程资源有权决定自身是否可以被远程浏览器访问。这就需要通过设置 Access-Control-Aloow-Origin 头部,并指定哪个域可以访问该资源来实现,例如:
Access-Control-Allow-Origin: http://www.wrox.com
这个头部信息指定只有 www.wrox.com 域有访问权限。与 IE 的相同,通过指定 * 值,可以允许所有请求访问当前资源,如下所示:
Access-Control-Allow-Origin: *
如果不是公共 API 或 Web 服务,我们不推荐像这样对所有请求来源开放。
要请求另一个域的资源,可以使用标准的 XHR 对象,并为 open() 方法传入一个绝对 URL ,例如:
var xhr = createXHR();
xhr.onreadystatechange = function(){
if(xhr.readyState == 4){
if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
}else {
alert("Request was unsuccessful: " + xhr.status);
}
}
};
xhr.open("get", "http://www.somewhere-else.com/page/", true);
xhr.send(null);
与 IE 中的 XDR 对象不同,跨域 XHR 对象允许访问 status 和 statusText 属性,也支持同步请求。不过,为确保安全,跨域 XHR 也存在一些额外的限制,简述如下。
- 不能使用 setRequestHeader() 设置自定义头部。
- 不能发送也不会介绍 cookie。
- getAllResponseHeaders() 方法只能返回空字符串。