当前位置: 欣欣网 > 码农

良苦用心啊!我把7大跨域解决方法原理画成10张图,做成图解!

2024-03-20码农

前言

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

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

截屏2021-10-01 上午7.16.06.png

为什么跨域?

image.png

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

截屏2021-10-01 上午8.50.11.png

如有有一个 不相同 ,就会出现跨域问题,不符合 同源策略 导致的后果有

  • 1、 LocalStorge、SessionStorge、Cookie 等浏览器内存无法跨域访问

  • 2、 DOM节点 无法跨域操作

  • 3、 Ajax请求 无法跨域请求

  • 注意点:一个IP是可以注册多个不同域名的,也就是多个域名可能指向同一个IP,即使是这样,他们也不符合 同源策略

    截屏2021-10-01 上午9.02.55.png

    跨域的时机?

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

  • 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>

    截屏2021-10-01 下午1.37.01.png

    最终,前端确实是跨域报错了。但这不是结果,我们要想知道是哪一个答案,关键在于看后端的node服务那里有没有输出,就一目了然了。所以,答案2才是对的。

    截屏2021-10-01 下午1.38.52.png

    截屏2021-10-01 下午1.41.51.png

    同域情况 && 跨域情况?

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

    截屏2021-10-01 上午9.24.38.png

    解决跨域的方案

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

    JSONP

    前面咱们说了,因为浏览器 同源策略 的存在,导致存在 跨域问题 ,那有没有不受 跨域问题 所束缚的东西呢?其实是有的,以下这三个标签 加载资源路径 是不受束缚的

  • 1、script标签: <script src="加载资源路径"></script>

  • 2、link标签: <link herf="加载资源路径"></link>

  • 3、img标签: <img src="加载资源路径"></img>

  • 而JSONP就是利用了 script src 加载不受束缚,从而可以拥有从 不同的域 拿到数据的能力。但是JSONP需要前端后端配合,才能实现最终的 跨域获取数据

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

    截屏2021-10-01 下午1.22.08.png

    后端代码

    // 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) => {
    returnnewPromise((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'林三心'age23 }, 'callback').then(data => {
    console.log(data) // 林三心今年23岁啦!!!
    })

    截屏2021-10-01 下午1.47.29.png

    JSONP的缺点就是,需要前后端配合,并且只支持 get请求方法

    WebSocket

    WebSocket是什么东西?其实我也不怎么懂,但是我也不会像别人一样把MDN的资料直接复制过来,因为复制过来相信大家也是看不懂的。

    我理解的WebSocket是一种协议(跟http同级,都是协议),并且他可以进行跨域通信,为什么他支持跨域通信呢?我这里找到一篇文章WebSocket凭啥可以跨域?,讲的挺好

    截屏2021-10-01 下午10.02.39.png

    后端代码

    先安装 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

    functionmyWebsocket(url, params{
    returnnewPromise((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'林三心'age23 }).then(data => {
    console.log(data) // 林三心今年23岁啦!!!
    })

    结果如下

    截屏2021-10-01 下午1.47.29.png

    Cors

    Cors,全称是 Cross-Origin Resource Sharing ,意思是 跨域资源共享 ,Cors一般是由后端来开启的,一旦开启,前端就可以跨域访问后端。

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

    截屏2021-10-01 下午6.41.11.png

    后端代码

    // 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);//输入相应的内容
    }
    }

    结果如下

    截屏2021-10-01 下午1.47.29.png

    截屏2021-10-01 下午7.10.57.png

    Node接口代理

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

    截屏2021-10-01 下午8.46.28.png

    后端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 ,并 nodemon 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);//输入相应的内容
    }
    }

    结果如下

    截屏2021-10-01 下午1.47.29.png

    Nginx

    其实 Nginx Node接口代理 是一个道理,只不过Nginx就不需要我们自己去搭建一个中间服务

    截屏2021-10-01 下午8.47.40.png

    先下载 nginx [1] ,然后将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);//输入相应的内容
    }
    }

    结果如下

    截屏2021-10-01 下午1.47.29.png

    postMessage

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

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

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

    截屏2021-10-01 下午9.28.53.png

    // http:127.0.0.1:5500/index.html
    <body>
    <iframesrc="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.html b.sanxin.com/index.html 之间的通信

    其实上面这两个正常情况下是无法通信的,因为他们的 域名 不相同,属于跨域通信

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

    截屏2021-10-01 下午9.58.55.png

    由于本菜鸟暂时没有服务器,所以暂时使用本地来模拟

    // http://127.0.0.1:5500/index.html
    <body>
    <iframesrc="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>

    结果如下

    截屏2021-10-01 下午1.47.29.png

    结语

    如果你觉得此文对你有一丁点帮助,点个赞,鼓励一下林三心哈哈。或者可以加入我的摸鱼群,想进学习群,摸鱼群,请加我的vx,我会定时直播模拟面试,答疑解惑