乐趣区

关于前端:良苦用心啊我把7大跨域解决方法原理画成10张图做成图解

前言

大家好,我是林三心。用最通俗易懂的话讲最难的知识点 是我的座右铭,根底是进阶的前提 是我的初衷。

咱们做前端的,平时跟后端对接接口那是必须的事件,然而可能很多同学疏忽了一个对接过程中可能会产生的问题——跨域,那跨域到底是啥呢?为什么会跨域呢?又怎么能力解决呢?

为什么跨域?

为什么会呈现跨域问题呢?那就不得不讲浏览器的 同源策略 了,它规定了 协定号 - 域名 - 端口号 这三者必须都 雷同 才合乎 同源策略

如有有一个 不雷同 ,就会呈现跨域问题,不合乎 同源策略 导致的结果有

  • 1、LocalStorge、SessionStorge、Cookie等浏览器内存无奈跨域拜访
  • 2、DOM 节点 无奈跨域操作
  • 3、Ajax 申请 无奈跨域申请

留神点:一个 IP 是能够注册多个不同域名的,也就是多个域名可能指向同一个 IP,即便是这样,他们也不合乎 同源策略

跨域的机会?

跨域产生在什么时候呢?我考过很多位同学,失去了两种答案

  • 1、申请一收回就被浏览器的跨域报错拦下来了(大多数人答复)
  • 2、申请收回去到后端,后端返回数据,在浏览器接管后端数据时被浏览器的跨域报错拦下来

那到底是哪种呢?咱们能够验证下,咱们先 npm i nodemon -g,而后创立一个index.js,而后nodemon index 起一个 node 服务

// index.js  http://127.0.0.1:8000

const http = require('http');

const port = 8000;

http.createServer(function (req, res) {const { query} = urllib.parse(req.url, true);
    console.log(query.name)
    console.log('到后端喽')
    res.end(JSON.stringify('林三心'));
}).listen(port, function () {console.log('server is listening on port' + port);
})

再创立一个 index.html,用来写前端的申请代码,咱们就写一个简略的AJAX 申请

// index.html  http://127.0.0.1:5500/index.html
<script>
    // 步骤一: 创立异步对象
    var ajax = new XMLHttpRequest();
    // 步骤二: 设置申请的 url 参数, 参数一是申请的类型, 参数二是申请的 url, 能够带参数
    ajax.open('get', 'http://127.0.0.1:8000?name= 前端过去的林三心');
    // 步骤三: 发送申请
    ajax.send();
    // 步骤四: 注册事件 onreadystatechange 状态扭转就会调用
    ajax.onreadystatechange = function () {if (ajax.readyState == 4 && ajax.status == 200) {
            // 步骤五 如果可能进到这个判断 阐明 数据 完满的回来了, 并且申请的页面是存在的
            console.log(ajax.responseText);// 输出相应的内容
        }
    }

</script>

最终,前端的确是跨域报错了。但这不是后果,咱们要想晓得是哪一个答案,关键在于看后端的 node 服务那里有没有输入,就高深莫测了。所以,答案 2 才是对的。

同域状况 && 跨域状况?

后面提到了 同源策略 ,满足 协定号 - 域名 - 端口号 这三者 都雷同 就是 同域 ,反之就是 跨域 ,会导致 跨域报错 ,上面通过几个例子让大家坚固一下对 同域和跨域 的意识把!

解决跨域的计划

跨域其实是一个很久的问题了,对应的解决方案也有很多,一起接着往下读吧!!!

JSONP

后面咱们说了,因为浏览器 同源策略 的存在,导致存在 跨域问题 ,那有没有不受 跨域问题 所解放的货色呢?其实是有的,以下这三个标签 加载资源门路 是不受解放的

  • 1、script 标签:<script src="加载资源门路"></script>
  • 2、link 标签:<link herf="加载资源门路"></link>
  • 3、img 标签:<img src="加载资源门路"></img>

而 JSONP 就是利用了 scriptsrc加载不受解放,从而能够领有从 不同的域 拿到数据的能力。然而 JSONP 须要前端后端配合,能力实现最终的 跨域获取数据

JSONP 艰深点说就是:利用 script 的 src 去发送申请,将一个办法名 callback 传给后端,后端拿到这个办法名,将所需数据,通过字符串拼接成新的字符串callback(所需数据),并发送到前端,前端接管到这个字符串之后,就会主动执行办法callback(所需数据)。老规矩,先上图,再上代码。

后端代码

// index.js  http://127.0.0.1:8000

const http = require('http');
const urllib = require('url');

const port = 8000;

http.createServer(function (req, res) {const { query} = urllib.parse(req.url, true);
    if (query && query.callback) {const { name, age, callback} = query
        const person = `${name}往年 ${age}岁啦!!!`
        const str = `${callback}(${JSON.stringify(person)})` // 拼成 callback(data)
        res.end(str);
    } else {res.end(JSON.stringify('没货色啊你'));
    }
}).listen(port, function () {console.log('server is listening on port' + port);
})

前端代码

// index.html  http://127.0.0.1:5500/index.html

    const jsonp = (url, params, cbName) => {return new Promise((resolve, reject) => {const script = document.createElement('script')
            window[cbName] = (data) => {resolve(data)
                document.body.removeChild(script)
            }
            params = {...params, callback: cbName}
            const arr = Object.keys(params).map(key => `${key}=${params[key]}`)
            script.src = `${url}?${arr.join('&')}`
            document.body.appendChild(script)
        })
    }

    jsonp('http://127.0.0.1:8000', { name: '林三心', age: 23}, 'callback').then(data => {console.log(data) // 林三心往年 23 岁啦!!!})

JSONP 的毛病就是,须要前后端配合,并且只反对get 申请办法

WebSocket

WebSocket 是什么货色?其实我也不怎么懂,然而我也不会像他人一样把 MDN 的材料间接复制过去,因为复制过去置信大家也是看不懂的。

我了解的 WebSocket 是一种协定(跟 http 同级,都是协定),并且他能够进行跨域通信,为什么他反对跨域通信呢?我这里找到一篇文章 WebSocket 凭啥能够跨域?,讲的挺好

后端代码

先装置npm i ws

// index.js  http://127.0.0.1:8000
const Websocket = require('ws');

const port = 8000;
const ws = new Websocket.Server({port})
ws.on('connection', (obj) => {obj.on('message', (data) => {data = JSON.parse(data.toString())
        const {name, age} = data
        obj.send(`${name}往年 ${age}岁啦!!!`)
    })
})

前端代码

// index.html  http://127.0.0.1:5500/index.html


    function myWebsocket(url, params) {return new Promise((resolve, reject) => {const socket = new WebSocket(url)
            socket.onopen = () => {socket.send(JSON.stringify(params))
            }
            socket.onmessage = (e) => {resolve(e.data)
            }
        })
    }
    myWebsocket('ws://127.0.0.1:8000', { name: '林三心', age: 23}).then(data => {console.log(data) // 林三心往年 23 岁啦!!!})

后果如下

Cors

Cors,全称是 Cross-Origin Resource Sharing,意思是 跨域资源共享,Cors 个别是由后端来开启的,一旦开启,前端就能够跨域拜访后端。

为什么后端开启 Cors,前端就能跨域申请后端呢?我的了解是:前端跨域拜访到后端,后端开启 Cors,发送 Access-Control-Allow-Origin: 域名 字段到前端(其实不止一个),前端浏览器判断Access-Control-Allow-Origin 的域名如果跟前端域名一样,浏览器就不会履行跨域拦挡,从而解决跨域问题。

后端代码

// index.js  http://127.0.0.1:8000

const http = require('http');
const urllib = require('url');

const port = 8000;

http.createServer(function (req, res) {
    // 开启 Cors
    res.writeHead(200, {
        // 设置容许跨域的域名,也可设置 * 容许所有域名
        'Access-Control-Allow-Origin': 'http://127.0.0.1:5500',
        // 跨域容许的申请办法,也可设置 * 容许所有办法
        "Access-Control-Allow-Methods": "DELETE,PUT,POST,GET,OPTIONS",
        // 容许的 header 类型
        'Access-Control-Allow-Headers': 'Content-Type'
    })
    const {query: { name, age} } = urllib.parse(req.url, true);
    res.end(`${name}往年 ${age}岁啦!!!`);
}).listen(port, function () {console.log('server is listening on port' + port);
})

前端代码

// index.html  http://127.0.0.1:5500/index.html
    // 步骤一: 创立异步对象
    var ajax = new XMLHttpRequest();
    // 步骤二: 设置申请的 url 参数, 参数一是申请的类型, 参数二是申请的 url, 能够带参数
    ajax.open('get', 'http://127.0.0.1:8000?name= 林三心 &age=23');
    // 步骤三: 发送申请
    ajax.send();
    // 步骤四: 注册事件 onreadystatechange 状态扭转就会调用
    ajax.onreadystatechange = function () {if (ajax.readyState == 4 && ajax.status == 200) {
            // 步骤五 如果可能进到这个判断 阐明 数据 完满的回来了, 并且申请的页面是存在的
            console.log(ajax.responseText);// 输出相应的内容
        }
    }

后果如下

Node 接口代理

还是回到 同源策略 ,同源策略它只是浏览器的一个策略而已,它是限度不到后端的,也就是 前端 - 后端 会被同源策略限度,然而 后端 - 后端 则不会被限度,所以能够通过 Node 接口代理,先拜访已设置 Cors 的后端 1,再让后端 1 去拜访后端 2 获取数据到后端 1,后端 1 再把数据传到前端

后端 2 代码

// index.js  http://127.0.0.1:8000

const http = require('http');
const urllib = require('url');

const port = 8000;

http.createServer(function (req, res) {console.log(888)
    const {query: { name, age} } = urllib.parse(req.url, true);
    res.end(`${name}往年 ${age}岁啦!!!`)
}).listen(port, function () {console.log('server is listening on port' + port);
})

创立一个index2.js,并nodmeon index2.js

后端 1 代码

// index2.js  http://127.0.0.1:8888

const http = require('http');
const urllib = require('url');
const querystring = require('querystring');
const port = 8888;

http.createServer(function (req, res) {
    // 开启 Cors
    res.writeHead(200, {
        // 设置容许跨域的域名,也可设置 * 容许所有域名
        'Access-Control-Allow-Origin': 'http://127.0.0.1:5500',
        // 跨域容许的申请办法,也可设置 * 容许所有办法
        "Access-Control-Allow-Methods": "DELETE,PUT,POST,GET,OPTIONS",
        // 容许的 header 类型
        'Access-Control-Allow-Headers': 'Content-Type'
    })
    const {query} = urllib.parse(req.url, true);
    const {methods = 'GET', headers} = req
    const proxyReq = http.request({
        host: '127.0.0.1',
        port: '8000',
        path: `/?${querystring.stringify(query)}`,
        methods,
        headers
    }, proxyRes => {
        proxyRes.on('data', chunk => {console.log(chunk.toString())
            res.end(chunk.toString())
        })
    }).end()}).listen(port, function () {console.log('server is listening on port' + port);
})

前端代码

// index.html  http://127.0.0.1:5500

// 步骤一: 创立异步对象
    var ajax = new XMLHttpRequest();
    // 步骤二: 设置申请的 url 参数, 参数一是申请的类型, 参数二是申请的 url, 能够带参数, 动静的传递参数 starName 到服务端
    ajax.open('get', 'http://127.0.0.1:8888?name= 林三心 &age=23');
    // 步骤三: 发送申请
    ajax.send();
    // 步骤四: 注册事件 onreadystatechange 状态扭转就会调用
    ajax.onreadystatechange = function () {if (ajax.readyState == 4 && ajax.status == 200) {
            // 步骤五 如果可能进到这个判断 阐明 数据 完满的回来了, 并且申请的页面是存在的
            console.log(ajax.responseText);// 输出相应的内容
        }
    }

后果如下

Nginx

其实 NginxNode 接口代理 是一个情理,只不过 Nginx 就不须要咱们本人去搭建一个两头服务

先下载 nginx,而后将 nginx 目录下的 nginx.conf 批改如下:

    server{
        listen 8888;
        server_name  127.0.0.1;
 
        location /{proxy_pass 127.0.0.1:8000;}
    }

最初通过命令行 nginx -s reload 启动 nginx

后端代码

// index.js  http://127.0.0.1:8000

const http = require('http');
const urllib = require('url');

const port = 8000;

http.createServer(function (req, res) {const { query: { name, age} } = urllib.parse(req.url, true);
    res.end(`${name}往年 ${age}岁啦!!!`);
}).listen(port, function () {console.log('server is listening on port' + port);
})

前端代码

// index.html  http://127.0.0.1:5500

// 步骤一: 创立异步对象
    var ajax = new XMLHttpRequest();
    // 步骤二: 设置申请的 url 参数, 参数一是申请的类型, 参数二是申请的 url, 能够带参数, 动静的传递参数 starName 到服务端
    ajax.open('get', 'http://127.0.0.1:8888?name= 林三心 &age=23');
    // 步骤三: 发送申请
    ajax.send();
    // 步骤四: 注册事件 onreadystatechange 状态扭转就会调用
    ajax.onreadystatechange = function () {if (ajax.readyState == 4 && ajax.status == 200) {
            // 步骤五 如果可能进到这个判断 阐明 数据 完满的回来了, 并且申请的页面是存在的
            console.log(ajax.responseText);// 输出相应的内容
        }
    }

后果如下

postMessage

场景:http://127.0.0.1:5500/index.html页面中应用了 iframe 标签 内嵌了一个 http://127.0.0.1:5555/index.html 的页面

尽管这两个页面存在于一个页面中,然而须要 iframe 标签 来嵌套才行,这两个页面之间是无奈进行通信的,因为他们 端口号 不同,依据 同源策略 ,他们之间存在 跨域问题

那应该怎么办呢?应用 postMessage 能够使这两个页面进行通信

// http:127.0.0.1:5500/index.html

<body>
    <iframe src="http://127.0.0.1:5555/index.html" id="frame"></iframe>
</body>
<script>
    document.getElementById('frame').onload = function () {this.contentWindow.postMessage({ name: '林三心', age: 23}, 'http://127.0.0.1:5555')
        window.onmessage = function (e) {console.log(e.data) // 林三心往年 23 岁啦!!!}
    }
</script>
// http://127.0.0.1:5555/index.html

<script>
        window.onmessage = function (e) {const { data: { name, age}, origin } = e
            e.source.postMessage(`${name}往年 ${age}岁啦!!!`, origin)
        }
</script>

document.domain && iframe

场景:a.sanxin.com/index.htmlb.sanxin.com/index.html之间的通信

其实下面这两个失常状况下是无奈通信的,因为他们的 域名 不雷同,属于跨域通信

那怎么办呢?其实他们有一个共同点,那就是他们的二级域名都是 sanxin.com,这使得他们能够通过document.domain && iframe 的形式来通信

因为本菜鸟临时没有服务器,所以临时应用本地来模仿

// http://127.0.0.1:5500/index.html

<body>
    <iframe src="http://127.0.0.1:5555/index.html" id="frame"></iframe>
</body>
<script>
    document.domain = '127.0.0.1'
    document.getElementById('frame').onload = function () {console.log(this.contentWindow.data) // 林三心往年 23 岁啦!!!}
</script>
// http://127.0.0.1:5555/index.html

 <script>
        // window.name="林三心往年 23 岁啦!!!"
        document.domain = '127.0.0.1'
        var data = '林三心往年 23 岁啦!!!';
</script>

后果如下

结语

退出移动版