如今请跟我作:在您的浏览器的地址栏中输入www.yhd.com并敲击回车。在网站内容所有加载完毕后,按F12打开浏览器的调试窗口。当切换到Sources页时,您会发现您当前所看到的一号店的页面是从多个不一样的域中获得的:html
或许有些读者会感到奇怪:在以前本身 写网页的时候就曾经尝试访问非当前域中的资源,却怎么也不成功,一号店是如何作到的?web
固然,这不是一号店的独门绝技,而仅仅是使用了一些跨域访问的技术而已。而在本文中,咱们就将对一种跨域访问技术CORS(Cross-Origin Resource Sharing)进行介绍。跨域
为何要用CORS浏览器
在须要作出一个技术决定时,咱们经常须要给出适当的理由。就CORS而言,使用它的根本缘由就是要完成资源的跨域访问,也就是如何绕过Same-origin Policy。app
那么什么是Same-origin Policy呢?简单地说,在一个浏览器中访问的网站不能访问另外一个网站中的数据,除非这两个网站具备相同的Origin,也便是拥有相同的协议、主机地址以及端口。一旦这三项数据中有一项不一样,那么该资源就将被认为是从不一样的Origin得来的,进而不被容许访问。dom
可是这个限制的确过于严格了:一个大型网站经常拥有一系列子域。在这些域之间交换数据就会受到Same-origin Policy的限制。为了绕过该限制,业界提出了一系列解决该问题的方法,例如更改document.domain属性,跨文档消息,JSONP以及CORS等。这些解决方案各有各的长处,所以咱们须要根据需求的不一样来对这些方案进行选择。函数
能够说更改document.domain属性的方法是最为直接快速的的方法,也较为常见。经过将从不一样域中获得的脚本的document.domain属性设置为同一个值,就可使得这些脚本之间能够相互交互。例如从“http://blog.ambergarden.com”获得的网页能够经过执行以下的脚本改变其document.domain属性中记录的所属域:post
1 document.domain = ‘ambergarden.com’;
那么接下来,该脚本就能够访问ambergarden.com中的数据了。网站
这种方法也有其自身的劣势,那就是软件开发人员不能够随便设置document.domain属性的值,至少在一些浏览器上是如此的。url
跨文档消息则是经过向Window实例发送消息来完成的。在使用时,软件开发人员须要经过调用一个Window的postMessage()函数来向该Window实例发送消息。此时Window实例内部的onmessage事件将被触发,进而使得该事件的消息处理函数被调用。可是在接收到消息的时候,消息处理函数首先须要判断消息来源的合法性,以免恶意用户经过发送消息的方式来非法执行代码。
JSONP则是经过在文档中嵌入一个<script>标记来从另外一个域中返回数据。例如在页面中添加一个以下的<script>标记:
1 <script src="http://blog.ambergarden.com/someData?callback=some_func"/>
该<script>标记会向http://blog.ambergarden.com/someData发送一个GET请求。在数据返回到客户端后,some_func()函数将会被调用。固然,这种方法拥有一个显著的缺点,那就是只支持GET操做。
就如您刚刚看到的同样,上面所列出的各个方法各自有各自的缺点及局限性。而相较于这些方法,CORS则没有那么多工做须要去作,也没有那么多限制。所以在本文中,咱们将主要对CORS进行讲解。
CORS运行流程
如今咱们就来看一个经过CORS来进行跨域访问的简单示例。假设ambergarden.com想从一个公有数据平台public-data.com中返回一些数据,那么在页面逻辑中,其能够经过下面的代码向public-data.com发送数据请求:
1 function retrieveData() { 2 var request = new XMLHttpRequest(); 3 request.open('GET', 'http://public-data.com/someData', true); 4 request.onreadystatechange = handler; 5 request.send(); 6 }
在运行这段代码的以后,浏览器会向服务发送以下的请求:
1 GET /someData/ HTTP/1.1 2 Host: public-data.com 3 ...... 4 Referer: http://ambergarden.com/somePage.html 5 Origin: http://ambergarden.com
而一个支持CORS协议的服务可能会给出下面的响应:
1 HTTP/1.1 200 OK 2 Access-Control-Allow-Origin: http://ambergarden.com 3 Content-Type: application/xml 4 ...... 5 6 [Payload Here]
这里有一个值得注意的响应头:Access-Control-Allow-Origin。该响应头用来记录能够访问该资源的域。在接收到服务端响应后,浏览器将会查看响应中是否包含Access-Control-Allow-Origin响应头。若是该响应头存在,那么浏览器会分析该响应头中所标示的内容。若是其包含了当前页面所在的域,那么浏览器就将知道这是一个被容许的跨域访问,从而再也不根据Same-origin Policy来限制用户对该数据的访问。
从整个访问数据的流程来看,用户所使用的跨域访问数据的脚本实际上和普通的访问同一个域中数据的脚本并无什么不一样。而不一样的,仅仅是在响应中多了一个Access-Control-Allow-Origin响应头。
是否是很简单?实际上咱们展现的仅仅是最为简单的Simple Request的执行流程。而CORS则将致使跨域访问的请求分为三种:Simple Request,Preflighted Request以及Requests with Credential。
若是一个请求没有包含任何自定义请求头,并且它所使用HTTP动词是GET,HEAD或POST之一,那么它就是一个Simple Request。可是在使用POST做为请求的动词时,该请求的Content-Type须要是application/x-www-form-urlencoded,multipart/form-data或text/plain之一。
若是一个请求包含了任何自定义请求头,或者它所使用的HTTP动词是GET,HEAD或POST以外的任何一个动词,那么它就是一个Preflighted Request。若是POST请求的Content-Type并非application/x-www-form-urlencoded,multipart/form-data或text/plain之一,那么其也是Preflighted Request。
通常状况下,一个跨域请求不会包含当前页面的用户凭证。一旦一个跨域请求包含了当前页面的用户凭证,那么其就属于Requests with Credential。
前面咱们已经看过浏览器对Simple Request是如何进行处理的。那么接下来咱们就来看看Preflight Request是如何执行的。相较于Simple Request,Preflight Request的运行流程则略为复杂一些。
假设如今咱们要向公有数据平台public-data.com写入一些数据,那么咱们就须要发送一个POST请求:
1 function sendData() { 2 var request = new XMLHttpRequest(), 3 payload = ......; 4 request.open('POST', 'http://public-data.com/someData', true); 5 request.setRequestHeader('X-CUSTOM-HEADER', 'custom_header_value'); 6 request.onreadystatechange = handler; 7 request.send(payload); 8 }
在执行了该段代码以后,浏览器首先发出的请求将以下所示:
1 OPTIONS /someData/ HTTP/1.1 2 Host: public-data.com 3 ...... 4 Origin: http://ambergarden.com 5 Access-Control-Request-Method: POST 6 Access-Control-Request-Headers: X-CUSTOM-HEADER
能够看到,咱们首先发送的并非POST请求,而是OPTION请求。该请求还经过Access-Control-Request-Method以及Access-Control-Request-Headers标示了请求类型以及请求中所包含的自定义HTTP Header。实际上,它至关于向服务端询问访问资源的权限:“您好,我想向你这里发送数据,你看能够吗?”。而在真正访问资源前发送一个请求进行探测也是该请求被称为是Preflight Request的缘由。
在服务端看到该OPTIONS请求后,其将分析该请求中的内容并返回一个响应,以通知浏览器是否容许向它发送数据:
1 HTTP/1.1 200 OK 2 Access-Control-Allow-Origin: http://ambergarden.com 3 Access-Control-Allow-Methods: POST, GET, OPTIONS 4 Access-Control-Allow-Headers: X-CUSTOM_HEADER 5 Access-Control-Max-Age: 1728000 6 ......
浏览器分析该响应并了解到其被容许向服务端发送数据之后,其才会向服务端发送真正的POST请求:
1 POST /someData/ HTTP/1.1 2 Host: public-data.com 3 X-CUSTOM-HEADER: custom_header_value 4 ...... 5 6 [Payload Here]
而服务端则会接收并处理该请求:
1 HTTP/1.1 200 OK 2 Access-Control-Allow-Origin: http://ambergarden.com 3 Content-Type: application/xml 4 ...... 5 6 [Payload Here]
最后一种请求Requests with Credential的运行流程则和前两种请求相似。只不过在发送请求的时候,咱们须要将用户凭证包含在请求中:
1 function retrieveData() { 2 var request = new XMLHttpRequest(); 3 request.open('GET', 'http://public-data.com/someData', true); 4 request.withCredentials = true; 5 request.onreadystatechange = handler; 6 request.send(); 7 }
而在服务端的响应中,其将拥有一个额外的Access-Control-Allow-Credentials响应头:
1 HTTP/1.1 200 OK 2 Access-Control-Allow-Origin: http://ambergarden.com 3 Content-Type: application/xml 4 ...... 5 6 [Payload Here]
集成对CORS的支持
从上面的示例中已经可以看到,在使用CORS来访问数据的时候,客户端不须要更改任何数据访问逻辑。全部的一切工做都是在服务端及浏览器之间自动完成的。所以若是但愿为一个系统集成CORS支持的时候,咱们须要作的工做主要集中在服务端。
固然,集成工做实际上十分简单:在你的web.xml中添加一个Filter(或利用已有的Filter)并根据传入的请求首先判断其是哪种CORS请求。在得知了请求的类型后,咱们就能够决定到底以哪一种方式响应用户了。这里的逻辑较为简单,所以我就再也不赘述了。
转载请注明原文地址并标明转载:http://www.cnblogs.com/loveis715/p/4592246.html
商业转载请事先与我联系:silverfox715@sina.com