前端跨域解決方案

跨域請求

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

常用跨域解決方案

基于Jsonp

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

原理詳解

首先因由于同源策略,我們無法通過ajax請求來獲取不符合條件的資源,但是假如我們用<script>標(biāo)簽請求的js文件中有這樣的語句:

test({"x": 1})

而我們前臺的請求如下:

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

那么獲取到請求以后就會執(zhí)行test這個(gè)函數(shù),并將一個(gè)對象作為參數(shù)傳入,此時(shí)如果我們的前臺本身有test這個(gè)函數(shù),那么就可以成功執(zhí)行這段代碼。換個(gè)說法,如果現(xiàn)在我們通過訪問一個(gè)后臺接口獲取到跟前面一樣的字符串(之前是通過訪問一個(gè)遠(yuǎn)程js資源得到的),那么因?yàn)槎际怯?code><script>標(biāo)簽獲取的,獲取的結(jié)果也一樣,只是請求的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>

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

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

基于CORS跨域資源共享配置

CORS是十分常用的一種跨域解決方案,其只需要在服務(wù)端配置對應(yīng)的響應(yīng)頭屬性即可,而無需前端做任何操作,最關(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)

還有一些其他訪問控制的配置如下:

Access-Control-Allow-Methods: POST, GET, PUT, DELETE, HEAD, OPTIONS
# 允許的請求方式
#(注意只有POST, GET以及HEAD是默認(rèn)允許的請求,其他的請求都必須先發(fā)送一個(gè)OPTIONS的預(yù)請求
# 當(dāng)預(yù)請求得到認(rèn)可后才可以進(jìn)行請求,而只有在該配置當(dāng)中配置的方法才會得到認(rèn)可)
Access-Control-Allow-Headers: 'Content-Type, ...'
# 允許請求的頭部信息,沒有設(shè)置在里面的header都是不允許的
Access-Control-Max-Age: '1000'
# 設(shè)置允許跨域的時(shí)間,此時(shí)在有效期內(nèi)無需再發(fā)送預(yù)請求進(jìn)行驗(yàn)證,直接發(fā)請求就可以了,單位是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è)地址,并且如果配置的是*,那么基于安全性問題,請求時(shí)將無法攜帶cookie

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

基于Http Proxy

由于cors的弊端,又出了Http Proxy的方案,需要在webpack中配置,常在基于vue、react的前后端分離項(xiàng)目中使用,首先要安裝對應(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'  //重寫的路徑  
          // }
        }
      },
  },
  ...
}

注:
配置proxy之后,實(shí)際上前端請求還是發(fā)給本身的node服務(wù)器(查看網(wǎng)絡(luò)請求就可以發(fā)現(xiàn)),例如前端是8080,配置了6666的代理,那么前端會先請求本身,即8080,然后8080再通過node去訪問6666,由于服務(wù)器之間的請求不存在跨域問題,所以6666返回?cái)?shù)據(jù)給node,node再返回給前端,由于同源,所以此時(shí)數(shù)據(jù)也就沒有跨域問題了
注:
在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配置反向代理

假如前臺服務(wù)端口為http://127.0.0.1:3000,后臺服務(wù)端接口為http://127.0.0.1:8000/api,那么因?yàn)椴煌矗厝淮嬖诳缬騿栴},此時(shí)可以在nginx中進(jìn)行配置如下:監(jiān)聽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/;    # 配置反向代理
    }
}

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

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

其他跨域解決方案

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

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

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

但這種方式實(shí)際上只是在模仿同源請求,并沒有實(shí)質(zhì)地解決跨域問題

基于Iframe的postMessage

postMessage方法允許頁面間基于Iframe進(jìn)行消息傳遞,例如A和B頁面進(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("來自A的信息...", "http://127.0.0.1:5500/b.html");
      // 發(fā)送消息給頁面B
    };
    // 監(jiān)聽頁面B發(fā)來的消息
    window.onmessage = function(ev) {
      console.log("A收到B的信息:", ev.data);
    };
  </script>
</html>
  • b.html
<html>
  <body></body>
  <script>
    //   監(jiān)聽頁面A發(fā)來的消息
    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(對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ù)請求參考:https://blog.csdn.net/itkingone/article/details/83818278

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,976評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,958評論 2 373

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