Ajax跨域操作
首先我们得明白,ajax默认情况只能在同一个域下进行操作,换句话说,就是在同一个服务器下我们发起请求和响应请求。
而跨域的意思呢,在不同的服务器下发起请求和响应请求是不被允许的,但是很多时候和有这样一种需求,我们希望在跨域下发起请求和响应请求,我们不得不考虑,如何解决这种跨域的阻挠问题。
什么是跨域?
比如我们访问的是本机的IP
拿到的却是别的服务器响过来的的数据,然后渲染到页面上。
这就是简单的跨域。
如何解决跨域的操作问题?
常见方案有三:一为CORS方案、二为JSONP方案、三为反向代理方案
浏览器为什么会产生跨域的这样一种限制?
我们需要了解一个概念,浏览器的同源策略,而跨域限制就是来自于浏览器的同源策略。
那什么是浏览器同源策略?
浏览器同源策略(Same origin policy)是一种约定,它是浏览器最核心也是最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能就都会受到一些影响。
浏览器同源策略要求有三个:
一个是协议相同、一个是域名相同、最后一个要求是要端口相同,只有这三个硬性要求都OK符合的话,才代表你访问的是同一个域,话句话来说,就是代表你访问的是同一个服务器,同域操作是安全的,不存在跨域问题。但如果这三个要求但凡有一个不符合约定,那都将证明你就是在跨域了的,浏览器会理解跨域是不安全的,不同域就会受到浏览器的限制。
浏览器同源策略图解:
访问请求失败问题,案例演示之你跨域了:
发起axios请求
axios.get('http://127.0.0.1:3000/list')
.then(res => {
console.log(res)
})
后端接口编写
router.get('/list', async (ctx, next) => {
ctx.body = {
errCode:0,
errMsg:"ok",
list:[
{
"userName":"张妙妙",
"age":21,
"gender":"女"
}
]
}
}
我们发起的请求为什么会出现跨域?
我们会容易混淆一个概念,默认下意识地都会以为,本机的localhost:3000等于本机的IP,也就是127.0.0.1:3000的IP地址,其实不然,在真实的服务器上之所以能够实现,是需要域名解析过来的,比如你访问的域名是www.xxxx11.com其实它会去找一个IP地址叫http://192.168.31.10:80的服务器,这就是通过DNS域名解析后的结果。
再来看一下遇到跨域的时候,控制台是如何报错提醒的,它会告诉你,你存在跨域了。
此外,报错信息它还告诉使用CORS方案去解决跨域问题。
1.CORS方案解决跨域问题
CORS:CORS全称是“跨域资源共享”(Cross-origin resource sharing)。它允许浏览器向跨源服务器发出xhr请求,从而客服了Ajax只能同源使用的限制,CORS方案只需要由服务器发送一个响应标头即可。
设置响应头信息
ctx.set('Access-Control-Allow-Origin','*');
参数解释:
Access-Control-Allow-Origin 是响应标头类型的一种,跟请求头一样样的
*号表示通配的意思,也就是说所有请求的url都会访问到这个资源,此外呢,
也可以指定域名的白名单,只允许指定的域名访问,如果不在这个指定的域名访问范围内,
就访问不到跨域请求的数据。
案例演示:
发起请求
axios.get('http://127.0.0.1:3000/list')
.then(res => {
console.log(res)
})
服务器:后端接口代码指定响应标头 Access-Control-Allow-Origin,而且采用通配的方式
router.get('/list', async (ctx, next) => {
console.log(ctx.request.query)
ctx.set('Access-Control-Allow-Origin','*')
ctx.body = {
errCode:0,
errMsg:"ok",
list:[
{"userName":"覃放","age":22}
]
}
})
看一下浏览器响应的信息回没回来。
看到上面这张图,说明采用CORS方案成功解决了请求跨域问题。
再来看一下Network控制台的响应标头。
因为我们使用了通配的方式,无论我们访问哪个域都可以进行响应数据,此外,我们还可以换成指定的域。
ctx.set('Access-Control-Allow-Origin','127.0.0.1:3000')
浏览器也是可以拿到响应信息。
总结:默认情况下不能进行跨域请求,是因为受到浏览器的安全机制的影响,也就是同源策略,它要求我们要三同,分别是协议相同、端口相同、域名相同,这三者相同即同时满足才允许访问我们的资源,但凡有一个要求不满足,都会产生跨域问题。
但是呢,产生了跨域也是可以解决的,其中第一个方案就是CORS方案,它叫资源共享,这个方案操作起来也很简单,直接在服务器设置响应头即可,它有*号这种全域通配方式,还可以特别指定哪些服务器是可以访问的指定域(白名单)。
CORS方案可支持GET请求和POST请求。
2.JSONP方案解决跨域问题:JSONP跨域请求数据
首先我们来了解一下JSONP这种方案。
JSONP:JSONP全称叫(JSON with Padding),把JSON数据当作一种内填充的形式进行使用,是JSON的一种“使用模式”,可用于解决主流浏览器的跨域数据访问的问题,它利用script标签不受浏览器同源策略的影响实现的。
主要是利用script标签来实现,因为script标签在HTML中可以引入任意服务器下的资源。
做法是客户端写一个函数,服务端去调用这个函数。这个函数用script标签来进行加载,这样就解决了浏览器的跨域问题,简单点理解就是JSONP方案解决跨域靠的便是script标签来实现。
代码演示案例:
同域调用
<script type="text/javascript">
function foo(data){
console.log(data)
}
</script>
<script type="text/javascript">
foo({"userName":"qinfubin"})
</script>
不同域调用,即跨域
目录结构
跨域实现:
<script type="text/javascript">
function foo(data){
console.log(data)
}
</script>
<script type="text/javascript" src="http://127.0.0.1:3000/javascripts/data.js">
</script>
我们可以看到使用了script标签后,丝毫不受浏览器同源策略的影响,可以进行跨域请求数据。
到这里我们就会明白了,为什么我们在做项目的时候可以随意引入外部资源而不受浏览器同源策略的限制,例如引入在线CDN形式的vue.min.js | jquery.min.js | axios.min.js等等,而这都将归功于script标签,让你不用关心浏览器同源策略的影响,我们就可以访问到另一个跨域的资源。
到这里,我们更会恍然大悟,原来我们早就用过了JSONP方案引入外部资源了,只是后知后觉。
利用这种script标签的特点我们就实现了JSONP的核心思想,你可以把script标签理解为你手中持着一块令牌,这块令牌它能让你在任何地方都通行,能被浏览器识别,无论你有什么样的需求,都会让你一路畅通无阻(请求数据),这样想一想是不是更容易理解JSONP方案了呢?
上面是一个简单的JSONP方案解决跨域请求数据的案例,但我们实际中不可能仅仅去调用这种JS文件,而往往是一种API的接口(在koa框架中相对于一个路由)。
模仿API案例
编写一个API接口代码去调用定义好的函数:
router.get('/list', async (ctx, next) => {
ctx.body = 'foo({"userName":"my name is qinfubin"})'
})
请求API接口
<script type="text/javascript">
function foo(data){
console.log(data)
}
</script>
<script src="http://127.0.0.1:3000/list"></script>
可见JSONP方案请求接口是最常见的。
对上述那个案例进一步优化,因为我们在前端定义的函数不可能是写死的,后端的接口也不能写死,而该是一种智能生成。
封装一下,更加智能哟!
浏览器发起跨域请求。
<script type="text/javascript">
function jsonp(url, callbackFunc){
let res = /callback=([^&]+)/
//正则拿到子项http://127.0.0.1:3000/list?callback=foo,&代表结束,我们要拿到foo
let fnName = url.match(res)[1] //匹配
console.log(fnName) //拿到的是foo
//创建脚本标签
let script = document.createElement('script')
script.src = url
document.body.append(script)
//生成一个全局的callbackFunc
window[fnName] = callbackFunc
//数据响应完成以后,删除script标签,释放页面资源
script.onload = function(){
this.remove()
}
}
jsonp('http://127.0.0.1:3000/list?callback=foo',function(data) {
console.log(data)
})
</script>
后端接口代码:
router.get('/list', async (ctx, next) => {
let fnName = ctx.request.query.callback//获取浏览器发过来的携带的字段信息:函数名
ctx.body = fnName + '({"userName":"qinfubin"})' //调用前台定义好的函数
})
控制台,前台查看拿到的跨域信息
Network查看请求信息
响应信息
以上案例便是JSONP跨域解决方案的基本使用。
回头看一下JSONP的基本流程,其实我们接口代码也就是服务端要做的事就是把json数据内填充到了函数调用当中。
后端调用该函数,前端定义函数,只要函数一被调用,函数内部的内容就会被执行,也就是拿到了服务端响应回来的数据
JSONP跨域方案总结:利用script标签不受浏览器同源策略的影响来解决跨域请求数据时被限制的问题,优点:兼容性相当好。缺点:只能发送GET请求
3.反向代理数据方案解决跨域。
什么是反向代理数据?
反向代理指的是使用代理服务器来接收客户端上发送的链接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给链接的客户端。
通俗点讲,反向代理就是我们真实的数据并没有存在代理的服务器上,而是存放在另外一台服务器上,跟微商差不多意思,很多微商是没有货源的,当我们去访问该微商的店铺的时候,你可能访问的其实是另外一个微商的资源,然后给你进行返回,这就是反向代理的基本原理。
其实很多案例都可以说明反向代理,比如超市就是一种代理,超市自己有各种各样的商品吗?不,它没有。超市只是个挂名,它要做的就是代理,他去A批发商那里进货可口可乐这种商品,他要去B批发商那里进货阿萨姆奶茶这种商品,以此类推。
反向代理可以干什么?可以用来实现些什么呢?
反向代理可以用来解决内外安全,负载均衡,跨域请求等功能
为什么反向代理可以实现跨域请求?
因为反向代理和JSONP方案一样,都不受浏览器同源策略的影响,我们都知道同源策略其实就是浏览器本身施加的一种安全策略,不过因为服务器上是没有浏览器这个概念的,所以自然而然的服务器就不存在同源策略这种能力了。
而反向代理呢恰好利用了服务器不存在这种同源策略的能力来实现的。
反向代理的原理概括:
服务端不存在同源策略,所以没有跨域限制,所以我们可以在服务器下去访问另一个服务器的资源,以此来实现跨域操作。
案例演示:
准备,在后端引入axios库,首先得安装axios
cnpm install axios -S
npm install axios -S 或者 npm install axios --save
然后随便在网上找一个代理API接口,可以进行访问拿到数据的(我这里拿到了了腾讯网代理新浪的API进行代理测试)。
https://pacaio.match.qq.com/openapi/getWeiboRankingList
如果能安装一个yformater浏览器工具后对j当前页面的son数据进行格式化后看到美化后的结果是这样的。
回到我们koa项目的后端接口代码,代理这个网上找的API接口。
前台发起get请求
<script type="text/javascript">
axios.get('/list').then(res=>{
console.log(res)
})
</script>
后端实现反向代理
const router = require('koa-router')()
const axios = require('axios')
router.get('/list', async (ctx, next) => {
//充当反向代理服务器,中转站
let response = await axios.get('https://pacaio.match.qq.com/openapi/getWeiboRankingList')
ctx.body = response.data //将拿到的JSON数据返回给前台
})
查看控制台响应回来的数据,我们看到反向代理数据成功返回了,我们可以通过服务端访问这种跨域资源。
回看反向代理流程:
梳理,前端发起接口请求,然后后端这个作为反向代理的服务器拿到这个url后,通过中转的方式去访问到另一台服务器上的资源,把这台服务器的资源代理过来后再响应给前台,遍历数据,前台再交到浏览器的手上,然后渲染界面上,这就是反向代理跨域请求的过程。
总结:
反向代理数据经常在前后端分离项目中采用,前端与后端开启两个服务器,通过代理联系在了一起,也就是现在比较流行的前端vue+后端node环境或者前端react+后端node环境,让两个服务器在进行通讯,实现分离式项目的开发。