前端跨域常见的处理方法
浏览器的同源策略:协议+域名+端口 都相同才叫同源,有一个不同就会产生跨域
常见的跨域方法有:
JsonP cors postMessage document.domain window.name lacation.hash http-proxy nginx websocket
1) JsonP
对于link,script,img是不受同源策略限制的,jsonp的原理 就是通过script不受同源策略限制而现实的。 通过src发送一个请求,如百度搜索的实现就是通过jsonp实现的 具体实现方法:
let script = document.createElement("script"); script.src="https://www.baidu.com/sugrec?prod=pc&wd=dd&cb=show"; document.body.appendChild(script);
上面是去调用百度的jsonp,传入搜索字符串dd,回调函数show,它会返回一个show()包裹的结果对象 所以我们定义show方法,就可以拿到返回值
function show(data){ console.log(data) }
上面的方法实现了通过jsonp的方式跨域,但是写起来相对繁琐,我们将其封装一下,通过下面的方法直接获取
jsonp({ url:"https://www.baidu.com/sugrec", params:{prod="pc",wd="dd"}, cb:"show" }).then(data=>{ console.log(data) })
接下来定义一个jsonp方法
function jsonp({url, params, cb}){ return new Promise((resolve,reject)=>{ params = {...params,cb}; let script = document.createElement("script"); let paramArr = []; for(let key in params){ paramArr.push(`${key}=${params[key]}`); } window[cb] = function(data){ resolve(data); document.body.removeChild(script); } script.src=`${url}?paramArr.join("&")`; document.body.appendChild(script); }) }
2) CORS
通过cors实现跨域,这个主要需要靠服务端支持,设置各种头信息 主要头信息有:
Access-Control-Allow-Origin 这个需要配置白名单,允许跨域的地址 Access-Control-Allow-Methods 允许访问的方法,默认支持get和post Access-Contro-Allow-Headers 允许客户端发送指定头信息 Access-Control-Allow-Credentials 允许客户端发送cookie Access-Control-Allow-Max-Age 每次发送请求的时候,都会先发送一个options请求去预检当前服务器是否允许当前请求方式,max-age设置就是设置在多少秒内不要再发送预检请求 Access-Control-Expose-Headers 允许服务端可以向客户端发送某些响应头信息,且客户端可以获取到该头信息
具体使用方法(我们用express模拟两个不同端口的服务地址):定义1.server.js文件
这样我们可以分别启动两个服务localhost:3000 和localhost:4000 然后在同级目录下定义a.html页面:
let express = require("express"); let app = new express(); app.use(express.static(__dirname)); app.listen(3000);
再定义2.server.js文件:
let express=require("express"); let app = new express(); app.listen(4000)
let xhr = new XMLHttpRequest(); xhr.open("GET","http://localhost:4000/getData"); xhr.onreadystatechange = function(){ if(xhr.readyState==4){ if(xhr.status>=200 && xhr.status
上面的html中,我们向4000端口的地址发送了ajax请求,然后在浏览器中打开localhost:3000/a.html,会报跨域问题,
解决方式,就是在2.server.js中,添加上面的Access-Control-Allow-Origin,
具体实现如下: 定义一个白名单
定义一个中间件
如果我们希望除了get post,还支持put delete等方法,在上面的配置下面,添加如下header
如果我们希望客户端可以向服务端发送cookie,配置如下:
如果希望客户端向服务端发送指定头信息,配置入下:
服务端希望向客户端发送响应头:
完整代码入下:
let whiteLists=["http://localhost:3000"];
app.use(function(req,res,next){ let origin = req.headers.origin; if(whiteList.includes(origin)){ res.setHeader("Access-Control-Allow-Origin", origin); } })
res.setHeader("Access-Control-Allow-Methods","PUT,DELETE");
res.setHeader("Access-Control-Allow-Credentiala",true)
res.setHeader("Access-Control-Allow-Headers","name");// name是指定头信息的名字
res.setHeader("Access-Control-Expose-Headers","cookie");
1.server.js
let express = require("express"); let app = express(); app.use(express.static(__dirname)); // 设置当前路径为静态文件路径 app.listen(3000);
2.server.js
let express = require("express"); let app = new express(); // 服务端解决跨域,需要配置白名单 let whiteLists = ["http://localhost:3000"]; app.use(function(req,res,next){ let origin = req.headers.origin; if(whiteLists.includes(origin)){ res.setHeader("Access-Control-Allow-Origin",origin); res.setHeader("Access-Control-Allow-Headers","name"); // 这里允许设置多个headers,用逗号隔开即可 res.setHeader("Access-Control-Allow-Credentials",true); // 允许客户端发送头信息 res.setHeader("Access-Control-Allow-Methods","PUT"); // 默认支持get和post。如果要支持其他方法,需要设置头信息 // 可以设置options预检请求的有效期,即5秒内不再发出 res.setHeader("Access-Control-Allow-Max-Age",5); res.setHeader("Access-Control-Expose-Headers","cookie"); // 设置服务端允许返回的响应头信息,且客户端可以读取 if(req.method=="options"){ // 如果请求是options预检,直接返回 res.end(); } } // 一定要调用next(),否则下面的方法不会执行 next(); }) app.post("/getData",function(req,res){ res.end("post 已经收到了") }) app.put("/getData",function(req,res){ res.setHeader("cookie","this is server cookie"); // 服务端设置响应头,客户端想要获取,需要设置Access-Control-Expose-Headers res.end("这个是PUT方法") }) app.get("/getData",function(req,res){ console.log(req.headers,"===="); res.end("我不爱你"); }) app.listen("4000");
a.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>CORS处理跨域</title> </head> <body> Hello <script> let xhr = new XMLHttpRequest(); // 向localhost4000发送请求 默认express支持get和post请求, // 如果要修改成其他请求方法 如put delete等,需要服务端设置Allow-Methods头信息 // xhr.open("GET","http://localhost:4000/getData"); xhr.open("GET","http://localhost:4000/getData"); xhr.setRequestHeader("name","fiona"); // 前端设置Header,需要服务端配置Access-Control-Allow-Headers // 前端设置cookie, 必须设置withCredentials, 并且要求服务端设置allow credentials头 document.cookie = "name=fionazhong"; xhr.withCredentials = true; xhr.onreadystatechange = function(){ if(xhr.readyState==4){ if(xhr.status>=200 && xhr.status<300 || xhr.status==304){ console.log(xhr.response); console.log(xhr.getAllResponseHeaders("cookie")) } } } xhr.send(); </script> </body> </html>
3) PostMessage
我们为了实现某些效果,经常会在页面嵌套iframe,那么iframe外面和里面如何通信呢?可以使用下面说的postMessage方法postMessage()允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档,多窗口,跨域消息传递
具体使用方法如下: 新建两个文件,a.html和b.html 在a.html中通过iframe引入b.html,在a.html中通过postMessage给b.html发送消息,然后再b.html中接收消息
消息发送:postMessage(data, origin)
data: 发送的数据
origin:字符串参数,指明目标窗口的源(协议+主机+端口号),这个参数是为了安全考虑,postMessage只会将消息发送给指定的窗口,*表示任意窗口都可以接收消息,/ 表示和当前窗口同源的窗口
a.html:
<iframe src="./subFrame.html" id="subFrame" frameborder="0" onload="loadIframe()"></iframe> <script> function loadIframe(){ var ifm = document.querySelector("#subFrame"); ifm.contentWindow.postMessage({say:"hello"},"*"); } </script>
然后在b.html中接收a.html中发送过来的消息
<div id="message"></div> <script> window.addEventListener("message",function(e){ console.log(e) document.querySelector("#message").innerText = e.data.say; }) </script>
控制台打印出来的e对象为:
其中data:发送的数据
source:发送消息的窗口对象