关于前端:良苦用心啊我把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>

后果如下

结语

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理