前端跨域解決方案

跨域請(qǐng)求

全稱:非同源策略請(qǐng)求(同源即同協(xié)議、同域名和同端口)曲稼,而ajax專門用于處理同源策略請(qǐng)求索绪,因此對(duì)于非同源請(qǐng)求,特別是在前后端分離的項(xiàng)目當(dāng)中贫悄,我們就會(huì)面臨這類問(wèn)題瑞驱。
這里要聲明一下:跨域是瀏覽器行為,而不是服務(wù)器行為(當(dāng)前臺(tái)向服務(wù)器發(fā)起請(qǐng)求時(shí)清女,后臺(tái)并沒(méi)有阻止請(qǐng)求操作钱烟,但后臺(tái)返回?cái)?shù)據(jù)時(shí),瀏覽器基于安全考慮嫡丙,若非同源請(qǐng)求拴袭,將可能禁止對(duì)請(qǐng)求數(shù)據(jù)的處理)

常用跨域解決方案

基于Jsonp

我們可以發(fā)現(xiàn)在html當(dāng)中含有src屬性的標(biāo)簽,如<srcipt>/<img>/<link>/<iframe>標(biāo)簽曙博,在請(qǐng)求資源時(shí)都不存在跨域請(qǐng)求的限制拥刻。而jsonp則是基于<script>標(biāo)簽去請(qǐng)求資源,服務(wù)器則在返回?cái)?shù)據(jù)資源時(shí)將其包在一個(gè)本地可調(diào)用的全局函數(shù)里返回父泳,然后本地則調(diào)用這個(gè)函數(shù)

原理詳解

首先因由于同源策略般哼,我們無(wú)法通過(guò)ajax請(qǐng)求來(lái)獲取不符合條件的資源,但是假如我們用<script>標(biāo)簽請(qǐng)求的js文件中有這樣的語(yǔ)句:

test({"x": 1})

而我們前臺(tái)的請(qǐng)求如下:

<script src="http://xxx.xxx.com/xxx.js"></script>

那么獲取到請(qǐng)求以后就會(huì)執(zhí)行test這個(gè)函數(shù)惠窄,并將一個(gè)對(duì)象作為參數(shù)傳入蒸眠,此時(shí)如果我們的前臺(tái)本身有test這個(gè)函數(shù),那么就可以成功執(zhí)行這段代碼杆融。換個(gè)說(shuō)法楞卡,如果現(xiàn)在我們通過(guò)訪問(wèn)一個(gè)后臺(tái)接口獲取到跟前面一樣的字符串(之前是通過(guò)訪問(wèn)一個(gè)遠(yuǎn)程js資源得到的),那么因?yàn)槎际怯?code><script>標(biāo)簽獲取的,獲取的結(jié)果也一樣蒋腮,只是請(qǐng)求的url稍微有些改變淘捡,可以發(fā)現(xiàn)結(jié)果都是是一樣的:正常執(zhí)行test這個(gè)函數(shù),例如下面這段代碼:

<script src="http://xxx.xxx.com/api"></script>
<script>
function test(data) {
  console.log(data);
}
</script>

那么控制臺(tái)就會(huì)成功輸出data的內(nèi)容池摧。這就是jsonp的原理焦除,可以看出和ajax請(qǐng)求的本質(zhì)是完全不一樣的,ajax是基于XmlHttpRequest作彤,而jsonp則是基于標(biāo)簽動(dòng)態(tài)請(qǐng)求膘魄,可以說(shuō)是一種偽請(qǐng)求

缺點(diǎn):由于是基于標(biāo)簽的src屬性請(qǐng)求的,只能使用get請(qǐng)求宦棺、并且安全性不好

基于CORS跨域資源共享配置

CORS是十分常用的一種跨域解決方案瓣距,其只需要在服務(wù)端配置對(duì)應(yīng)的響應(yīng)頭屬性即可,而無(wú)需前端做任何操作代咸,最關(guān)鍵的就是在返回頭里配置Access-Control-Allow-Origin屬性蹈丸,舉例(這里基于flask進(jìn)行示例):

from flask import Flask, make_response
import json

app = Flask(__name__)

@app.route('/test', methods=['GET'])
def user_info():
    response = make_response(json.dumps({'data': 'content'}))
    response.headers['Access-Control-Allow-Origin'] = '*'
    return response

if __name__ == '__main__':
    app.run(debug=True, port=5000)

還有一些其他訪問(wèn)控制的配置如下:

Access-Control-Allow-Methods: POST, GET, PUT, DELETE, HEAD, OPTIONS
# 允許的請(qǐng)求方式
#(注意只有POST, GET以及HEAD是默認(rèn)允許的請(qǐng)求,其他的請(qǐng)求都必須先發(fā)送一個(gè)OPTIONS的預(yù)請(qǐng)求
# 當(dāng)預(yù)請(qǐng)求得到認(rèn)可后才可以進(jìn)行請(qǐng)求呐芥,而只有在該配置當(dāng)中配置的方法才會(huì)得到認(rèn)可)
Access-Control-Allow-Headers: 'Content-Type, ...'
# 允許請(qǐng)求的頭部信息逻杖,沒(méi)有設(shè)置在里面的header都是不允許的
Access-Control-Max-Age: '1000'
# 設(shè)置允許跨域的時(shí)間,此時(shí)在有效期內(nèi)無(wú)需再發(fā)送預(yù)請(qǐng)求進(jìn)行驗(yàn)證思瘟,直接發(fā)請(qǐng)求就可以了荸百,單位是s
Access-Control-Allow-Credentials: true
# 是否允許發(fā)送cookie

詳細(xì)參考:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials

缺點(diǎn):cors配置源地址只能配置*(多源)或者一個(gè)地址,并且如果配置的是*滨攻,那么基于安全性問(wèn)題够话,請(qǐng)求時(shí)將無(wú)法攜帶cookie

注:
如果不想配置成*,又希望多個(gè)地址可以光绕,那么可以在程序當(dāng)中判斷訪問(wèn)的地址是否是允許的女嘲,如果是,則加入到cors配置當(dāng)中

基于Http Proxy

由于cors的弊端诞帐,又出了Http Proxy的方案欣尼,需要在webpack中配置,常在基于vue停蕉、react的前后端分離項(xiàng)目中使用愕鼓,首先要安裝對(duì)應(yīng)模塊包:

npm install webpack webpack-dev-server

然后在webpack的配置文件webpack.config.js中配置:

devServer: {
  port: 8080,
  proxy: {
    '/': {
      target: 'http://127.0.0.1:6666',
      // 代理的地址
      // secure: false,
      // 如果是https接口,需要配置這個(gè)參數(shù)
      changeOrigin: true
      // 是否跨域
    }
  }
}
vue-cli中配置

如果是基于vue-cli的項(xiàng)目工程慧起,那么這里介紹兩種方式實(shí)現(xiàn)proxy代理的配置:

  • 第一種:配置devServer菇晃,在build/webpack.base.conf.js中的module.exports進(jìn)行和上面相同的配置
  • 第二種:配置proxyTable,直接在config/index.js中的module.exports.dev配置如下:
module.exports = {
  dev: {
    ...
    proxyTable: {
        // 代理配置
        '/': {
          target: 'http://127.0.0.1:6666',
          changeOrigin: true,
          // pathRewrite: {
          //   '^/apis': '/api'  //重寫(xiě)的路徑  
          // }
        }
      },
  },
  ...
}

注:
配置proxy之后蚓挤,實(shí)際上前端請(qǐng)求還是發(fā)給本身的node服務(wù)器(查看網(wǎng)絡(luò)請(qǐng)求就可以發(fā)現(xiàn))磺送,例如前端是8080剩失,配置了6666的代理,那么前端會(huì)先請(qǐng)求本身册着,即8080,然后8080再通過(guò)node去訪問(wèn)6666脾歧,由于服務(wù)器之間的請(qǐng)求不存在跨域問(wèn)題甲捏,所以6666返回?cái)?shù)據(jù)給node,node再返回給前端鞭执,由于同源司顿,所以此時(shí)數(shù)據(jù)也就沒(méi)有跨域問(wèn)題了
注:
在vue-cli3.0+以后,則直接在項(xiàng)目根目錄下創(chuàng)建文件vue.config.js兄纺,并添加如下內(nèi)容即可:

module.exports = {
  devServer: {
    proxy: {
      "/": {
        target: "http://127.0.0.1:6666/",
        changeOrigin: true
      }
    }
  }
};
基于nginx配置CORS

在nginx中也可以配置cors大溜,舉例:

server {
    listen 80;
    server_name  localhost;
    ...
    
    location / {
        root   html;
        index  index.html index.htm;
        proxy_pass http://xxx;
        add_header Access-Control-Allow-Origin *;  # 配置cors
        ...
    }
基于nginx配置反向代理

假如前臺(tái)服務(wù)端口為http://127.0.0.1:3000,后臺(tái)服務(wù)端接口為http://127.0.0.1:8000/api估脆,那么因?yàn)椴煌辞辗埽厝淮嬖诳缬騿?wèn)題,此時(shí)可以在nginx中進(jìn)行配置如下:監(jiān)聽(tīng)3000端口疙赠,并配置/api的反向代理地址為:http://127.0.0.1:8000/api付材,配置文件示例如下:

server {
    listen 3000;
    server_name  localhost;
    ...
    
    location /api/{
        ...
        proxy_pass http://127.0.0.1:8000/api/;    # 配置反向代理
    }
}

再將前臺(tái)請(qǐng)求的接口改為:http://127.0.0.1:3000/api,即可解決跨域問(wèn)題圃阳。
(原理:經(jīng)過(guò)nginx的反向代理配置厌衔,現(xiàn)在訪問(wèn)http://127.0.0.1:3000/api就相當(dāng)于訪問(wèn)http://127.0.0.1:8000/api,而前臺(tái)請(qǐng)求的url因?yàn)楦某闪?code>http://127.0.0.1:3000/api捍岳,在瀏覽器看來(lái)前臺(tái)和請(qǐng)求接口同源富寿,也就不存在跨域問(wèn)題了)

參考:https://blog.csdn.net/larger5/article/details/81286324

其他跨域解決方案

基于修改本地host文件(不推薦)

例如前臺(tái)地址:127.0.0.1:6666,而后臺(tái)接口地址:http://api.xxx.com锣夹,此時(shí)如果想要訪問(wèn)后臺(tái)接口页徐,可以在本地host文件中加一行:

127.0.0.1:6666  http://api.xxx.com

但這種方式實(shí)際上只是在模仿同源請(qǐng)求,并沒(méi)有實(shí)質(zhì)地解決跨域問(wèn)題

基于Iframe的postMessage

postMessage方法允許頁(yè)面間基于Iframe進(jìn)行消息傳遞晕城,例如A和B頁(yè)面進(jìn)行消息傳遞:

  • a.html
<html>
  <body>
    <iframe
      id="iframe"
      src="http://127.0.0.1:5500/b.html"
      style="display: none;"
    ></iframe>
  </body>
  <script>
    iframe.onload = function() {
      iframe.contentWindow.postMessage("來(lái)自A的信息...", "http://127.0.0.1:5500/b.html");
      // 發(fā)送消息給頁(yè)面B
    };
    // 監(jiān)聽(tīng)頁(yè)面B發(fā)來(lái)的消息
    window.onmessage = function(ev) {
      console.log("A收到B的信息:", ev.data);
    };
  </script>
</html>
  • b.html
<html>
  <body></body>
  <script>
    //   監(jiān)聽(tīng)頁(yè)面A發(fā)來(lái)的消息
    window.onmessage = function(ev) {
      console.log("B收到A的信息:", ev.data);
      ev.source.postMessage("回信...", "*");
    };
  </script>
</html>

參考:https://www.cnblogs.com/yyy6/p/9481671.html

基于H5的web socket

由于原生web socket不太好使用泞坦,這里使用socket.io(對(duì)web socket進(jìn)行了封裝的框架)進(jìn)行示例:

  • 服務(wù)端(基于node):
const server = require("http").createServer();
const io = require("socket.io")(server);
io.on("connection", client => {
  client.on("event", data => {
    console.log(data);
  });
  client.on("message", msg => {
    console.log(msg);
  });
  client.on("disconnect", () => {
    console.log("server has closed!");
  });
});
server.listen(3000);
  • 客戶端:
<html>
  <body></body>
  <script src="https://cdn.bootcss.com/socket.io/2.3.0/socket.io.js"></script>
  <script>
    let socket = io('http://127.0.0.1:3000/');

    socket.on('connect', () => {
      socket.on('message', msg => {
        console.log(msg);
      })

      socket.on('disconnect', () => {
        console.log('server has closed!');
      })
    })

    socket.send('test');

  </script>
</html>

web socket使用參考:https://zhuanlan.zhihu.com/p/74326818
使用WebSocket進(jìn)行跨域數(shù)據(jù)請(qǐng)求參考:https://blog.csdn.net/itkingone/article/details/83818278

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市砖顷,隨后出現(xiàn)的幾起案子贰锁,更是在濱河造成了極大的恐慌,老刑警劉巖滤蝠,帶你破解...
    沈念sama閱讀 221,273評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件豌熄,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡物咳,警方通過(guò)查閱死者的電腦和手機(jī)锣险,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人芯肤,你說(shuō)我怎么就攤上這事巷折。” “怎么了崖咨?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,709評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵锻拘,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我击蹲,道長(zhǎng)署拟,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,520評(píng)論 1 296
  • 正文 為了忘掉前任歌豺,我火速辦了婚禮推穷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘类咧。我一直安慰自己馒铃,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布轮听。 她就那樣靜靜地躺著骗露,像睡著了一般。 火紅的嫁衣襯著肌膚如雪血巍。 梳的紋絲不亂的頭發(fā)上萧锉,一...
    開(kāi)封第一講書(shū)人閱讀 52,158評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音述寡,去河邊找鬼柿隙。 笑死,一個(gè)胖子當(dāng)著我的面吹牛鲫凶,可吹牛的內(nèi)容都是我干的禀崖。 我是一名探鬼主播,決...
    沈念sama閱讀 40,755評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼螟炫,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼波附!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起昼钻,我...
    開(kāi)封第一講書(shū)人閱讀 39,660評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤掸屡,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后然评,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體仅财,經(jīng)...
    沈念sama閱讀 46,203評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評(píng)論 3 340
  • 正文 我和宋清朗相戀三年碗淌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了盏求。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抖锥。...
    茶點(diǎn)故事閱讀 40,427評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖碎罚,靈堂內(nèi)的尸體忽然破棺而出磅废,到底是詐尸還是另有隱情,我是刑警寧澤荆烈,帶...
    沈念sama閱讀 36,122評(píng)論 5 349
  • 正文 年R本政府宣布还蹲,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏姨蟋。R本人自食惡果不足惜冕房,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望驰坊。 院中可真熱鬧,春花似錦、人聲如沸鞋邑。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,272評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)枚碗。三九已至,卻和暖如春铸本,著一層夾襖步出監(jiān)牢的瞬間肮雨,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工箱玷, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留怨规,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,808評(píng)論 3 376
  • 正文 我出身青樓锡足,卻偏偏與公主長(zhǎng)得像波丰,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子舶得,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容