前端跨域問題解決及七大跨域原理詳解
咱們做前端的,平時(shí)跟后端對(duì)接接口那是必須的事情,但是可能很多同學(xué)忽略了一個(gè)對(duì)接過程中可能會(huì)發(fā)生的問題——跨域,那跨域到底是啥呢?為什么會(huì)跨域呢?又怎么才能解決呢?
為什么跨域?
為什么會(huì)出現(xiàn)跨域問題呢?那就不得不講瀏覽器的同源策略了,它規(guī)定了協(xié)議號(hào)-域名-端口號(hào)這三者必須都相同才符合同源策略
如有有一個(gè)不相同,就會(huì)出現(xiàn)跨域問題,不符合同源策略導(dǎo)致的后果有
- 1、LocalStorge、SessionStorge、Cookie等瀏覽器內(nèi)存無法跨域訪問
- 2、DOM節(jié)點(diǎn)無法跨域操作
- 3、Ajax請(qǐng)求無法跨域請(qǐng)求
注意點(diǎn):一個(gè)IP是可以注冊(cè)多個(gè)不同域名的,也就是多個(gè)域名可能指向同一個(gè)IP,即使是這樣,他們也不符合同源策略
跨域的時(shí)機(jī)?
跨域發(fā)生在什么時(shí)候呢?我考過很多位同學(xué),得到了兩種答案
- 1、請(qǐng)求一發(fā)出就被瀏覽器的跨域報(bào)錯(cuò)攔下來了(大多數(shù)人回答)
- 2、請(qǐng)求發(fā)出去到后端,后端返回?cái)?shù)據(jù),在瀏覽器接收后端數(shù)據(jù)時(shí)被瀏覽器的跨域報(bào)錯(cuò)攔下來
那到底是哪種呢?我們可以驗(yàn)證下,咱們先npm i nodemon -g,然后創(chuàng)建一個(gè)index.js,然后nodemon index起一個(gè)node服務(wù)
// 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); })
再創(chuàng)建一個(gè)index.html,用來寫前端的請(qǐng)求代碼,咱們就寫一個(gè)簡(jiǎn)單的AJAX請(qǐng)求吧
// index.html http://127.0.0.1:5500/index.html <script> //步驟一:創(chuàng)建異步對(duì)象 var ajax = new XMLHttpRequest(); //步驟二:設(shè)置請(qǐng)求的url參數(shù),參數(shù)一是請(qǐng)求的類型,參數(shù)二是請(qǐng)求的url,可以帶參數(shù) ajax.open('get', 'http://127.0.0.1:8000?name=前端過來的林三心'); //步驟三:發(fā)送請(qǐng)求 ajax.send(); //步驟四:注冊(cè)事件 onreadystatechange 狀態(tài)改變就會(huì)調(diào)用 ajax.onreadystatechange = function () { if (ajax.readyState == 4 && ajax.status == 200) { //步驟五 如果能夠進(jìn)到這個(gè)判斷 說明 數(shù)據(jù) 完美的回來了,并且請(qǐng)求的頁(yè)面是存在的 console.log(ajax.responseText);//輸入相應(yīng)的內(nèi)容 } } </script>
最終,前端確實(shí)是跨域報(bào)錯(cuò)了。但這不是結(jié)果,我們要想知道是哪一個(gè)答案,關(guān)鍵在于看后端的node服務(wù)那里有沒有輸出,就一目了然了。所以,答案2才是對(duì)的。
同域情況 && 跨域情況?
前面提到了同源策略,滿足協(xié)議號(hào)-域名-端口號(hào)這三者都相同就是同域,反之就是跨域,會(huì)導(dǎo)致跨域報(bào)錯(cuò),下面通過幾個(gè)例子讓大家鞏固一下對(duì)同域和跨域的認(rèn)識(shí)把!
解決跨域的方案
跨域其實(shí)是一個(gè)很久的問題了,對(duì)應(yīng)的解決方案也有很多,一起接著往下讀吧!??!
JSONP
前面咱們說了,因?yàn)闉g覽器同源策略的存在,導(dǎo)致存在跨域問題,那有沒有不受跨域問題所束縛的東西呢?其實(shí)是有的,以下這三個(gè)標(biāo)簽加載資源路徑是不受束縛的
1、script標(biāo)簽:<script src="加載資源路徑"></script>
2、link標(biāo)簽:<link herf="加載資源路徑"></link>
3、img標(biāo)簽:<img src="加載資源路徑"></img>
而JSONP就是利用了script的src加載不受束縛,從而可以擁有從不同的域拿到數(shù)據(jù)的能力。但是JSONP需要前端后端配合,才能實(shí)現(xiàn)最終的跨域獲取數(shù)據(jù)。
JSONP通俗點(diǎn)說就是:利用script的src去發(fā)送請(qǐng)求,將一個(gè)方法名callback傳給后端,后端拿到這個(gè)方法名,將所需數(shù)據(jù),通過字符串拼接成新的字符串callback(所需數(shù)據(jù)),并發(fā)送到前端,前端接收到這個(gè)字符串之后,就會(huì)自動(dòng)執(zhí)行方法callback(所需數(shù)據(jù))。
后端代碼
// 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的缺點(diǎn)就是,需要前后端配合,并且只支持get請(qǐng)求方法
WebSocket
我理解的WebSocket是一種協(xié)議(跟http同級(jí),都是協(xié)議),并且他可以進(jìn)行跨域通信。
后端代碼
先安裝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,前端就能跨域請(qǐng)求后端呢?我的理解是:前端跨域訪問到后端,后端開啟Cors,發(fā)送Access-Control-Allow-Origin: 域名 字段到前端(其實(shí)不止一個(gè)),前端瀏覽器判斷Access-Control-Allow-Origin的域名如果跟前端域名一樣,瀏覽器就不會(huì)實(shí)行跨域攔截,從而解決跨域問題。
后端代碼
// 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, { //設(shè)置允許跨域的域名,也可設(shè)置*允許所有域名 'Access-Control-Allow-Origin': 'http://127.0.0.1:5500', //跨域允許的請(qǐng)求方法,也可設(shè)置*允許所有方法 "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 //步驟一:創(chuàng)建異步對(duì)象 var ajax = new XMLHttpRequest(); //步驟二:設(shè)置請(qǐng)求的url參數(shù),參數(shù)一是請(qǐng)求的類型,參數(shù)二是請(qǐng)求的url,可以帶參數(shù) ajax.open('get', 'http://127.0.0.1:8000?name=林三心&age=23'); //步驟三:發(fā)送請(qǐng)求 ajax.send(); //步驟四:注冊(cè)事件 onreadystatechange 狀態(tài)改變就會(huì)調(diào)用 ajax.onreadystatechange = function () { if (ajax.readyState == 4 && ajax.status == 200) { //步驟五 如果能夠進(jìn)到這個(gè)判斷 說明 數(shù)據(jù) 完美的回來了,并且請(qǐng)求的頁(yè)面是存在的 console.log(ajax.responseText);//輸入相應(yīng)的內(nèi)容 } }
Node接口代理
還是回到同源策略,同源策略它只是瀏覽器的一個(gè)策略而已,它是限制不到后端的,也就是前端-后端會(huì)被同源策略限制,但是后端-后端則不會(huì)被限制,所以可以通過Node接口代理,先訪問已設(shè)置Cors的后端1,再讓后端1去訪問后端2獲取數(shù)據(jù)到后端1,后端1再把數(shù)據(jù)傳到前端
后端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); })
創(chuàng)建一個(gè)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, { //設(shè)置允許跨域的域名,也可設(shè)置*允許所有域名 'Access-Control-Allow-Origin': 'http://127.0.0.1:5500', //跨域允許的請(qǐng)求方法,也可設(shè)置*允許所有方法 "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 //步驟一:創(chuàng)建異步對(duì)象 var ajax = new XMLHttpRequest(); //步驟二:設(shè)置請(qǐng)求的url參數(shù),參數(shù)一是請(qǐng)求的類型,參數(shù)二是請(qǐng)求的url,可以帶參數(shù),動(dòng)態(tài)的傳遞參數(shù)starName到服務(wù)端 ajax.open('get', 'http://127.0.0.1:8888?name=林三心&age=23'); //步驟三:發(fā)送請(qǐng)求 ajax.send(); //步驟四:注冊(cè)事件 onreadystatechange 狀態(tài)改變就會(huì)調(diào)用 ajax.onreadystatechange = function () { if (ajax.readyState == 4 && ajax.status == 200) { //步驟五 如果能夠進(jìn)到這個(gè)判斷 說明 數(shù)據(jù) 完美的回來了,并且請(qǐng)求的頁(yè)面是存在的 console.log(ajax.responseText);//輸入相應(yīng)的內(nèi)容 } }
Nginx
其實(shí)Nginx跟Node接口代理是一個(gè)道理,只不過Nginx就不需要我們自己去搭建一個(gè)中間服務(wù)
先下載nginx,然后將nginx目錄下的nginx.conf修改如下:
server{ listen 8888; server_name 127.0.0.1; location /{ proxy_pass 127.0.0.1:8000; } }
最后通過命令行nginx -s reload啟動(dòng)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 //步驟一:創(chuàng)建異步對(duì)象 var ajax = new XMLHttpRequest(); //步驟二:設(shè)置請(qǐng)求的url參數(shù),參數(shù)一是請(qǐng)求的類型,參數(shù)二是請(qǐng)求的url,可以帶參數(shù),動(dòng)態(tài)的傳遞參數(shù)starName到服務(wù)端 ajax.open('get', 'http://127.0.0.1:8888?name=林三心&age=23'); //步驟三:發(fā)送請(qǐng)求 ajax.send(); //步驟四:注冊(cè)事件 onreadystatechange 狀態(tài)改變就會(huì)調(diào)用 ajax.onreadystatechange = function () { if (ajax.readyState == 4 && ajax.status == 200) { //步驟五 如果能夠進(jìn)到這個(gè)判斷 說明 數(shù)據(jù) 完美的回來了,并且請(qǐng)求的頁(yè)面是存在的 console.log(ajax.responseText);//輸入相應(yīng)的內(nèi)容 } }
postMessage
場(chǎng)景:http://127.0.0.1:5500/index.html頁(yè)面中使用了iframe標(biāo)簽內(nèi)嵌了一個(gè)http://127.0.0.1:5555/index.html的頁(yè)面
雖然這兩個(gè)頁(yè)面存在于一個(gè)頁(yè)面中,但是需要iframe標(biāo)簽來嵌套才行,這兩個(gè)頁(yè)面之間是無法進(jìn)行通信的,因?yàn)樗麄兌丝谔?hào)不同,根據(jù)同源策略,他們之間存在跨域問題
那應(yīng)該怎么辦呢?使用postMessage可以使這兩個(gè)頁(yè)面進(jìn)行通信
// 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
場(chǎng)景:a.sanxin.com/index.html 與 b.sanxin.com/index.html之間的通信
其實(shí)上面這兩個(gè)正常情況下是無法通信的,因?yàn)樗麄兊挠蛎幌嗤?,屬于跨域通?/p>
那怎么辦呢?其實(shí)他們有一個(gè)共同點(diǎn),那就是他們的二級(jí)域名都是sanxin.com,這使得他們可以通過document.domain && iframe的方式來通信
以上就是前端跨域問題解決及七大跨域原理詳解的詳細(xì)內(nèi)容,更多關(guān)于前端跨域問題及跨域原理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
微信小程序非跳轉(zhuǎn)式組件授權(quán)登錄的方法示例
這篇文章主要介紹了微信小程序非跳轉(zhuǎn)式組件授權(quán)登錄的方法示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-05-05JavaScript?Echarts柱狀圖label優(yōu)化中問題針對(duì)講解
這篇文章主要介紹了JavaScript?Echarts柱狀圖label優(yōu)化中問題,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2022-12-12javascript實(shí)現(xiàn)字符串反轉(zhuǎn)的方法
這篇文章主要介紹了javascript實(shí)現(xiàn)字符串反轉(zhuǎn)的方法,實(shí)例分析了javascript實(shí)現(xiàn)字符串反轉(zhuǎn)的技巧,需要的朋友可以參考下2015-02-02uniapp頁(yè)面?zhèn)鲄⒌娜N方式實(shí)例總結(jié)
在進(jìn)行頁(yè)面的跳轉(zhuǎn)的時(shí)候,往往需要我們將一些參數(shù)攜帶著傳遞過去這里的class樣式,下面這篇文章主要給大家介紹了關(guān)于uniapp頁(yè)面?zhèn)鲄⒌娜N方式,需要的朋友可以參考下2022-11-11js獲取鼠標(biāo)點(diǎn)擊的對(duì)象,點(diǎn)擊另一個(gè)按鈕刪除該對(duì)象的實(shí)現(xiàn)代碼
下面小編就為大家?guī)硪黄猨s獲取鼠標(biāo)點(diǎn)擊的對(duì)象,點(diǎn)擊另一個(gè)按鈕刪除該對(duì)象的實(shí)現(xiàn)代碼。小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-05-05el表達(dá)式 寫入bootstrap表格數(shù)據(jù)頁(yè)面的實(shí)例代碼
這篇文章主要介紹了el表達(dá)式 寫入bootstrap表格數(shù)據(jù)頁(yè)面的實(shí)例代碼,非常不錯(cuò),具有參考借鑒價(jià)值,需要的的朋友參考下吧2017-01-01JavaScript中全選、全不選、反選、無刷新刪除、批量刪除、即點(diǎn)即改入庫(kù)(在yii框架中操作)的代碼分享
這篇文章主要介紹了JavaScript中全選、全不選、反選、無刷新刪除、批量刪除、即點(diǎn)即改入庫(kù)(在yii框架中操作)的代碼分享,需要的朋友可以參考下2016-11-11javascript自定義in_array()函數(shù)實(shí)現(xiàn)方法
這篇文章主要介紹了javascript自定義in_array()函數(shù)實(shí)現(xiàn)方法,涉及javascript數(shù)組的遍歷與查找相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-08-08