前端跨域常见的处理方法

浏览器的同源策略:协议+域名+端口 都相同才叫同源,有一个不同就会产生跨域

常见的跨域方法有:

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文件
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)
这样我们可以分别启动两个服务localhost:3000 和localhost:4000 然后在同级目录下定义a.html页面:
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,
具体实现如下: 定义一个白名单
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);
  }
})
如果我们希望除了get post,还支持put delete等方法,在上面的配置下面,添加如下header
res.setHeader("Access-Control-Allow-Methods","PUT,DELETE");
如果我们希望客户端可以向服务端发送cookie,配置如下:
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:发送消息的窗口对象

 

posted @ 2020-11-21 15:06  陌上花开缓缓归!  阅读(282)  评论(0编辑  收藏  举报
Top ↑