前端常用跨域方式(实例展示) CORS、JSONP、Nginx......
我们在写前后端分离的项目时,数据要通过前端页面发送请求接口返回拿到数据后然后渲染到页面上的,在这时如果前端页面所在的服务器和后端的接口服务器不同源的话,就会出现跨域问题,比如这样。 (实例地址)
前端向80后端服务器发送请求 ↓
后端80无跨域处理的服务器: ↓
因为前端页面服务跑在5500端口上,去请求没有经过跨域处理的服务端口为80的接口服务器时,因为端口不同受浏览器同源策略的影响,浏览器会阻止非同源的请求。
同源策略:
- 协议
- 主机
- 端口
只要两个服务器满足以上任意一个条件,那么这两个服务器即非同源,默认情况下浏览器会阻止非同源请求从而使前端页面拿不到数据。
解决方法:
实例解析:
当访问跨域的服务器时,浏览器会检查返回的http响应头
Access-Control-Allow-Origin: “允许被访问的源地址”
Access-Control-Allow-Methods: “允许的请求方法”
Access-Control-Allow-Headers: “允许携带的http请求头字段”
只有当前端页面所发的请求符合这些响应头字段的要求时,浏览器才允许访问成功,否则就会报访问80端口时的错误。
8083服务器设置cors响应头字段使能被跨域访问 ↓
前端页面访问8083经过cors跨域处理的服务器 ↓
nginx代理转发原理是同源策略只限制于浏览器端的服务器,当后端服务器访问后端服务器时即使是非同源也是可以正常访问的,所以我们构建一个8081端口的nginx服务器,并开启cors使得我们前端的5500端口可以访问该8081的服务器,然后再将请求转发给没有cors的80服务器(即 5500 -> 8081 -> 80)
开启8081的nginx服务并设置cors响应头 ↓
前端页面请求8081端口的nginx服务器然后转发给端口80无跨域处理的服务器 ↓
实现原理和nginx差不多,只不过这个代理服务器是我们自己编写实现
自定义端口为8082的代理服务器,设置跨域cors响应头,请求转发给端口为80的无跨域处理服务器 ↓
前端页面请求8082的自定义代理服务器 ↓
该跨域方式原理为script标签可以不受同源策略影响,前端页面定义回调函数并将该回调函数名作为请求url参数名,后端服务器将数据作为回调函数的实参包裹渲染给前端页面,此时触发前端页面的回调函数,形参即为后端传过来的数据
// jsonp跨域原理(通过script标签不受同源策略影响) var script = document.createElement("script"); script.src = 'http://localhost:'+port+'/test?callback=getJsonpData'; document.body.appendChild(script); script.onload = function () { document.body.removeChild(script) } // jsonp方式回调函数 function getJsonpData(data) { let port = document.querySelector('input').value if(data){ let li = document.createElement('li') li.textContent = (port ? port : '80') + ":" + data.messgae document.querySelector("ul").appendChild(li) }else{ let li = document.createElement('li') li.textContent = (port ? port : '80') + ":" + "访问失败" document.querySelector("ul").appendChild(li) } }
后端8084实现jsonp方式的服务器 ↓
前端页面访问端口为8084的json处理的服务器 ↓
该方式的原理是postmessage可以不受同源策略影响,访问iframe页面为服务器端口8085的页面,在该iframe页面中通过postmessage发送数据,然后前端服务器可以通过window.onmessage回调监听发送过来的数据
// 使用ifrmane的postmessage跨域 var iframe = document.createElement("iframe"); iframe.style.display = 'none' iframe.src = 'http://localhost:'+port; document.body.appendChild(iframe) iframe.onload = function(){ // 设置连接超时,3s后iframe服务没有装载则判定为失败 let timeout = setTimeout(()=>{ // 触发超时,把失败信息渲染到页面 let li = document.createElement('li') li.textContent = (port ? port : '80') + ":" + "访问失败" document.querySelector("ul").appendChild(li) // 移除本次添加的iframe元素 document.body.removeChild(iframe) return new Error("加载超时") },3000) // 向8085服务器页面发送postmessage数据 iframe.contentWindow.postMessage('我需要拿8085的数据', 'http://localhost:8085') //发送数据 window.onmessage = function(e) { // 拿到postmessage传来的数据渲染到页面上 let li = document.createElement('li') li.textContent = (port ? port : '80') + ":" + e.data.messgae document.querySelector("ul").appendChild(li) // 连接成功了,清除错误定时器 clearTimeout(timeout) timeout = null // 移除本次添加的iframe元素 document.body.removeChild(iframe) } }
端口为8085的后端服务器渲染对应的postmessage.html给前端请求的iframe页面 ↓
iframe页面发送postmessage数据给前端页面 ↓
前端页面访问 端口为8085的postmess服务器 ↓
websocket不受浏览器同源策略影响,所以我们也可以通过它来跨域
var ws = new WebSocket('ws://localhost:'+port); //监听建立连接 ws.onopen = function(res){ // console.log('连接成功'); // console.log(res); } //监听消息 ws.onmessage = function(res){ // console.log('客户端接收到了服务端发来的消息'); let msg = JSON.parse(res.data).messgae let li = document.createElement('li') li.textContent = (port ? port : '80') + ":" + msg document.querySelector("ul").appendChild(li) } ws.onerror = function(err) { let li = document.createElement('li') li.textContent = (port ? port : '80') + ":" + "访问失败" document.querySelector("ul").appendChild(li) }
端口为8086的服务器实现了websocket跨域访问 ↓
前端页面访问端口为8086的websocket服务器 ↓