ajax可以让我们实现无刷新进行数据更新的操作,ajax自带的安全防护机制,必须满足同源策略才能访问这一资源,同源策略(协议,域名,端口三者必须完全相同),网站资源如果不是在部署的服务器上就会被ajax的自带安全机制拦截,而大多数的情况下,网站资源跟部署的不会在同一个服务器上,这样可以让网站性能提升,对用户友好,要是想访问不是当前部署服务器上的资源,这就需要跨域操作。
跨域问题的产生以价值意义
什么是同源策略? 协议,域名(ip地址),端口三者必须一致,不一致就是非同源策略 什么是跨域? 跨域的专业名词叫做非同源策略请求,也叫跨域传述,服务器跟服务器之间不存在跨域。 因为浏览器默认是不允许跨域请求的,是浏览器对JavaScript施加的安全限制。 但是服务器跟服务器之间没有施加安全限制所以允许服务器跟服务器之间可以互相请求。 nginx就是采用这种方式 如何解决解决跨域? 前后端代码全部部署到一个Web服务器上就不会产生跨域,因为满足同源策略。 为什么会产生跨域? 为了保证整个项目的性能优化,保证服务器的负载均衡或保证服务器内部资源满足自身使用内存大小和服务器的 高速运作,一般都会进行服务器拆分。 数据调用第三方平台开放接口也属于跨域,如某度,某讯等等这些大公司都有开源的数据接口, 在自己项目中调用第三方平台接口也属于跨域 服务器拆分为哪几种? web服务器:用于存放静态资源 data服务器:用于后台开发,存放业务逻辑和数据分析(包含了对数据库的管理 ) 图片服务器:图片量过多单独拿出服务器进行处理(因为图片请求时间比一般请求时间都长所以需要单独处理) 音频服务器:对音频数量过多的时候单独拿服务器进行处理 视频服务器:对于视频过多的时候单独拿服务器进行处理 对于重点资源或者请求慢的资源进行单独拿出来处理 现在很多项目需要进行项目优化,这就导致了服务器拆分,就产生了跨域传输 如何区分跨域请求还是同源请求? 看三个方面:协议,域名,端口号三者都一样就是同源策略请求,其中一者不同就是跨域请求,需要进行跨域请求处理。
修改本地的HOST文件进行跨域(已淘汰)
最一开始都是采用这个方法,前提必须是部署的时候前后端代码都在一个服务器中。 装一个xampp的软件,这是一个集mysql和apache还有各方面集合在一起的软件,通过xampp构建一个web服务来访问。 通过xampp修改本地的host文件进行访问网络地址。 本地的地址为127.0.0.1:3000通过修改本地的host文件让他指向网络请求地址http:xxx.con 这一种方式还不能算真正跨域方式(假跨域),因为他部署上线的时候还是满足同源策略
第一种:JSONP跨域(不常用,安全性低)
jsonp跨域的原理:通过一些标签自带的跨域属性进行跨域访问资源操作,但是jsonp的方式只支持get方式,通过动态创建一个script标签,script的src路径设置成访问资源路径,后面携带回调函数,这个回调函数必须先定义,然后就能接受服务器返回过来的数据。
jsonp知识
//客户端跟服务端的数据传输都是基于json格式传输,所以采用jsonp跨域这种方式请求回来的数据一定也是json jsonp的实现方案原理: 客户端往服务器发送请求,有些原生标签带有特殊性质如:script,img,link,iframe等等。这些标签都带有不存在跨域请求的限制。 因为这些特性script可以通过src属性引入网络资源文件进行加载。 第一步现在本地创建一个script标签的src等于请求数据接口,后面带过去一个回调函数,回调函数是在本地定义的, 回调函数内部做一些我们对服务器端返回的数据做的一些处理。服务器接收到请求之后,同时也会拿到带过来 的回调函数,把数据转化成json字符串之后在通过回调函数返回给客户端,客户端拿到之后会把通过回调函数返回 的数据之后,浏览器拿到的是一个回调函数调用字符串,然后浏览器会识别js表达式进行执行, 回调函数执行就会进行对服务端请求回来的数据进行处理。 //服务器通过请求拿到的回调函数 请求.query能获取到所有的请求信息 callback=func 准备数据 data={} 给客户端返回数据并处理成字符串 `func(${JSON.stringify(data)})` 注意: 第一点标签必须是无跨域限制的 第二点回调函数必须是全局的,(回调函数执行是在服务器返回数据之后, 浏览器拿到回调函数执行的字符串会识别js表达式进行执行回调函数) 第三点:jsonp需要服务器端的支持 第四点:因为标签带有跨域特性的都是资源文件请求,资源文件请求只能用get请求 这里会有人问浏览器为什么会执行回调函数??? 这里就有涉及到了浏览器的执行机制和渲染机制,去博文看看就知道了
jsonp的使用
//声明函数(必须是全局函数,因为私有函数js会有可能拿不到) handle(res){ //服务器返回的参数 console.log(res) } <script src='http:www.hadf.com?callback=handle'></script> jquey使用jsonp的方法 $ajax({ url:'url', dataType:'jsonp',//设置为jsonp的请求 //jsonp的回调函数会返回给success函数执行 success:res=>{} }) //jquery的jsonp请求会帮我们创建一个全局函数并添加到请求上,回调函数在服务器端拿到数据之后传给success函数执行, //回调函数后面的&是为了防止浏览器缓存所存在的 jquery做的默认事情 第一:默认帮我们创建一个全局函数并添加到请求地址上去 第二:默认帮我们创建一个script标签并把请求地址添加到src上并发送请求 第三:帮我们默认执行全局函数的时候传给success这个函数执行 服务端通过 请求.query能够拿到所有的请求信息
jsonp的安全问题
jsonp是基于script的src发送请求,所有的标签带有跨域特性的都是资源文件请求,所有的资源文件请求都是get请求,jsonp是通过url把函数传给服务器的,可能会被别人做URL劫持,导致数据的不安全,别人劫持之后返回一个木马程序,浏览器也会进行识别执行
第二种:CORS跨域(跨域资源共享,常用)
这个是在服务器上配置请求头信息实现跨域
缺点:设置通配符*后不能携带cookie,如果想要携带必须设置为单源的
//固定写法 返回响应头 *是所以都可访问 response.setHeader('Access-Control-Allow-Origin','*'); //请求头信息自定义 response.setHeader('Access-Control-Allow-Headers','*'); //请求方法自定义 response.setHeader('Access-Control-Allow-Method','*'); //单独设置 response.setHeader('Access-Control-Allow-Origin','http://127.0.0.1:9000');
服务器的代码
//引入cors.js import config form './config.js'; //使用node中间件 app.use((req,res,next)=>{ //把引入的东西都结构出来 const{ ALLOW_ORIGIN, CREDENTIALS, HEADERS, ALLOW_METHODS }=config.CORS; //设置响应头,允许跨域的源 *号是所有都可以访问 Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口) res.header('Access-Control-Allow-Origin',ALLOW_ORIGIN) //是否允许携带跨域资源凭证 res.header('Access-Control-Allow-Credentials',CREDENTIALS) //允许设置自定义请求头 res.header('Access-Control-Allow-Headers',HEADERS) //请求方法 res.header('Access-Control-Allow-Methods',ALLOW_METHODS) //cors每次请求都会发送一个预请求,判断是否可以跨域,拦截该请求,如果是返回为ok req.method==='OPTIONS'?res.send('ok!'):next(); }); app.use(session(config.SEEION)); app.use(bodyParser.urlencoded({ extended:false })) //开启cors之后,在发送跨域请求之前都会发送一个options请求,也叫做试探性请求,主要用于验证是否可以进行跨域,服务器接收到 //试探性请求之后,返回给客户端ok即可,然后在发送真正的请求
前端CORS请求配置代码
config.js //暴露出去 module.exports={ //服务器端口号 PORT:3001, //CORS跨域相关信息 CORS:{ //允许那个源跨域 ALLOW_ORIGIN:"http://127.0.0.1", //设置允许跨域的请求方法 ALLOW_METHODS:'PUT,POST,GET,DELETE,OPTIONS,HEAD', //允许的请求头有哪些 HEADERS:'Content-Type,Content-Length,Authorization,Accept,X-Requested-With', //是不是在跨域请求中可以携带资源凭证,是否允许携带cookie等等 CREDENTIALS:true } //SEESSION存储相关信息 SESSION:{ secret:'zfpx', saveUninitialized:false, resave:false, //cookie cookie:{ maxage:1000 } } }
axios代码
//默认请求的时候添加请求前缀 axios.defaults.baseURL='http://127.0.0.1:6000'; //如果前端配置了这个withCredentials=true,后段设置Access-Control-Allow-Origin不能为 " * ",必须是你的源地址 //在跨域请求时,会携带用户凭证 axios.defaults.withCredentials=true; // axios.defaults.headers['Content-Type']='application/x-www-form-urlencoded'; // axios.defaults.transformRequest=function(data){ if(!data) return data; let result =``; for(let attr in data){ if(!data.hasOwnProperty(attr)) bareak; result+=`&${attr}=${data[attr]}`; } return result.substring(1); }; axios.interceptors.response.use(function onFulfilled(response){ return response.data; },function onRejected(reason){ return Promise.reject(reason); }); axios.defaults.validateStatus=function(status){ return /^(2|3)\d{2}$/.test(status); }
cors的分工
前端:正常发送请求即可 后端:设置响应头,允许那些方式等等(需要处理一下options试探性请求)
CORS的安全问题
Origin字段有俩种设置方案,一种是*一种是具体地址,设置成*号之后,就不可携带跨域资源凭证了(因为设置为所有源请求之后 浏览器为了保证客户端数据安全)
第三种:proxy代理(常用)
需要基于webpack 和webpack-dev-server实现
webpack4.0版本要求必须安装webpack-cli
webpack默认打包src文件夹下的东西
webpack的配置代码都是在webpack.config.js里面配置的
npx webpack-dev-server启动服务(在npm5.2版本以上在本地安装的模块可以采用npx执行)
基于react和vue工程化开发都采用这种方式
部署上线之后会拿node写一个中间层
//webpack的配置代码 //引入路径模块 let path = require('path'); //打包html let HtmlWebpackPlugin=require('html-webpack-plugin'); module.exports={ //指定模式 生产模式 mode:'production', //打包入口 entry:'./src/index.js', //打包出口 output:{ //打包出口文件名 filename:'bundle.min.js', //出口文件访问路径 path:path.resolve(__dirname,'build') }, //安装webpack-dev-server后可以用webpack-dev-server启动一个服务 devServer:{ //端口号 port:3000, //显示打包进度 porgress:true, //指定访问静态资源目录 contentBase:'./build', //proxy代理 proxy:{ //拦截所有以/发生的请求 '/':{ // 请求地址,拦截所有以/发送的请求添加上这个地址并请求 target:'http://127.0.0.1:3211', //改变源地址,开启这个之后dev-server会帮我们启一个服务做一个中层的代理,因为服务器跟服务器之间不存在跨域, //请求到数据之后再返回给客户端,相当于拿node写了一个中间件(类似于node中间件代理的这种方式) cahngeOrigin:true } } }, //插件 plugins:[ //打包html new HtmlWebpackPlugin({ //打包地址 template:'./src/index.html', //打包文件名 filename:'index.html' }) ] } //真正的webpack配置文件需要配置css,js,eslint的处理,性能优化等等
proxy的原理
是基于node中间件进行实现的,proxy相当于node给你模拟了一个nginx服务器请求,服务器请求服务器之间不存在跨域,因为跨域是浏览器对js做的安全限制,proxy请求回来的数据,在返回给客户端,解决跨域
第四种:nginx代理跨域(前端不用,都是后端干的)
先启动一个nginx服务器,nginx服务器上面安装nginx,然后在nginx上面配置一个web服务器,web服务器里面在做反向代理
// www.xxx.com转发到 www.aaa.con发送请求 #proxy服务器 server{ //端口号 listen 80; //当前发送请求的域名 server_name www.xxx.com; //请求http://www.xxx.com:80/api,将该请求转发到www.aaa.con,则保证请求在同一个域,解决跨域问题 //转发 location /api{ //请求的服务器 proxy_pass www.aaa.con; //处理cookie信息进行反向代理 proxy_cookie_deom www.aaa.con www.xxx.com; add_header Access-Control-Allow-Origin www.aaa.con; add_header Access-Control-Allow-Credentials true; } }
实现原理:既然不能跨域请求,我们就通过请求到达服务前部署一个服务,将接口请求进行转发,这就是反向代理,通过一定的转发规则可以将前端的请求转发到其他服务。通过反向代理我们将前端后端项目统一通过反向代理来提供对外的服务,这样前端看上去就跟不存在跨域一样。反向代理过于繁琐,麻烦之处就在于对nginx等反向代理服务的配置,前后端分离大多数采用这种方式。
第五种:postMessage(不常用)
有俩个页面 a和b 俩个页面的数据可以通信,并可以给不同的服务器发送请求
前端代码
//a页面 //a向b拿取数据 //只是向b拿去数据并不想让他这个页面显示 <iframe id='iframe' src='127.0.0.1:2221/message/b.html' style='dislay:none;'></iframe> <srcipt> //iframe引入的b页面加载完毕后在执行 iframe.onload=function(){ //引入b页面里面东西,打印出来的是b页面的window console.log(iframe.contentWindow) //向b页面发送请求 第一个参数是发送的信息,第二个是往哪个源地址发送信息,第二个参数有俩个值第一个是具体的路径,第二个是*所有的 iframe.contentWindow.postMessage('哈喽','127.0.0.1:2221/') } //b页面发送过来信息了,接收 //监听b页面传输过来的信息 window.onmessage=function(ev){ //打印传输过来的信息 console.log(eve.data); } </srcipt>
b页面 <srcipt> //监听a页面传输过来的信息,因为是属于127.0.0.1:2221/地址下的 window.onmessage=function(ev){ //打印传输过来的信息 console.log(eve.data); //ev.source代表哪个源地址发送过来的信息 //给请求过来的源地址返回信息,第一个参数代表返回的数据,第二个参数代表把数据发给谁,ev.origin代表的是发送给请求源,第二个参数也可以写* ev.source.postMessage(ev.data+'12121',ev.origin) } //发送过去之后a页面也进行接收 </srcipt>
> 后台代码 ```c server1.js let express=require('express'); app=express(); app.listen(1001,_=>{ console.log('ok!') }) app.use(express.static('./'));
server2.js let express=require('express'); app=express(); app.listen(2221,_=>{ console.log('ok!') }) app.use(express.static('./'));
访问
127.0.0.1:1001/message/a.html 127.0.0.1:2221/message/b.html
第六种:webSocket协议跨域(scoket.io方式)
用的是H5里面提供的webscoket通信协议来完成的,也可以使用这个实现实时通讯,webSocket是一个服务端跟客户端实时通讯的协议
实时聊天思路:客户端A向服务器发送请求,服务器接收到请求把信息存起来,再把信息返回给客户端B,在没有webscoket之前采用常规轮询和常规链接这种操作很麻烦性能还不好,有了scoket.io特别方便,微信就是采用scoket.io这种方式实现的实时通讯
前端代码
//websocket协议跨域代码 //引入scoket.io.js实现实时通讯 <script src='./socket.io.js'></script> <script > //通过io可以和当前的这个域名建立链接 let scoket=io('http://127.0.0.1/'); //链接成功处理 socket.on('connect',function(){ //监听服务端信息 服务器会实时给我们推送消息 socket.on('message',function(msg){ console.log('data from server'+msg); }); //监听服务端关闭 服务器可能会因为某些原因会中断 socket.on('disconnect',function(){ console.log('server socket has closed!'); }); }); //发送消息给服务器端 scoket.send('hahaha') </script>
后端代码(采用node和express实现)
//首先也得安装一个socket模块,安装上去 //监听socket链接 server是node服务器创建的服务 //'connection'当我们和客户端创立连接之后会触发这个函数执行 socket.listen(server).on('connection',fucntion(client){ //接收信息 client代表的是客户端 'message'监听客户端发送的信息 client.on('message',fucntion(msg){ //给客户端发送返回的信息 client.send(msg+'@@'); }); //断开连接 client.on('disconnect',fucntion(){ console.log('client socket has closed!'); }) })
第七种:document.domain+iframe(基于iframe的方案,不友好)
只能实现同一个主域,不同子域之间的操作
v.xx.com和spost.xx.com这俩同在一个域下,域 xx.com
父页面A //子页面 <iframe src='http://www.2342.com/b.html'></iframe> <script> //设置主域 docment.domain='2342.com'; var user='admin'; </script>
子页面B <script> //设置主域 docment.domain='2342.com'; //获取父页面的数据window.parent,父页面下的user alert(window.parent.user); </script>
document.domain+iframe注意点
必须是同一个主域下面的 限制较多
第八种:window.name+iframe(基于iframe的方案,不友好)
这种方法需要三个页面,porxy和a是同一个域 ,b是另一个域
//封装成函数 let proxy=function(url,callback){ let count=0; let iframe=document.createElement('iframe'); iframe.src=url; iframe.onload=function(){ if(count===0){ iframe.contentWindow.location='http://127.0.0.1:3000/proxy.html'; count++; return; } callback(iframe.contentWindow.name); }; document.body.appendChild(iframe); }
proxy页面 空页面
A页面 //拿到b页面里面的数据 <iframe src='http://http:127.0.0.1:1002/b.html' style='display:none;'></iframe> <script> let count=0; //这个onload会因为第一次进入之后修改src,还会在触发一次onload,所以要使用count进行计算只有第一次触发才会修改src iframe.onload=function(){ if(count===0){ //需要我们把数据先把地址重新指向到同源中 iframe.src='http://127.0.0.1:1001/proxy.html'; } //拿到b页面里面的数据 console.log(iframe.contentWindow.name); } </script>
B页面 <script> //服务器端需要返回给A的信息都在window.name中存储,浏览器天生自带属性name window.name='haha'; </script>
第九种:location.hash+iframe(基于iframe的方案,不友好)
也是需要三个页面,A和C同源,A和B非同源,通过hash值实现的
A页面 //拿到b页面里面的数据 <iframe id='iframe' src='http://http:127.0.0.1:3000/b.html' style='display:none;'></iframe> <script> //拿到iframe let iframe=document.getElementById('iframe'); let flag=0; //向b.html传hash值 //为了防止出现无限调用的情况需要添加一个标记 if(flag===0){ iframe.onload=function(){ iframe.src='http://127.0.0.1:3000/b.html#msg=hahah' } } //开放给同域c.html的回调方法 function func(res){ alert(res) } </script>
B页面 //拿到C页面里面的数据 <iframe id='iframe' src='http://http:127.0.0.1:2000/c.html' style='display:none;'></iframe> <script> let iframe=document.getElementById('iframe'); let flag=0; //监听A传来的HASH值在改变,在传给C.html if(falg===0){ window.onhashchange=function(){ iframe.src='http://127.0.0.1:2000/c.html'+location.hash; } } </script>
C页面 <script> //监听b传来的HASH值 window.onhashchange=function(){ //在通过操作同域A的js回调,将结果传回 上一个页面的上一个页面 这个结果可以是hash也可以是其他 window.parent.parent.func(location.hash); } </script>
实现思路:A向B发送请求,B拿到请求通过HASH的方式传给C,C在通过window.parent.parent调用到A页面的func函数并把数据传过去
这个方法的限制过多
axios发送请求
axios
安装axios.js,axios是基于promise封装的 axios.get('URL').then(res=>{})