要记录好这个问题,首先需要描述清楚这个问题。
有如下的一个场景:我们在实现一个系统中某个功能的时候,可能系统的某些模块不在一个域名下。用户中心模块部署在user.xxx.com下,产品相关的功能部署在product.xxx.com下,在用户中心的某些页面,我们或许要请求产品功能模块的功能,此时就会有问题出现。以Google Chrome(版本 45.0.2454.101 m)浏览器为例,当当前的页面在A域名下,我们在页面中构造了一个异步请求,我们需要调用位于B域名下的某个API,代码片段如下:
<script type="text/javascript">
$(function() {
//当前页面所在域名:http://localhost:9000/
//jQuery版本:jquery-1.11.3.min.js
$.ajax({
url : "http://localhost:9001/order/pindexpost",
type : "POST",
dataType : 'json',
success : function(data) {
alert(data);
}
});
});
</script>
但是请求之后,浏览器控制台给出了如下的错误信息
那么这就是标题中所提到的跨域资源共享CORS问题了。我们把这个概念细化一下,首先,为什么会有CORS?简单点理解就是出于安全考虑,浏览器都遵循一个叫同源策略的东西,他约束浏览器说“不是你的东西,你问人家要,人家默认不给你,除非人家同意给你”,基于这个策略,就有了跨域资源共享的问题,那么,怎么就叫跨域了呢?上面代码中的域名不是一样的吗,都是指向本地的localhost啊。举个栗子说明下
http://www.blogjava.net:80,左边是一个完整的请求,包含了协议,子域名等等若干部分,我们把它理解为域,但凡是在这个域下发生的请求,都无法直接访问到其他域的资源
- http://www.blogjava.net:80能访问http://www.blogjava.net:81下的资源吗?不行!
- http://www.blogjava.net:80能访问http://abc.blogjava.net:80下的资源吗?不行!
- http://www.blogjava.net:80能访问https://www.blogjava.net:80下的资源吗?不行!
- http://www.blogjava.net:80能访问http://域名对应的真实IP:80下的资源吗?不行!
- http://www.blogjava.net:80能访问http://www.xyz.net:80下的资源吗?不行!
域哪怕有一点点不一样,一点点不一样,一点点不一样,都是不可以访问的,那么问题来了,如何实现跨域呢? 目前有三种方案可以打破同源策略所带来的限制,实现跨域请求。
-
使用JSONP。这种方案是使用HTML的script标签来实现的,script标签的src属性不受同源策略约束,可以访问任意站点的资源,但是,该方案有着自己的约束。首先,他只能发出GET请求,因为script标签的初衷就是为了获取js脚本,所以如果返回的数据内容不是js脚本的话,也不会成功。基于这个约束,我们需要改造一下我们的请求:
1 <script type="text/javascript">
2 $(function () {
3 //当前页面所在域名:http://localhost:9000/
4 // jQuery版本:jquery-1.11.3.min.js
5 $.ajax({
6 url: "http://localhost:9001/order/pindexpost",
7 type: "POST", dataType: 'jsonp',
8 jsonpCallback: "callback"
9 });
10 })
11 ;
12 function callback(result) {
13 alert("suc:" + result);
14 }
15 </script>
刚才提到,JSONP方式是基于script标签的,所以我们要对返回的数据做一些处理,使返回的数据是一段js脚本才可以。我们在客户端预定义好callback函数,使得服务端返回数据的时候可以直接调用这个callback函数,服务端代码片段如下:
1 response().setContentType("application/javascript");
2 return ok("callback(" + data + ")");
要注明返回的格式,并且data的格式也要符合js的语法。其实说白了,JSONP就是请求目标数据,然后将目标数据和回调函数进行拼接,以javascript的形式返回给浏览器,之后浏览器执行的这个回调函数。
使用代理服务器。这个比较好理解,不过运维的成分多一些,设置一个代理服务器,根据请求API所在命名空间的不同,转发到相应的域去,这样一来就骗过了浏览器,让浏览器以为所有的数据都来自一个域。
- 设置请求的相应头。在目标API的响应头中添加“Access-Control-Allow-Origin”,设置值为“*”,这就是让服务器通知浏览器,说“我这个API的响应,是面向所有人的,*就代表这个意思啊”,但是,这也会带来一个安全性的问题,所以你可以设置部分站点可以跨域访问,比如Access-Control-Allow-Origin: http://www.blogjava.net/,但是这个响应头存在一定的兼容问题,具体支持情况可以查看caniuse.com