溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

分析web前端開發(fā)中跨域問題

發(fā)布時(shí)間:2021-11-06 14:28:09 來源:億速云 閱讀:109 作者:iii 欄目:web開發(fā)

本篇內(nèi)容介紹了“分析web前端開發(fā)中跨域問題”的有關(guān)知識,在實(shí)際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

什么同源策略

同源策略:同源策略(Same origin policy)是一種約定,它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,則瀏覽器的正常功能可能都會受到影響??梢哉fWeb是構(gòu)建在同源策略基礎(chǔ)之上的,瀏覽器只是針對同源策略的一種實(shí)現(xiàn)。

同源策略是一種約定,它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,瀏覽器很容易受到XSS、CSRF等攻擊。所謂同源是指協(xié)議+域名+端口三者相同,即便兩個(gè)不同的域名指向同一個(gè)ip地址,也非同源。

Url組成部分

了解同源策略以后,同樣需要對url的組成部分也順帶了解一下吧,只有了解url之后當(dāng)出現(xiàn)跨域的時(shí)候才知道哪里出了問題,這樣才能和后端、運(yùn)維開懟,懟天懟地對空氣。O(∩_∩)O哈哈~

分析web前端開發(fā)中跨域問題

從上圖中能夠清晰的看出url中每個(gè)部分的含義:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. protocol:協(xié)議常用的協(xié)議是http

  3. auth:驗(yàn)證,因?yàn)槊魑膫鬏斢脩裘兔艽a,非HTTPS環(huán)境下很不安全,一般用的非常少

  4. hostname:主機(jī)地址,可以是域名,也可以是IP地址

  5. port:端口http協(xié)議默認(rèn)端口是:80端口,如果不寫默認(rèn)就是:80端口

  6. pathname:路徑網(wǎng)絡(luò)資源在服務(wù)器中的指定路徑

  7. serarch:查詢字符串如果需要從服務(wù)器那里查詢內(nèi)容,在這里編輯

  8. hash:哈希網(wǎng)頁中可能會分為不同的片段,如果想訪問網(wǎng)頁后直接到達(dá)指定位置,可以在這部分設(shè)置

項(xiàng)目過程過程中經(jīng)常會用到一些緩存,瀏覽器為了網(wǎng)頁的安全在緩存的時(shí)候,由于同源策略的問題對其緩存內(nèi)容進(jìn)行了限制,其實(shí)想想也是對的,如果不使用同源策略的話,很容易沖掉緩存的東西。

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. Cookie、LocalStorage和IndexDB等無法讀取。

  3. DOM無法獲得。

  4. AJAX請求不能發(fā)送。

當(dāng)然瀏覽器也沒有把所有的東西都限制了,比如圖片、互聯(lián)網(wǎng)資源等還是允許跨域請求的。允許跨域請求都是使用標(biāo)簽,只有三個(gè)標(biāo)簽是允許跨域加載資源:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. <img src=XXX>

  3. <link href=XXX>

  4. <script src=XXX>

在項(xiàng)目開發(fā)過程中時(shí)不時(shí)的就會遇到下面這樣拋出了錯(cuò)誤,有的人可能在開發(fā)過程中沒有遇到過,如果是的話,你可能遇到一個(gè)很不錯(cuò)的后端或者運(yùn)維。

XMLHttpRequest cannot loadhttp://www.******.com/. No 'Access-Control-Allow-Origin'   header is present on the requested resource. Origin 'null' is therefore not allowed access.

上面的報(bào)錯(cuò)就是典型的跨域報(bào)錯(cuò),既然跨域這么常見到底都有哪些情況會導(dǎo)致跨域的問題:

說明是否允許通信
同一域名下允許
同一域名下不同文件夾允許
同一域名,不同端口不允許
同一域名,不同協(xié)議不允許
域名和域名對應(yīng)ip不允許
主域名相同,子域名不同不允許
同一域名,不同二級域名不允許
不同域名不允許

跨域解決方案

由于瀏覽器的限制造成了很多的跨域問題,同樣也是為了安全,既然出現(xiàn)了跨域就必定要有一些對應(yīng)的解決方案,總不能遇到跨域之后項(xiàng)目就不做了啊,可能瞬間就涼了。閑話就不多扯了。

JSONP

在遇到跨域的時(shí)候經(jīng)常會提及到的一個(gè)詞就是JSONP,一直在說JSONP?可是通過什么原理來實(shí)現(xiàn)的呢?我覺得應(yīng)該了解一下到底什么再去了解一下實(shí)現(xiàn)固然原理也就懂得咯。

什么是JSONP

JSONP:JSON的一種“使用模式”,可用于解決主流瀏覽器的跨域數(shù)據(jù)訪問的問題。由于同源策略,一般來說位于server1.example.com的網(wǎng)頁無法與不是server1.example.com的服務(wù)器溝通,而HTML的<script>元素是一個(gè)例外。利用<script>元素的這個(gè)開放策略,網(wǎng)頁可以得到從其他來源動態(tài)產(chǎn)生的JSON資料,而這種使用模式就是所謂的JSONP。用JSONP抓到的資料并不是JSON,而是任意的JavaScript,用JavaScript直譯器執(zhí)行而不是用JSON解析器解析。 - 選自百度百科

對于JSONP簡單的百度了一下,百度給出的解釋如上,看完整段話,有一些小的收獲,第一script標(biāo)簽具有開放策略,可以使用src的開放性解決其跨域問題。在這里簡單的闡述一下個(gè)人觀點(diǎn)。JSONP可以分為兩個(gè)部分來解讀,JSON與padding,JSON固然就不用解釋了,只是一種數(shù)據(jù)格式,padding在css中是內(nèi)填充的意思,其實(shí)JSONP的原理與內(nèi)填充有些類似。通過把數(shù)據(jù)填充js文件中然后引入到頁面中,并在頁面中使用。

有沒有注意過百度,其實(shí)百度的即時(shí)搜索就是使用JSONP來實(shí)現(xiàn)的,可以嘗試一下,在百度中搜索一下,就會在Network中看到一個(gè)以sugrec為開頭的請求,這個(gè)請求就是使用的JSONP的形式,為了大家方便特意截選了一個(gè)段連接。

連接:  https://www.baidu.com/sugrec?prod=pc&wd=json&pwd=json&cb=query  返回格式:  query({      "q": "json",      "p": false,      "g": [{          "type": "sug",          "sa": "s_1",          "q": "json格式"      }, {          "type": "sug",          "sa": "s_2",          "q": "jsonp"      }, {          "type": "sug",          "sa": "s_3",          "q": "json解析"      },      ...]  })

通過對百度的即時(shí)搜索的分析就可以簡單的看出JSONP的實(shí)現(xiàn)原理,請求會的js文件中包含一個(gè)函數(shù),其函數(shù)名稱就是連接中cb的參數(shù)最為參數(shù)傳給后臺,后臺通過處理并在執(zhí)行這個(gè)與參數(shù)對應(yīng)的函數(shù)的,當(dāng)函數(shù)執(zhí)行的時(shí)候?qū)褦?shù)據(jù)以實(shí)參的形式傳遞給對應(yīng)的函數(shù),解決跨域問題。為了方便閱讀這里只截取了代碼片段。

案例:

前端代碼:

$('#btn').click(function(){      var frame = document.createElement('script');      frame.src = 'http://localhost:5000/jsonp?name=aaron&age=18&callback=query';      $('body').append(frame);  });  function query(res){      console.log(res.message+res.name+'你已經(jīng)'+res.age+'歲了');  }

后端代碼:

router.get('/jsonp', (req, res) => {      let {name,age,callback} = req.query;      let data = {message:'success',name,age};      data = JSON.stringify(data);      res.end(`${callback}(${data})`);  });

通過如上代碼就可以簡單的實(shí)現(xiàn)JSONP,雖然JSONP解決了跨域的問題,還是有很多弊端的,比如會在頁面中添加一些script標(biāo)簽,數(shù)據(jù)不能雙向操作等等。

使用JSONP的時(shí)候尤其要注意一點(diǎn),一定要把插入的script放到所應(yīng)用函數(shù)的下面。這個(gè)和js的執(zhí)行順序有關(guān)系,如果把script標(biāo)簽放在上面的話,其方法還沒有被讀取在script標(biāo)簽中就執(zhí)行了這個(gè)方法必定報(bào)錯(cuò)的,這點(diǎn)很重要哦。

document.domain

document.domain項(xiàng)目中一般應(yīng)用的較少,默認(rèn)情況下document.domain存放的是載入文檔的服務(wù)器的主機(jī)名??梢栽诳刂婆_輸出一下,得到的則是segmentfault.com這個(gè)域名。我在項(xiàng)目中所用到的則是結(jié)合iframe的時(shí)候遇到的跨域,并使用的domain解決的。

在使用document.domain實(shí)現(xiàn)跨域的時(shí)候需要注意一下,這兩個(gè)域名必須屬于同一個(gè)一級域名!而且所用的協(xié)議,端口都要一致,否則無法利用document.domain進(jìn)行跨域。Javascript出于對安全性的考慮,而禁止兩個(gè)或者多個(gè)不同域的頁面進(jìn)行互相操作。而相同域的頁面在相互操作的時(shí)候不會有任何問題。

簡單的解釋一下,例如想要在www.a.com中將看到segmentfault.com中的內(nèi)容并將其網(wǎng)頁使用iframe將其嵌入到其網(wǎng)頁中,但是此時(shí)瀏覽器是不允許通過JavaScript直接操作segmentfault.com的,因?yàn)檫@兩個(gè)頁面屬于不同的域,在操作之前瀏覽器會檢測是否符合同源策略,如果符合則允許操作,反之則不行。

若想要同過document.domain實(shí)現(xiàn)跨域的話,必須使其滿足同源策略,這個(gè)時(shí)候就需要用到document.domain,document.domain都設(shè)成相同的域名就可以了。但要注意的是,document.domain的設(shè)置是有限制的,我們只能把document.domain設(shè)置成自身或更高一級的父域,且主域必須相同。

例如:

a.com  news.a.com

news.a.com屬于a.com的一個(gè)子域名,按照上面所說已經(jīng)滿足了上面的規(guī)則,如果想要實(shí)現(xiàn)跨域操作就需要對接子頁面的document.domain進(jìn)行操作。

父頁面:

document.domain = 'a.com';  var ifr = document.createElement('iframe');  ifr.src = 'news.a.com/map.html';  ifr.style.display = 'none';  document.body.appendChild(ifr);  ifr.onload = function(){      var doc = ifr.contentDocument || ifr.contentWindow.document;      var oUl = doc.getElementById('ul1');      alert(oUl.innerHTML);      ifr.onload = null;  };

子頁面:

document.domain = 'a.com';  $ajax.get({      //  ...省略  })

其實(shí)現(xiàn)原理就是通過iframe載入一個(gè)與你想要通過ajax獲取數(shù)據(jù)的目標(biāo)頁面處在相同的域的頁面,所以這個(gè)iframe中的頁面是可以正常使用ajax去獲取你要的數(shù)據(jù)的,然后就是通過我們剛剛講得修改document.domain的方法,讓我們能通過js完全控制這個(gè)iframe,這樣我們就可以讓iframe去發(fā)送ajax請求,然后收到的數(shù)據(jù)我們也可以獲得了。

location.hash

若理解了document.domain實(shí)現(xiàn)跨域原理,那么location.hash也就很號理解了,其原理與document.domain很相似一樣都是動態(tài)插入一個(gè)iframe,然后把iframe的src指向服務(wù)端地址,而服務(wù)端同樣都是輸出一段JavaScript代碼,同樣都是利用和子窗口之間的通信完成數(shù)據(jù)傳輸,同樣要針對同源策略做出處理。

既然說到了hash到底什么是hash這里也就單獨(dú)的說一下吧,雖然很好理解,但是對于新同學(xué)來說可能還不知道hash具體是什么?

hash:一般翻譯做散列、雜湊,或音譯為哈希,是把任意長度的輸入(又叫做預(yù)映射pre-image)通過散列算法變換成固定長度的輸出,該輸出就是散列值。這種轉(zhuǎn)換是一種壓縮映射,也就是,散列值的空間通常遠(yuǎn)小于輸入的空間,不同的輸入可能會散列成相同的輸出,所以不可能從散列值來確定唯一的輸入值。簡單的說就是一種將任意長度的消息壓縮到某一固定長度的消息摘要的函數(shù)。 -- 節(jié)選自百度百科

讀完之后感覺自己整個(gè)人都不好了,有些似懂非懂的意思,我所理解的哈希是指一個(gè)過程,這個(gè)過程就是把任意長度的輸入,通過哈希算法,變換成固定長度的輸出,所輸出的稱為哈希值。這種變換是一種壓縮映射,也即哈希值所占的空間一般來說遠(yuǎn)小于輸入值的空間,不同的輸入可能會哈希出相同的輸出(概率很小)。

哈希有如下特點(diǎn):

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. 如果兩個(gè)哈希值是不相同的(根據(jù)同一函數(shù)),那么這兩個(gè)散列值的原始輸入一定是不相同的。

  3. 如果兩個(gè)哈希值相同,兩個(gè)輸入值很可能(極大概率)是相同的,但也可能不同,這種情況稱為“哈希碰撞”

  4. 抗篡改能力:對于一個(gè)數(shù)據(jù)塊,哪怕只改動其一個(gè)比特位,其hash值的改動也會非常大。

  5. 它是一種單向函數(shù)是“非對稱”的,即它是一個(gè)從明文到密文的不可逆的映射,只有加密過程,沒有解密過程。

那么哈希在平時(shí)項(xiàng)目開發(fā)中有什么用途呢?可以用哈希來做什么事情?對于前端來說用到哈希最多的時(shí)候可能就是錨點(diǎn)定位。通過不同的哈希值定位到描點(diǎn)指定的元素位置上。

<a href='#1'>red</a>  <a href='#2'>black</a>  <a href='#3'>yellow</a>  <a href='#4'>pink</a>  <div id='1' style='width:500px;height:200px;background-color:red'> </div>  <div id='2' style='width:500px;height:200px;background-color:black'> </div>  <div id='3' style='width:500px;height:200px;background-color:yellow'> </div>  <div id='4' style='width:500px;height:1200px;background-color:pink'> </div>

關(guān)于更多細(xì)節(jié)的東西不再這里贅述了,如果想要了解更多的話大家可以自行g(shù)oogle,再說下去的話可能就跑題了。

簡單的介紹了一下哈希與哈希的用處那么又該如何使用哈希來實(shí)現(xiàn)跨域呢?其實(shí)很簡單,如果index頁面要獲取遠(yuǎn)端服務(wù)器的數(shù)據(jù),動態(tài)插入一個(gè)iframe,將iframe的src屬性指向服務(wù)端地址。這時(shí)top window和包裹這個(gè)iframe的子窗口由于同源策略的原因是不能直接通信的,所以改變子窗口的路徑就行了,將數(shù)據(jù)當(dāng)做改變后的路徑的hash值加在路徑上,然后就能通信了,將數(shù)據(jù)加在index頁面地址的hash值上。index頁面監(jiān)聽地址的hash值變化然后做出判斷,處理數(shù)據(jù)。

父頁面:

<iframe src="http://localhost:7000/b.html#key=1&key1=2"></iframe>

由于哈希值的改變不會改變網(wǎng)頁的網(wǎng)址的,所以服務(wù)端可以通過獲取到哈希來解析url中的參數(shù),并把數(shù)據(jù)返回給前端即可。通過parent.location.hash去改變哈希值,然后就可以像document.domain一樣去獲取到子頁面的數(shù)據(jù)了。parent.location.hash該方法是有局限性的,在IE和Chrome中是不支持這種操作的。那么整個(gè)問題應(yīng)如何解決呢?

在同域的域名下面添加一個(gè)*.html(*代表任意名)文件,然后把通過iframe把*.html引入到父頁面中,并把需要請求的接口iframe添加到*.html中去請求,這樣就可以解決了。

http://localhost:6000/a.html

<!DOCTYPE html>  <html>  <head>  <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />  <title>無</title>  </head>  <body>  <script type="text/javascript">  function sendRequest(){      var ifr = document.createElement('iframe');      ifr.style.display = 'none';      ifr.src = 'http://localhost:7000/b.html#Aaron';      document.body.appendChild(ifr);  }  function checkHash(){      var data = location.hash?location.hash.substring(1):'';      if(data) location.hash = '';  }  setInterval(checkHash,1000);  window.onload = sendRequest;  </script>  </body>  </html>

http://localhost:7000/b.html

<!DOCTYPE html>  <html>  <head>  <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />  <title>無</title>  </head>  <body>  <script type="text/javascript">  function checkHash(){      var data = '';      switch(location.hash){          case '#Aaron':                data = 'my Aaron';                break;          case '#Angie':                data = 'my Angie';                break;          default : break;      }      data && callBack('#'+data);  }  function callBack(hash){     var proxy = document.createElement('iframe');     proxy.style.display = 'none';     proxy.src = 'http://localhost/c.html'+hash;     document.body.appendChild(proxy);  }  window.onload = checkHash;  </script>  </body>  </html>

http://localhost:6000/c.html

<!DOCTYPE html>  <html>  <head>  <meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />  <title>無</title>  </head>  <body>  <script type="text/javascript">  parent.parent.location.hash = self.location.hash.substring(1);  </script>  </body>  </html>

a.html中有一個(gè)隱藏的iframe,該iframe指向異域http://localhost:7000/b.html的b.html,且傳遞hash值給b.html`b.html獲取hash值,生成data值,然后動態(tài)創(chuàng)建iframe,該iframe將data值傳給與a.html同域的c.html 因?yàn)閏.html與a.html`同域,可以傳值固然也就解決了跨域問題。

window.name

window.name這個(gè)屬性不是一個(gè)簡單的全局屬性只要在一個(gè)window下,無論url怎么變化,只要設(shè)置好了window.name,那么后續(xù)就一直都不會改變,同理,在iframe中,即使url在變化,iframe中的window.name也是一個(gè)固定的值,利用這個(gè),我們就可以實(shí)現(xiàn)跨域了。

http://localhost:6000/a.html

<iframe src="http://localhost:7000/b.html" frameborder="1"></iframe>  <script>  var ifr = document.querySelector('iframe')  ifr.style.display = 'none'  var flag = 0;  ifr.onload = function () {      if (flag == 1) {          ifr.contentWindow.close();      } else if (flag == 0) {          flag = 1;          ifr.contentWindow.location = 'http://localhost:6000/proxy.html';      }  }  </script>

http://localhost:7000/b.html

var person = {    name: 'Aaron',    age: 18  }  window.name = JSON.stringify(person)

http://localhost:6000/proxy.html

<!DOCTYPE html>  <html lang="en">  <head>  <meta charset="UTF-8">  <title>proxy</title>  </head>  <body>  <p>這是proxy頁面</p>  </body>  </html>

在http://localhost:6000下有一個(gè)a.html,在http://localhost:7000下有一個(gè)b.html,在http://localhost:6000/a.html中創(chuàng)建了一個(gè)iframe標(biāo)簽并把地址指向了http://localhost:7000/b.html,在b.html中的window.name賦值保存了一段數(shù)據(jù),但是現(xiàn)在還獲取不了,因?yàn)槭强缬虻?,所以,我們可以把src設(shè)置為當(dāng)前域的http://localhost:6000/proxy.html,雖然域名改變了但是window.name是沒有改變的。這樣就可以拿到我們想要的數(shù)據(jù)了。

postMessage(HTML5)

可能很多不知道postMessage整個(gè)API,在HTML5中新增了postMessage方法允許來自不同源的腳本采用異步方式進(jìn)行有限的通信,可以實(shí)現(xiàn)跨文本檔、多窗口、跨域消息傳遞,postMessage在很多瀏覽器中都已經(jīng)得到了良好的支持,所以可放心的使用。該方法可以通過綁定window的message事件來監(jiān)聽發(fā)送跨文檔消息傳輸內(nèi)容。

postMessage()方法接受兩個(gè)參數(shù)

 1.  data:要傳遞的數(shù)據(jù),html5規(guī)范中提到該參數(shù)可以是JavaScript的任意基本類型或可復(fù)制的對象,然而并不是所有瀏覽器都做到了這點(diǎn)兒,部分瀏覽器只能處理字符串參數(shù),所以我們在傳遞參數(shù)的時(shí)候需要使用JSON.stringify()方法對對象參數(shù)序列化,在低版本IE中引用json2.js可以實(shí)現(xiàn)類似效果。

 1.  origin:字符串參數(shù),指明目標(biāo)窗口的源,協(xié)議+主機(jī)+端口號+URL,URL會被忽略,所以可以不寫,這個(gè)參數(shù)是為了安全考慮,postMessage()方法只會將message傳遞給指定窗口,當(dāng)然如果愿意也可以建參數(shù)設(shè)置為"*",這樣可以傳遞給任意窗口,如果要指定和當(dāng)前窗口同源的話設(shè)置為"/"。

http://localhost:6000/a.html

<!DOCTYPE HTML>  <html>  <head>  <meta charset="utf-8">  <title>無</title>  </head>  <body>  <div>      <input id="text" type="text" value="My name&rsquo;s Aaron" />      <button id="send" >發(fā)送消息</button>  </div>  <iframe id="receiver" src="http://localhost:7000/b.html"></iframe>  <script>  window.onload = function() {      var receiver = document.getElementById('receiver').contentWindow;      var btn = document.getElementById('send');      btn.addEventListener('click', function (e) {          e.preventDefault();          var val = document.getElementById('text').value;          receiver.postMessage("Hello "+val+"!", "http://localhost:7000");      });  }  </script>  </body>  </html>

http://localhost:7000/b.html

<!DOCTYPE HTML>  <html>  <head>  <meta charset="utf-8">  <title>無</title>  </head>  <body>  <div id="message">      Hello World!  </div>  <script>  window.onload = function() {      var messageEle = document.getElementById('message');      window.addEventListener('message', function (e) {          if (e.origin !== "http://localhost:6000") {              return;          }          messageEle.innerHTML = "從"+ e.origin +"收到消息: " + e.data;      });  }  </script>  </body>  </html>

這樣我們就可以接收任何窗口傳遞來的消息了,為了安全起見,我們利用這時(shí)候的MessageEvent對象判斷了一下消息源,MessageEvent對象,這個(gè)對象中包含很多東西。

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. data:顧名思義,是傳遞來的message

  3. source:發(fā)送消息的窗口對象

  4. origin:發(fā)送消息窗口的源(協(xié)議+主機(jī)+端口號)

使用postMessage方法比以上方法用起來要輕便,不必有太多的繁瑣操作,可以說postMessage是對于解決跨域來說是一個(gè)比較好的解決方案,不會顯得代碼特別的臃腫,并且各個(gè)瀏覽器又有良好的支持。

跨域資源共享(CORS)

CORS:全稱"跨域資源共享"(Cross-origin resource sharing)。CORS需要瀏覽器和服務(wù)器同時(shí)支持,才可以實(shí)現(xiàn)跨域請求,目前幾乎所有瀏覽器都支持CORS,IE則不能低于IE10。CORS的整個(gè)過程都由瀏覽器自動完成,前端無需做任何設(shè)置,跟平時(shí)發(fā)送ajax請求并無差異。CORS的關(guān)鍵在于服務(wù)器,只要服務(wù)器實(shí)現(xiàn)CORS接口,就可以實(shí)現(xiàn)跨域通信。

跨域資源共享(CORS) 是一種機(jī)制,它使用額外的HTTP頭來告訴瀏覽器讓運(yùn)行在一個(gè)origin (domain) 上的Web應(yīng)用被準(zhǔn)許訪問來自不同源服務(wù)器上的指定的資源。當(dāng)一個(gè)資源從與該資源本身所在的服務(wù)器不同的域、協(xié)議或端口請求一個(gè)資源時(shí),資源會發(fā)起一個(gè)跨域HTTP請求。在上面說過src是不受同源策略限制的,但是出于安全原因,瀏覽器限制從腳本內(nèi)發(fā)起的跨源HTTP請求。例如,XMLHttpRequest和FetchAPI遵循同源策略。這意味著使用這些API的Web應(yīng)用程序只能從加載應(yīng)用程序的同一個(gè)域請求HTTP資源,除非響應(yīng)報(bào)文包含了正確CORS響應(yīng)頭。

所有CORS相關(guān)的的頭都是Access-Control為前綴的。下面是每個(gè)頭的一些細(xì)節(jié)。

字段描述
Access-Control-Allow-Methods該字段必需,它的值是逗號分隔的一個(gè)字符串,表明服務(wù)器支持的所有跨域請求的方法。注意,返回的是所有支持的方法,而不單是瀏覽器請求的那個(gè)方法。這是為了避免多次"預(yù)檢"請求
Access-Control-Allow-Headers如果瀏覽器請求包括Access-Control-Request-Headers字段,則Access-Control-Allow-Headers字段是必需的。它也是一個(gè)逗號分隔的字符串,表明服務(wù)器支持的所有頭信息字段,不限于瀏覽器在"預(yù)檢"中請求的字段
Access-Control-Allow-Credentials該字段與簡單請求時(shí)的含義相同。
Access-Control-Max-Age該字段可選,用來指定本次預(yù)檢請求的有效期,單位為秒。上面結(jié)果中,有效期是20天(1728000秒),即允許緩存該條回應(yīng)1728000秒(即20天),在此期間,不用發(fā)出另一條預(yù)檢請求。
import express from "express";  import cors from "cors";  const app = express()  const  var corsOptions = {    origin: 'http://example.com',    optionsSuccessStatus: 200  }  app.get('/products/:id', cors(corsOptions), (req, res, next) => {    res.json({msg: 'This is CORS-enabled for only example.com.'})  })  app.listen(80, function () {    console.log('啟用corba,端口:80')  })

使用CORS簡單請求,非常容易,對于前端來說無需做任何配置,與發(fā)送普通ajax請求無異。唯一需要注意的是,需要攜帶cookie信息時(shí),需要將withCredentials設(shè)置為true即可。CORS的配置,完全在后端設(shè)置,配置起來也比較容易,目前對于大部分瀏覽器兼容性也比較好,現(xiàn)在應(yīng)用最多的就是CORS解決跨域了。

WebSocket協(xié)議跨域

WebSocket是HTML5新推出的一個(gè)API,通過WebSocket可以實(shí)現(xiàn)客戶端與服務(wù)端的即時(shí)通信,如聊天室,服務(wù)數(shù)據(jù)同步渲染等等。WebSocket是點(diǎn)對點(diǎn)通信,服務(wù)端與客戶端可以通過某種標(biāo)識完成的。WebSocket是不受同源策略限制的所以可以利用這個(gè)特性直接與服務(wù)端進(jìn)行點(diǎn)對點(diǎn)通信。

以下示例沒有使用HTML5的WebSocket而是使用的socket.io完成類似的功能操作。

若若的說一句:其實(shí)我一直以為WebSocket與Ajax一樣是受同源策略限制的,經(jīng)過學(xué)習(xí)才發(fā)現(xiàn)不是的。真是學(xué)到老活到老(關(guān)谷口音)。O(&cap;_&cap;)O

服務(wù)端:

var io = require('socket.io')(1234);  io.sockets.on('connection', (client) => {      client.on('message', function (msg) { //監(jiān)聽到信息處理          client.send('服務(wù)器收到了信息:' + msg);      });      client.on("disconnect", function () { //斷開處理          console.log("client has disconnected");      });  });  console.log("listen 1234...");

客戶端:

$(function () {      var ioiosocket = io.connect('http://localhost:1234/');      var $ul = $("ul");      var $input = $("input");      iosocket.on('connect', function () {  //接通處理          $ul.append($('<li>連上啦</li>'));          iosocket.on('message', function (message) {  //收到信息處理              $ul.append($('<li></li>').text(message));          });          iosocket.on('disconnect', function () { //斷開處理              $ul.append('<li>Disconnected</li>');          });      });      $input.keypress(function (event) {          if (event.which == 13) { //回車              event.preventDefault();              console.log("send : " + $input.val());              iosocket.send($input.val());              $input.val('');          }      });  });

Websocket既然能支持跨域方法,那就是說,一個(gè)開放給公網(wǎng)的Websocket服務(wù)任何人都能訪問,這樣的話會使數(shù)據(jù)變得很不安全,所以可以通過對連接域名進(jìn)行認(rèn)證即可。

服務(wù)器反代

學(xué)習(xí)路程首先了解了一下什么是反代,在計(jì)算機(jī)網(wǎng)絡(luò)中,反向代理是代理服務(wù)器的一種。服務(wù)器根據(jù)客戶端的請求,從其關(guān)聯(lián)的一組或多組后端服務(wù)器(如Web服務(wù)器)上獲取資源,然后再將這些資源返回給客戶端,客戶端只會得知反向代理的IP地址,而不知道在代理服務(wù)器后面的服務(wù)器簇的存在。 -- 節(jié)選自百度百科

反向代理服務(wù)器:就nginx把http請求轉(zhuǎn)發(fā)到另一個(gè)或者一些服務(wù)器上。從而輕松實(shí)現(xiàn)跨域訪問。比如服務(wù)器中分別部署了N個(gè)服務(wù)器,當(dāng)客戶端發(fā)起請求時(shí)不用直接請求服務(wù)器中N個(gè)節(jié)點(diǎn)上的服務(wù),只需要訪問我們的代理服務(wù)器就行了,代理服務(wù)器根據(jù)請求內(nèi)容分發(fā)到不同服務(wù)器節(jié)點(diǎn)。這僅是一種使用場景,當(dāng)然還可以做負(fù)載均衡等。

反向代理理解起來不是特別的難,平時(shí)生活中最常見的例子,當(dāng)我們撥打人工客服的時(shí)候,并不是直接撥打客服的某一個(gè)電話號碼,而是撥打總機(jī)號碼,當(dāng)我們撥打然后由總機(jī)進(jìn)行處理,然后再分發(fā)給不同的客服人員。r然而當(dāng)服務(wù)人員需要讓你掛斷電話等待回?fù)艿臅r(shí)候,也不是直接撥打到你的電話,同樣是也通過總機(jī)之后再轉(zhuǎn)發(fā)到你的電話。其實(shí)這個(gè)總機(jī)也就相當(dāng)于反代服務(wù)器。雖然這個(gè)例子不太貼切但是多多少少就是這個(gè)意思。

由于不太懂Nginx不知道該如何處理這個(gè)部分,只是對反向代理做了一個(gè)簡單的了解,等以后學(xué)習(xí)了Nginx會補(bǔ)上相關(guān)代碼。

Nodejs代理跨域

使用Nodejs進(jìn)行跨域在我看來,就是使用Node服務(wù)做了一個(gè)中間代理轉(zhuǎn)發(fā),其原理和反向代理差不多,當(dāng)訪問某一個(gè)URL時(shí)需要通過服務(wù)器分發(fā)到另一個(gè)服務(wù)器URL地址中。這里就不過多的贅述了,直接看代碼吧。

示例代碼入下:

main.js

import http from "http";  import httpProxy from "http-proxy";  // 新建一個(gè)代理 Proxy Server 對象    const proxy = httpProxy.createProxyServer({});     // 捕獲異常    proxy.on('error', function (err, req, res) {      res.writeHead(500, {        'Content-Type': 'text/plain'      });      res.end('error');    });       // 在每次請求中,調(diào)用 proxy.web(req, res config) 方法進(jìn)行請求分發(fā)    const server = http.createServer((req, res) => {     // 在這里可以自定義你的路由分發(fā)      let host = req.headers.host, ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;      switch(host){        case 'www.a.com':             proxy.web(req, res, { target: 'http://localhost:3000' });            break;        case 'www.b.com':            proxy.web(req, res, { target: 'http://localhost:4000' });            break;      default:            res.writeHead(200, {                'Content-Type': 'text/plain'            });            res.end('Hello Aaron!');      }    });    server.listen(8080);

如代碼所示,當(dāng)訪問www.a.com的時(shí)候,請求就被轉(zhuǎn)發(fā)到了3000接口上,訪問www.b.com時(shí)就被轉(zhuǎn)發(fā)到了4000這個(gè)接口上。這樣就簡單的完成了一個(gè)反向代理的工作。

在使用vue開發(fā)的時(shí)候難免也會遇到跨域問題,或許你根本就沒有遇到,可能你們正好處于同一個(gè)域里面,還有一種可能就是,后端同學(xué)或者運(yùn)維同學(xué)已經(jīng)處理好有關(guān)跨域的相關(guān)操作。但是當(dāng)在開發(fā)過程中遇到跨域的時(shí)候,什么前端應(yīng)該有對應(yīng)的解決辦法。vue-cli是基于Node服務(wù)的,所以我們可以利用這個(gè)服務(wù)來做代理工作,暫時(shí)解決開發(fā)中的跨域問題。

build/webpack.config.js

module.exports = {      devServer: {          historyApiFallback: true,          proxy: [{              context: '/login',  //  url以/login為開頭時(shí)啟用代理              target: 'http://www.a.com:8080',  // 代理跨域目標(biāo)接口              changeOrigin: true,              secure: false,  // 當(dāng)代理某些https服務(wù)報(bào)錯(cuò)時(shí)用              cookieDomainRewrite: 'www.b.com'  // 可以為false,表示不修改          }],          noInfo: true      }  }

在開發(fā)過程中遇到的可以通過這種方式解決,但是到達(dá)生產(chǎn)環(huán)境時(shí)到底使用什么方法還是需要斟酌的,畢竟要使服務(wù)數(shù)據(jù)變得更加的安全才是最好的。

“分析web前端開發(fā)中跨域問題”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

web
AI