Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
YvetteLau opened this issue Apr 21, 2019 · 1 comment
Open

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

YvetteLau opened this issue Apr 21, 2019 · 1 comment

Comments

@YvetteLau
Copy link
Owner

No description provided.

@YvetteLau
Copy link
Owner Author

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

那么同源策略的作用是什么呢?同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。

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

跨域的方法有哪些?

常用的跨域方法

  1. jsonp

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

实现原理:

Step1: 创建 callback 方法

Step2: 插入 script 标签

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

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

下面代码仅为说明 jsonp 原理,项目中请使用成熟的库。分别看一下前端和服务端的简单实现:

//前端代码
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);

从今天起,jsonp的原理就要了然于心啦~

  1. cors

jsonp 只能支持 get 请求,cors 可以支持多种请求。cors 并不需要前端做什么工作。

简单跨域请求:

只要服务器设置的Access-Control-Allow-Origin Header和请求来源匹配,浏览器就允许跨域

  1. 请求的方法是get,head或者post。
  2. Content-Type是application/x-www-form-urlencoded, multipart/form-data 或 text/plain中的一个值,或者不设置也可以,一般默认就是application/x-www-form-urlencoded。
  3. 请求中没有自定义的HTTP头部,如x-token。(应该是这几种头部 Accept,Accept-Language,Content-Language,Last-Event-ID,Content-Type)
//简单跨域请求
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,不做处理
    }
});

更多CORS的知识可以访问: HTTP访问控制(CORS)

  1. nginx 反向代理

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

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

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

nginx配置如下:

server {
    listen       8090;

    server_name  localhost;

    location / {
        root   /Users/liuyan35/Test/Study/CORS/1-jsonp;
        index  index.html index.htm;
    }
    location /say {
        rewrite  ^/say/(.*)$ /$1 break;
        proxy_pass   http://localhost:3000;
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
    }
    # others
}
  1. 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'); //向页面发送消息
    });
});
  1. postMessage

postMessage 通过用作前端页面之前的跨域,如父页面与iframe页面的跨域。window.postMessage方法,允许跨窗口通信,不论这两个窗口是否同源。

话说工作中两个页面之前需要通信的情况并不多,我本人工作中,仅使用过两次,一次是H5页面中发送postMessage信息,ReactNative的webview中接收此此消息,并作出相应处理。另一次是可轮播的页面,某个轮播页使用的是iframe页面,为了解决滑动的事件冲突,iframe页面中去监听手势,发送消息告诉父页面是否左滑和右滑。

子页面向父页面发消息

父页面

window.addEventListener('message', (e) => {
    this.props.movePage(e.data);
}, false);

子页面(iframe):

if(/*左滑*/) {
    window.parent && window.parent.postMessage(-1, '*')
}else if(/*右滑*/){
    window.parent && window.parent.postMessage(1, '*')
}

父页面向子页面发消息

父页面:

let iframe = document.querySelector('#iframe');
iframe.onload = function() {
    iframe.contentWindow.postMessage('hello', 'http://localhost:3002');
}

子页面:

window.addEventListener('message', function(e) {
    console.log(e.data);
    e.source.postMessage('Hi', e.origin); //回消息
});
  1. node 中间件

node 中间件的跨域原理和nginx代理跨域,同源策略是浏览器的限制,服务端没有同源策略。

node中间件实现跨域的原理如下:

1.接受客户端请求

2.将请求 转发给服务器。

3.拿到服务器 响应 数据。

4.将 响应 转发给客户端。

不常用跨域方法

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

  1. window.name + iframe

  2. location.hash + iframe

  3. document.domain (主域需相同)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant