通过XHR实现Ajax通信的一个主要限制,来源于跨域安全策略。默认情况下,XHR对象只能访问与包含它的页面位于同一个域中的资源。这种安全策略跨域可以预防某些恶意行为。但是,实现合理的跨域请求对开发某些浏览器应用程序来说也是至关最重要的。
我们先说何为跨域的请求,这里得提到一个词叫“同源策略”。
先不用急着点进去看,我直接说几个核心的标准:即为域名、协议、端口三者中,如果有一个是不相同的,即为跨域请求;
那么跨域限制了什么?(这里举例两点,更多可以自行百度和验证)
1.Ajax的请求发送不出去
2.cookie的获取
最简单的例子莫过于,大家都知道,localhost 和127.0.0.1,这两个地址在浏览器的跳转都是一样,但我如果从localhost去请求127.0.0.1,就会造成跨域从而浏览器拦截,为什么?域名不一样!
事先我希望大家能明白跨域到底是谁的问题的?是浏览器的问题!我们请求出去了吗?我们确实是请求出去了!但问题就是在于响应信息给客户端时,被浏览器所拦截然后报错!
话不多说,喝口水,上代码!
我们先写好服务器的代码,这里为了方便快速我用了node
客户端代码如下,这里我用了wamp做为服务器,端口默认为80;这里直接用jQuery的ajax;我们发送了一个端口为8080的请求
现在我们在浏览器中直接打开这个页面
首先我们可以看到,在命令行中出现了我们在node中的打印,证明我们请求成功了,然后浏览器这边是报了个错,我们把报错信息翻译一下:
是一个标准得不能再标准的跨域请求所造成的报错了;那么演示了问题的产生,就改解决了~我们先看这个报错信息提到的一点
已被CORS策略阻止:在网络上没有'Access-Control-Allow-Origin'标头 要求的资源
这里我们就着这个报错信息,引出第一个解决办法:CORS;
CORS(Cross-Origin Resource Sharing,跨域资源共享),定义了在必须访问跨域资源时,浏览器与服务器应该如何沟通。CORS背后的基本思想,就是使用自定义的HTTP头部与浏览器与访问器进行沟通,从而决定请求或响应是应该成功,还是应该失败。
使用CORS需要在服务器进行配置,我们依旧用上面node的代码写下去;
这里需要设置响应头信息:
res.setHeader('Access-Control-Allow-Origin', '域名'); // 可以使用通配符:*
此时浏览器端的访问就没问题了
以上虽然解除了ajax的请求,但还有一个就是cookie的获取;
以上我们在浏览器写了一个cookie,但在node中打印req.headers,获取请求头,却没有cookie的影子~
这时就需要前后端配合了,而不只是后端的设置,这里我们引出高程3中的一个词:带凭据的请求
如果拥有凭据,可以携带和响应cookie;
node的修改:
此时cookie也能正常获取了(因为中文格式乱了码,不过不要在意细节)
基本没啥问题后,我又寻思着,天天调后端的接口,得给后端妹子留点哥的个人信息不是?
我希望自定义请求头,把我的名字带上,让后端妹子对我印象深刻
结果我不仅人傻了,一看服务器的请求,还是一个options方法的请求是怎么回事!
这里再引出一个名词:Preflighted Request
CORS通过一种叫Preflighted Request的透明服务器验证机制支持开发人员使用自定义的头部、GET或POST以外的方法,以及不同类型的主体内容。在使用下列高级选项来发送请求时,就会向服务器发送一个Prefighted请求。
也就是之前我们的跨域可以看作是简单跨域,现在我们通过自定义请求头而造成了复杂跨域:
复杂跨域请求要满足以下:
1、请求方法不是GET/HEAD/POST
2、POST请求的Content-Type并非application/x-www-form-urlencoded, multipart/form-data, 或text/plain
3、请求设置了自定义的header字段
我们再看报错信息
得知还是响应的问题,再从后端入手,我们增加一条配置
这时的请求也就没问题了,但注意还是有一次OPTIONS请求,往后的请求但不一定每次都会有;
以上其中到底还是使用CORS允许跨域,再说几个其他的跨域方法;
最有名也最简单的莫过于JSONP了
JSONP是JSON with padding(填充式JSON或参数式JSON)的简写,是应用JSON的一种新方法,在后来的Web服务器中非常流行。JSONP和JSON差不多,只不过是被包含在函数调用中的JSON;
JSONP由两部分组成:回调函数和数据;回调函数是当响应到来时应该在页面中调用的函数。回调函数的名字一般是在请求中指定。而数据就是传入回调函数中的JSON数据。
以上好像说得有点云里雾里,但一看代码你就能秒懂!首先跨域限制了ajax的发送,那我们不用ajax不就行了!
JSONP是通过动态<script>元素使用的,使用时可以为src属性指定一个跨域URL。这里的script和img元素类似,都有能力不受限制地从其他域加载资源。
<script src="http://127.0.0.1:8080/test"></script>
这里我们能看到,因为node响应的是一段js代码,而我们请求放在script标签里面,也就执行了这段代码;这也就是JSONP的原理了:利用script标签的src很好的规避了同源策略;
那img的src,link的href也能请求,为什么只用script标签呢?因为script能执行返回的代码!
以上代码我们稍作加工即可达到传递数据的效果
首先你需要给我传递回调函数名字,这里我写了一个简单的data作为返回;接着我得到回调函数的名字之后,响应的就是这个回调的调用,参数为我的data数据;
我在客户端准备好回调函数,然后作为参数传递给服务器
最后也就在浏览器中成功拿到
JSONP在jQuery中有对应封装好的的api,这个可以自行查阅文档~
再说第三种跨域方式:服务器代理跨域
既然跨域是因为响应的时候被浏览器拦截了,那我们让服务器去帮我们请求,得到请求结果再把结果响应给客户端;整个请求的过程没有浏览器的职责,自然也没有跨域的拦截响应了~
这里先举个例子,常用vue-cli开发的人都知道,在webpack的配置中有一项可以帮我们实现这个功能;
这个通常在前端测试接口的时候,如果在本地开发测试用到线上的结构会造成跨域,就可以在dev中配置proxyTable,让服务器帮我们去请求;
如果不了解的可以直接忽略,看下面的实现;
首先我请求一个豆瓣电影的页面
浏览器告诉我跨域了
接着我把这个地址转发给服务器,让服务器帮我去请求(这里不用管我客户端和我的服务器是否跨域,就当作我客户端和我的服务器是不跨域的来看)
我们在node中使用axios帮我们请求
此时浏览器也就拿到了数据
nginx代理跨域也是利用服务器请求的原理,只需要配置一下nginx的配置文件,这里虽然做过,但不不太了解ngxin配置,也就不误人子弟~
最后再说一个冷门一点的webSocket跨域;原生WebSocket使用起来繁琐也复杂,我们用Socket.io;
这时在浏览器也拿到了数据(注意我浏览器还是80的端口,但node服务器是3000)
常见几种跨域解决方法和会出现的问题就说到这里~
如果想看冷门一点的跨域方法可以查看:跨了个域——冷门篇