XHR的一个主要约束是同源策略,即:相同域、相同端口、相同协议,可以通过跨域资源共享CORS(Cross-Origin Resourse Sharing)实现跨域资源共享。其基本思想是通过自定义HTTP头让浏览器与服务器沟通,从而决定请求或响应是应该成功还是失败。发送get/post请求时,给它添加一个额外的Origin头部,其中包含请求页面的源信息(协议,域名和端口),以便服务器根据这个头部信息来决定是否响应,像这样的:Origin:http://www.nczonline.net。如果服务器认为这个请求可以接受,就在头部发回相同的源信息:Access-Control-Allow-Origin:http://www.nczonline.net。如果没有这个头部或者源信息不匹配,那么服务器就会驳回请求,正常的情况下浏览器会处理请求。
跨域请求,就是一个站点中的资源去访问另外一个不同域名站点上的资源。这种情况很常见,比如说通过 <link> 标签加载外部样式表文件、通过 <img> 标签加载外部图片、通过 <script> 标签加载外部脚本文件等等。默认情况下,脚本访问文档属性等数据采用的是同源策略(Same origin policy)。
那么,什么是同源策略呢?如果两个页面的协议、域名和端口是完全相同的,那么它们就是同源的。 如果两个页面的主域名相同,则还可以通过设置 document.domain 属性将它们认为是同源的。
(1)、IE对CORS支持——XDR(XDomainRequest)
IE中使用XDR对象实现CORS,它的使用与XHR对象类似,也是实例化后调用open()和send()方法。不同的是,XDR的open()方法只有两个参数:请求类型和URL,默认异步请求。
XDR和XHR的一些不同之处:
cookie不会随请求发送,也不会随响应返回。
只能设置请求头部信息中的Content-Type字段。
不能访问响应头部信息。
只支持GET和POST请求
var xdr= new XDomainRequest();
xdr.οnlοad=function(){
alert(xhr.responseText);
}
xdr.οnerrοr=function(){
alert("An error occurred");
}
xdr.timeout=1000;
xdr.ontimeout=function(){
alert("Request took too long");
}
xhr.open("get","http://www.other.com");
xdr.contentType="application/x-www-form-urlencoded";
xhr.send("name1=value1&name2=value2");
(2)、其他浏览器支持CORS——原生XHR
大多数浏览器的XHR对象原生支持CORS,只需要在open()方法中传入响应的url即可。与IE中的XDR对象不同,通过跨域XHR对象可以访问status和statusText属性,而且还支持同步请求。但是也有以下限制:
不能使用setRequestHeader()设置自定义头部。
不能发送和接收cookie。
调用getAllRequestHeaders()方法总返回空字符串。
(3)、跨浏览器支持CORS
综合以上两种情况,可以实现跨浏览器的CORS。检查XHR是否支持CORS的最简单方式是检查 withCredentials属性,然后结合检查XDomainRequest对象是否存在即可。
(4)、简单请求
什么样的请求算是简单请求呢?简单请求必须满足下面2点:
A、只使用 GET、POST 进行的请求,这里的POST只包括发送给服务器的数据类型(Content-Type)必须是 application/x-www-form-urlencoded、multipart/form-data 或者 text/plain中一个。
B、HTTP 请求没有设置自定义的请求头,如我们常用的 X-JSON。
客户端:
var xhr = new XMLHttpRequest();
var url = 'http://dotnet.aspx.cc/SimpleCrossSiteRequests.aspx';
xhr.open('GET', url, true);
xhr.onreadystatechange = handler;
xhr.send();
服务器端:
Response.AddHeader("Access-Control-Allow-Origin", "http://www.meng_xian_hui.com:801");
Response.Write("你的第一个跨域测试成功啦!!!");
需要特别注意的是:在请求信息中,浏览器使用 Origin 这个 HTTP 头来标识该请求来自于 http://www.meng_xian_hui.com:801;在返回的响应信息中,使用 Access-Control-Allow-Origin 头来控制哪些域名的脚本可以访问该资源。如果设置 Access-Control-Allow-Origin:*,则允许所有域名的脚本访问该资源。如果有多个,则只需要使用逗号分隔开即可。
(5)、预检请求
预检请求首先需要向另外一个域名的资源发送一个 HTTP OPTIONS 请求头,其目的就是为了判断实际发送的请求是否是安全的。下面的2种情况需要进行预检:
A、不是上面的简单请求,比如使用Content-Type 为 application/xml 或 text/xml 的 POST 请求
B、在请求中设置自定义头,比如 X-JSON、X-MENGXIANHUI 等
客户端:
var xml = "<root>测试</root>";
xhr.open('POST', url, true);
xhr.setRequestHeader("POWERED-BY-MENGXIANHUI","Approve");
xhr.setRequestHeader("Content-Type", "application/xml");
xhr.onreadystatechange = handler;
xhr.send(xml);
服务器端://通知客户端允许预检请求。并设置缓存时间
Response.ClearContent();
Response.AddHeader("Access-Control-Allow-Origin", "http://www.meng_xian_hui.com:801");
Response.AddHeader("Access-Control-Allow-Methods","POST, GET, OPTIONS");
Response.AddHeader("Access-Control-Allow-Headers", "POWERED-BY-MENGXIANHUI");
Response.AddHeader("Access-Control-Max-Age", "30");
//此过程无需返回数据
Response.End();
以上的代码反映了预检请求的执行过程:首先发送 OPTIONS 请求头,用来向服务器咨询服务器的更多信息,以便为后续的真实请求做准备。比如是否支持 POST 方法等。值得注意的是:浏览器还发送 Access-Control-Request-Method: POST 和 Access-Control-Request-Headers:POWERED-BY-MENGXIANHU请求头。
(6)、带验证信息的请求
XMLHttpRequest和访问控制功能,最有趣的特性就是,发送凭证请求(HTTP Cookies和验证信息)的功能。一般而言,对于跨站请求,浏览器是不会发送凭证信息的。但如果将XMLHttpRequest的一个特殊标志位设置为true,浏览器就将允许该请求的发送。身份验证是Web开发中经常遇到的问题,在跨域请求中,默认情况下是不发送验证信息的。要想发送验证信息,需要进行withCredentials 属性,
客户端:
var xhr = new XMLHttpRequest();
var url = 'http://dotnet.aspx.cc/RequestsWithCredentials.aspx';
xhr.open('GET', url, true);
xhr.onreadystatechange = handler;
xhr.withCredentials = "true";
xhr.send();
浏览器端:
Access-Control-Allow-Origin: http://www.meng_xian_hui.com:801
Access-Control-Allow-Credentials: true
将XMLHttpRequest的withCredentials标志设置为true,从而使得Cookies可以随着请求发送。因为这是一个简单的GET请求,所以浏览器不会发送一个“预请求”。但是,如果服务器端的响应中,如果没有返回Access-Control-Allow-Credentials: true的响应头,那么浏览器将不会把响应结果传递给发出请求的脚步程序,以保证信息的安全。
特别注意: 给一个带有withCredentials的请求发送响应的时候,服务器端必须指定允许请求的域名,不能使用'*'.上面这个例子中,如果响应头是这样的:Access-Control-Allow-Origin: * ,则响应会失败. 在这个例子里,因为Access-Control-Allow-Origin的值是http://www.meng_xian_hui.com:801这个指定的请求域名,所以客户端把带有凭证信息的内容被返回给了客户端.
(7)、IE8已经开始支持跨域访问资源了,但是,IE8提供的功能还比较简单,可以进行简单的请求,
客户端:
var xhr = new XDomainRequest();
var url = 'http://dotnet.aspx.cc/SimpleCrossSiteRequests.aspx';
xhr.open('GET', url, true);
xhr.onreadystatechange = handler;
xhr.send();
服务器端:
Response.AddHeader("Access-Control-Allow-Origin", "http://www.meng_xian_hui.com:801");
Response.Write("你的第一个跨域测试成功啦!!!");
(8)、HTTP响应头
这部分里列出了跨域资源共享(Cross-Origin Resource Sharing)时,服务器端需要返回的响应头信息.上一部分内容是这部分内容在实际运用中的一个概述.
Access-Control-Allow-Origin
返回的资源需要有一个 Access-Control-Allow-Origin 头信息,语法如下:
Access-Control-Allow-Origin: <origin> | *
origin参数指定一个允许向该服务器提交请求的URI.对于一个不带有withCredentials的请求,可以指定为'*',表示允许来自所有域的请求.
举个栗子,允许来自 http://mozilla.com 的请求,你可以这样指定:
Access-Control-Allow-Origin: http://mozilla.com
如果服务器端指定了域名,而不是'*',那么响应头的Vary值里必须包含Origin.它告诉客户端: 响应是根据请求头里的Origin的值来返回不同的内容的.
Access-Control-Expose-Headers
设置浏览器允许访问的服务器的头信息的白名单:
Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header
这样, X-My-Custom-Header 和 X-Another-Custom-Header这两个头信息,都可以被浏览器得到.
Access-Control-Max-Age
这个头告诉我们这次预请求的结果的有效期是多久,如下:
Access-Control-Max-Age: <delta-seconds>
delta-seconds 参数表示,允许这个预请求的参数缓存的秒数,在此期间,不用发出另一条预检请求.
Access-Control-Allow-Credentials
告知客户端,当请求的withCredentials属性是true的时候,响应是否可以被得到.当它作为预请求的响应的一部分时,它用来告知实际的请求是否使用了withCredentials.注意,简单的GET请求不会预检,所以如果一个请求是为了得到一个带有withCredentials的资源,而响应里又没有Access-Control-Allow-Credentials头信息,那么说明这个响应被忽略了.
Access-Control-Allow-Credentials: true | false
Access-Control-Allow-Methods
指明资源可以被请求的方式有哪些(一个或者多个). 这个响应头信息在客户端发出预检请求的时候会被返回. 上面有相关的例子.
Access-Control-Allow-Methods: <method>[, <method>]*
Access-Control-Allow-Headers
也是在响应预检请求的时候使用.用来指明在实际的请求中,可以使用哪些自定义HTTP请求头.比如
Access-Control-Allow-Headers: X-PINGOTHER
这样在实际的请求里,请求头信息里就可以有这么一条:
X-PINGOTHER: pingpong
可以有多个自定义HTTP请求头,用逗号分隔.
Access-Control-Allow-Headers: <field-name>[, <field-name>]*
(9)、HTTP 请求头
这部分内容列出来当浏览器发出跨域请求时可能用到的HTTP请求头.注意这些请求头信息都是在请求服务器的时候已经为你设置好的,当开发者使用跨域的XMLHttpRequest的时候,不需要手动的设置这些头信息.
Origin:表明发送请求或者预请求的域
Origin: <origin>
参数origin是一个URI,告诉服务器端,请求来自哪里.它不包含任何路径信息,只是服务器名.
Note: Origin的值可以是一个空字符串,这是很有用的.
注意,不仅仅是跨域请求,普通请求也会带有ORIGIN头信息.
Access-Control-Request-Method
在发出预检请求时带有这个头信息,告诉服务器在实际请求时会使用的请求方式
Access-Control-Request-Method: <method>
Access-Control-Request-Headers
在发出预检请求时带有这个头信息,告诉服务器在实际请求时会携带的自定义头信息.如有多个,可以用逗号分开.
Access-Control-Request-Headers: <field-name>[, <field-name>]*