跨域

跨域的方法有哪些?原理是什么?

知其然知其所以然,在说跨域方法之前,我们先了解下什么叫跨域,浏览器有同源策略,只有当“协议”、“域名”、“端口号”都相同时,才能称之为是同源,其中有一个不同,即是跨域。

那么同源策略的作用是什么呢?

如果没有同源策略,不同源的数据和资源(如HTTP头、Cookie、DOM、localStorage等)就能相互随意访问,根本没有隐私和安全可言。为了安全起见和资源的有效管理,浏览器必须要采用这种策略。

那么我们又为什么需要跨域呢?

一是前端和服务器分开部署,接口请求需要跨域
二是我们可能会加载其它网站的页面作为iframe内嵌。

跨域的方法有哪些?

  1. jsonp

尽管浏览器有同源策略,但是<script>标签的 src 属性不会被同源策略所约束,可以获取任意服务器上的脚本并执行。jsonp 通过插入script标签的方式来实现跨域,参数只能通过url传入,仅能支持get请求。

实现原理:

Step1: 创建 callback 方法

Step2: 插入 script 标签

Step3: 后台接受到请求,解析前端传过去的 callback 方法,返回该方法的调用,并且数据作为参数传入该方法

Step4: 前端执行服务端返回的方法调用

//前端代码
function jsonp({url, params, cb}) {  
  return new Promise((resolve, reject) => {    
    //创建script标签     
    let script = document.createElement('script');     
    //将回调函数挂在 window 上    
    window[cb] = function(data) {     
      resolve(data);            //代码执行后,删除插入的script标签  
      document.body.removeChild(script);  
    }       
    //回调函数加在请求地址上      
    params = {...params, cb} //wb=b&cb=show      
    let arrs = [];       
    for(let key in params) {        
      arrs.push(`${key}=${params[key]}`);    
    }       
    script.src = `${url}?${arrs.join('&')}`;      
    document.body.appendChild(script);   
  });
}//使用
function sayHi(data) { 
  console.log(data);
}
jsonp({  
  url: 'http://localhost:3000/say', 
  params: {       
    //code   
  },   
  cb: 'sayHi'
}).then(data => {  
  console.log(data);
});
//express启动一个后台服务
let express = require('express');
let app = express();
app.get('/say', (req, res) => { 
  let {cb} = req.query; //获取传来的callback函数名,cb是key 
  res.send(`${cb}('Hello!')`);
});
app.listen(3000);
  1. cors

浏览器将cors请求分为两种: 简单请求、复杂请求

  • 简单请求条件(同时满足以下两大条件):
    1、 HEAD GET POST 请求
    2、 HTTP的头信息为下字段:
    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-ID
    • Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

对于简单请求,浏览器直接发出cors请求(即在头信息中添加origin字段)

  • 复杂请求
  1. 请求方法是PUT/DELETE/CONNECT/OPTIONS/TRACE/PATCH,
  2. Content-Type字段的类型与简单请求相反

复杂请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求,该请求是 option 方法的,通过该请求来判断服务端是否允许跨域请求。
jsonp 只能支持 get 请求,cors 可以支持多种请求。cors 并不需要前端做什么工作。

//简单跨域请求
app.use((req, res, next) => {  
res.setHeader('Access-Control-Allow-Origin', 'XXXX');});

带预检(Preflighted)的跨域请求

不满于简单跨域请求的,即是带预检的跨域请求。服务端需要设置 Access-Control-Allow-Origin (允许跨域资源请求的域) 、 Access-Control-Allow-Methods (允许的请求方法) 和 Access-Control-Allow-Headers (允许的请求头)

app.use((req, res, next) => {   
res.setHeader('Access-Control-Allow-Origin', 'XXX');  
  res.setHeader('Access-Control-Allow-Headers', 'XXX'); //允许返回的头 
  res.setHeader('Access-Control-Allow-Methods', 'XXX');//允许使用put方法请求接口 
  res.setHeader('Access-Control-Max-Age', 6); //预检的存活时间  
  if(req.method === "OPTIONS") {  
    res.end(); //如果method是OPTIONS,不做处理 
  }
});

为什么要发起预检请求?

  1. nginx 反向代理

使用nginx反向代理实现跨域,只需要修改nginx的配置即可解决跨域问题。

A网站向B网站请求某个接口时,向B网站发送一个请求,nginx根据配置文件接收这个请求,代替A网站向B网站来请求。nginx拿到这个资源后再返回给A网站,以此来解决了跨域问题。

例如nginx的端口号为 3001,需要请求的服务器端口号为 3000。(localhost:8090 请求 localhost:3000/say)

nginx配置如下:

server {
    listen    3001;
    server_name  localhost;
    root /Users/qiaojing/Documents/native-demo/cross-domain/nginx;
    index index.html;
    #charset koi8-r;
    #access_log  logs/host.access.log  main;
    location /123 {
        proxy_pass http://127.0.0.1:3000;
    }

4.websocket
Websocket 是 HTML5 的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。
Websocket 不受同源策略影响,只要服务器端支持,无需任何配置就支持跨域。
前端页面在 8080 的端口。

let socket = new WebSocket('ws://localhost:3000'); //协议是ws
socket.onopen = function() {    
socket.send('Hi,你好');
}
socket.onmessage = function(e) {    
console.log(e.data)
}

服务端 3000端口。可以看出websocket无需做跨域配置。

let WebSocket = require('ws');
let wss = new WebSocket.Server({port: 3000});
wss.on('connection', function(ws) {    
ws.on('message', function(data) {       
 console.log(data); //接受到页面发来的消息'Hi,你好'       
  ws.send('Hi'); //向页面发送消息  
    });
 });

5.postMessage
postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:

页面和其打开的新窗口的数据传递
多窗口之间消息传递
页面与嵌套的iframe消息传递
上面三个场景的跨域数据传递

postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。

message: 将要发送到其他 window的数据。
targetOrigin:通过窗口的origin属性来指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示无限制)或者一个URI。在发送消息的时候,如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配targetOrigin提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送。
transfer(可选):是一串和message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。

接下来我们看个例子: http://localhost:3000/a.html页面向http://localhost:4000/b.html传递“我爱你”,然后后者传回"我不爱你"。

// a.html
  <iframe src="http://localhost:4000/b.html" frameborder="0" id="frame" onload="load()"></iframe> //等它加载完触发一个事件
  //内嵌在http://localhost:3000/a.html
    <script>
      function load() {
        let frame = document.getElementById('frame')
        frame.contentWindow.postMessage('我爱你', 'http://localhost:4000') //发送数据
        window.onmessage = function(e) { //接受返回数据
          console.log(e.data) //我不爱你
        }
      }
    </script>
复制代码// b.html
  window.onmessage = function(e) {
    console.log(e.data) //我爱你
    e.source.postMessage('我不爱你', e.origin)
 }

以下三种跨域方式很少用,如有兴趣,可自行查阅相关资料。

1.window.name + iframe
2.location.hash + iframe
3.document.domain (主域需相同)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值