那些年曾談起的跨域

Aaron發表於2019-06-20

對於前端開發來說跨域應該是最不陌生的問題了,無論是開發過程中還是在面試過程中都是一個經常遇到的一個問題,在開發過程中遇到這個問題的話一般都是找後端同學去解決,以至於很多人都忽略了對跨域的認識。為什麼會導致跨域?遇到跨域又怎麼去解決呢?本文會對這些問題一一的介紹。

JavaScript中,在不同的域名下面進行資料互動,就會遇到跨域問題,說到跨域首先要從同源說起,瀏覽器為了提供一種安全的執行環境,各個瀏覽器廠商協定使用同源策略。

什麼同源策略

同源策略:同源策略(Same origin policy)是一種約定,它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,則瀏覽器的正常功能可能都會受到影響。可以說Web是構建在同源策略基礎之上的,瀏覽器只是針對同源策略的一種實現。

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

Url組成部分

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

o_url.jpg

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

  1. protocol:協議常用的協議是http
  2. auth:驗證,因為明文傳輸使用者名稱和密碼,非HTTPS環境下很不安全,一般用的非常少。
  3. hostname:主機地址,可以是域名,也可以是IP地址
  4. port:http協議預設埠是:80埠,如果不寫預設就是:80埠
  5. pathname:路徑網路資源在伺服器中的指定路徑
  6. serarch:查詢字串如果需要從伺服器那裡查詢內容,在這裡編輯
  7. hash:雜湊網頁中可能會分為不同的片段,如果想訪問網頁後直接到達指定位置,可以在這部分設定

專案過程過程中經常會用到一些快取,瀏覽器為了網頁的安全在快取的時候,由於同源策略的問題對其快取內容進行了限制,其實想想也是對的,如果不使用同源策略的話,很容易沖掉快取的東西。

  1. CookieLocalStorageIndexDB等無法讀取。
  2. DOM無法獲得。
  3. AJAX請求不能傳送。

當然瀏覽器也沒有把所有的東西都限制了,比如圖片、網際網路資源等還是允許跨域請求的。允許跨域請求都是使用標籤,只有三個標籤是允許跨域載入資源:

  1. <img src=XXX>
  2. <link href=XXX>
  3. <script src=XXX>

在專案開發過程中時不時的就會遇到下面這樣丟擲了錯誤,有的人可能在開發過程中沒有遇到過,如果是的話,你可能遇到一個很不錯的後端或者運維。

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

上面的報錯就是典型的跨域報錯,既然跨域這麼常見到底都有哪些情況會導致跨域的問題:

說明 是否允許通訊
同一域名下 允許
同一域名下不同資料夾 允許
同一域名,不同埠 不允許
同一域名,不同協議 不允許
域名和域名對應ip 不允許
主域名相同,子域名不同 不允許
同一域名,不同二級域名 不允許
不同域名 不允許

跨域解決方案

由於瀏覽器的限制造成了很多的跨域問題,同樣也是為了安全,既然出現了跨域就必定要有一些對應的解決方案,總不能遇到跨域之後專案就不做了啊,可能瞬間就涼了。閒話就不多扯了。

JSONP

在遇到跨域的時候經常會提及到的一個詞就是JSONP,一直在說JSONP?可是通過什麼原理來實現的呢?我覺得應該瞭解一下到底什麼再去了解一下實現固然原理也就懂得咯。

什麼是JSONP

JSONP:JSON的一種“使用模式”,可用於解決主流瀏覽器的跨域資料訪問的問題。由於同源策略,一般來說位於server1.example.com的網頁無法與不是server1.example.com的伺服器溝通,而HTML<script>元素是一個例外。利用<script>元素的這個開放策略,網頁可以得到從其他來源動態產生的JSON資料,而這種使用模式就是所謂的JSONP。用JSONP抓到的資料並不是JSON,而是任意的JavaScript,用JavaScript直譯器執行而不是用JSON解析器解析。 - 選自百度百科

對於JSONP簡單的百度了一下,百度給出的解釋如上,看完整段話,有一些小的收穫,第一script標籤具有開放策略,可以使用src的開放性解決其跨域問題。在這裡簡單的闡述一下個人觀點。JSONP可以分為兩個部分來解讀,JSONpaddingJSON固然就不用解釋了,只是一種資料格式,paddingcss中是內填充的意思,其實JSONP的原理與內填充有些類似。通過把資料填充js檔案中然後引入到頁面中,並在頁面中使用。

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

連線:
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解析"
    },
    ...]
})

通過對百度的即時搜尋的分析就可以簡單的看出JSONP的實現原理,請求會的js檔案中包含一個函式,其函式名稱就是連線中cb的引數最為引數傳給後臺,後臺通過處理並在執行這個與引數對應的函式的,當函式執行的時候將把資料以實參的形式傳遞給對應的函式,解決跨域問題。為了方便閱讀這裡只擷取了程式碼片段。

案例:

前端程式碼:

$('#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+'你已經'+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})`);
});

通過如上程式碼就可以簡單的實現JSONP,雖然JSONP解決了跨域的問題,還是有很多弊端的,比如會在頁面中新增一些script標籤,資料不能雙向操作等等。

使用JSONP的時候尤其要注意一點,一定要把插入的script放到所應用函式的下面。這個和js的執行順序有關係,如果把script標籤放在上面的話,其方法還沒有被讀取在script標籤中就執行了這個方法必定報錯的,這點很重要哦。

document.domain

document.domain專案中一般應用的較少,預設情況下document.domain存放的是載入文件的伺服器的主機名。可以在控制檯輸出一下,得到的則是segmentfault.com這個域名。我在專案中所用到的則是結合iframe的時候遇到的跨域,並使用的domain解決的。

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

簡單的解釋一下,例如想要在www.a.com中將看到segmentfault.com中的內容並將其網頁使用iframe將其嵌入到其網頁中,但是此時瀏覽器是不允許通過JavaScript直接操作segmentfault.com的,因為這兩個頁面屬於不同的域,在操作之前瀏覽器會檢測是否符合同源策略,如果符合則允許操作,反之則不行。

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

例如:

a.com
news.a.com

news.a.com屬於a.com的一個子域名,按照上面所說已經滿足了上面的規則,如果想要實現跨域操作就需要對接子頁面的document.domain進行操作。

父頁面:

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({
    //  ...省略
})

其實現原理就是通過iframe載入一個與你想要通過ajax獲取資料的目標頁面處在相同的域的頁面,所以這個iframe中的頁面是可以正常使用ajax去獲取你要的資料的,然後就是通過我們剛剛講得修改document.domain的方法,讓我們能通過js完全控制這個iframe,這樣我們就可以讓iframe去傳送ajax請求,然後收到的資料我們也可以獲得了。

location.hash

若理解了document.domain實現跨域原理,那麼location.hash也就很號理解了,其原理與document.domain很相似一樣都是動態插入一個iframe,然後把iframesrc指向服務端地址,而服務端同樣都是輸出一段JavaScript程式碼,同樣都是利用和子視窗之間的通訊完成資料傳輸,同樣要針對同源策略做出處理。

既然說到了hash到底什麼是hash這裡也就單獨的說一下吧,雖然很好理解,但是對於新同學來說可能還不知道hash具體是什麼?

hash:一般翻譯做雜湊、雜湊,或音譯為雜湊,是把任意長度的輸入(又叫做預對映pre-image)通過雜湊演算法變換成固定長度的輸出,該輸出就是雜湊值。這種轉換是一種壓縮對映,也就是,雜湊值的空間通常遠小於輸入的空間,不同的輸入可能會雜湊成相同的輸出,所以不可能從雜湊值來確定唯一的輸入值。簡單的說就是一種將任意長度的訊息壓縮到某一固定長度的訊息摘要的函式。 -- 節選自百度百科

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

雜湊有如下特點:

  1. 如果兩個雜湊值是不相同的(根據同一函式),那麼這兩個雜湊值的原始輸入一定是不相同的。
  2. 如果兩個雜湊值相同,兩個輸入值很可能(極大概率)是相同的,但也可能不同,這種情況稱為“雜湊碰撞”
  3. 抗篡改能力:對於一個資料塊,哪怕只改動其一個位元位,其hash值的改動也會非常大。
  4. 它是一種單向函式是“非對稱”的,即它是一個從明文到密文的不可逆的對映,只有加密過程,沒有解密過程。

那麼雜湊在平時專案開發中有什麼用途呢?可以用雜湊來做什麼事情?對於前端來說用到雜湊最多的時候可能就是錨點定位。通過不同的雜湊值定位到描點指定的元素位置上。

<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>

關於更多細節的東西不再這裡贅述了,如果想要了解更多的話大家可以自行google,再說下去的話可能就跑題了。

簡單的介紹了一下雜湊與雜湊的用處那麼又該如何使用雜湊來實現跨域呢?其實很簡單,如果index頁面要獲取遠端伺服器的資料,動態插入一個iframe,將iframe的src屬性指向服務端地址。這時top window和包裹這個iframe的子視窗由於同源策略的原因是不能直接通訊的,所以改變子視窗的路徑就行了,將資料當做改變後的路徑的hash值加在路徑上,然後就能通訊了,將資料加在index頁面地址的hash值上。index頁面監聽地址的hash值變化然後做出判斷,處理資料。

父頁面:

<iframe src="a.com/index.php/msg#key=1&key1=2"></iframe>

由於雜湊值的改變不會改變網頁的網址的,所以服務端可以通過獲取到雜湊來解析url中的引數,並把資料返回給前端即可。通過parent.location.hash去改變雜湊值,然後就可以像document.domain一樣去獲取到子頁面的資料了。parent.location.hash該方法是有侷限性的,在IEChrome中是不支援這種操作的。那麼整個問題應如何解決呢?

在同域的域名下面新增一個*.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中有一個隱藏的iframe,該iframe指向異域http://localhost:7000/b.htmlb.html,且傳遞hash值給b.html`b.html獲取hash值,生成data值,然後動態建立iframe,該iframedata值傳給與a.html同域的c.html 因為c.htmla.html`同域,可以傳值固然也就解決了跨域問題。

window.name

window.name這個屬性不是一個簡單的全域性屬性只要在一個window下,無論url怎麼變化,只要設定好了window.name,那麼後續就一直都不會改變,同理,在iframe中,即使url在變化,iframe中的window.name也是一個固定的值,利用這個,我們就可以實現跨域了。

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下有一個a.html,在http://localhost:7000下有一個b.html,在http://localhost:6000/a.html中建立了一個iframe標籤並把地址指向了http://localhost:7000/b.html,在b.html中的window.name賦值儲存了一段資料,但是現在還獲取不了,因為是跨域的,所以,我們可以把src設定為當前域的http://localhost:6000/proxy.html,雖然域名改變了但是window.name是沒有改變的。這樣就可以拿到我們想要的資料了。

postMessage(HTML5)

可能很多不知道postMessage整個API,在HTML5中新增了postMessage方法允許來自不同源的指令碼採用非同步方式進行有限的通訊,可以實現跨文字檔、多視窗、跨域訊息傳遞,postMessage在很多瀏覽器中都已經得到了良好的支援,所以可放心的使用。該方法可以通過繫結windowmessage事件來監聽傳送跨文件訊息傳輸內容。

postMessage()方法接受兩個引數

    1. data:要傳遞的資料,html5規範中提到該引數可以是JavaScript的任意基本型別或可複製的物件,然而並不是所有瀏覽器都做到了這點兒,部分瀏覽器只能處理字串引數,所以我們在傳遞引數的時候需要使用JSON.stringify()方法對物件引數序列化,在低版本IE中引用json2.js可以實現類似效果。
    1. origin:字串引數,指明目標視窗的源,協議+主機+埠號+URLURL會被忽略,所以可以不寫,這個引數是為了安全考慮,postMessage()方法只會將message傳遞給指定視窗,當然如果願意也可以建引數設定為"*",這樣可以傳遞給任意視窗,如果要指定和當前視窗同源的話設定為"/"。

    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’s Aaron" />
        <button id="send" >傳送訊息</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>

    這樣我們就可以接收任何視窗傳遞來的訊息了,為了安全起見,我們利用這時候的MessageEvent物件判斷了一下訊息源,MessageEvent物件,這個物件中包含很多東西。

    1. data:顧名思義,是傳遞來的message
    2. source:傳送訊息的視窗物件
    3. origin:傳送訊息視窗的源(協議+主機+埠號)

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

    跨域資源共享(CORS)

    CORS:全稱"跨域資源共享"(Cross-origin resource sharing)。CORS需要瀏覽器和伺服器同時支援,才可以實現跨域請求,目前幾乎所有瀏覽器都支援CORSIE則不能低於IE10CORS的整個過程都由瀏覽器自動完成,前端無需做任何設定,跟平時傳送ajax請求並無差異。CORS的關鍵在於伺服器,只要伺服器實現CORS介面,就可以實現跨域通訊。

    跨域資源共享(CORS) 是一種機制,它使用額外的HTTP頭來告訴瀏覽器讓執行在一個origin (domain) 上的Web應用被准許訪問來自不同源伺服器上的指定的資源。當一個資源從與該資源本身所在的伺服器不同的域、協議或埠請求一個資源時,資源會發起一個跨域HTTP請求。在上面說過src是不受同源策略限制的,但是出於安全原因,瀏覽器限制從指令碼內發起的跨源HTTP請求。例如,XMLHttpRequestFetchAPI遵循同源策略。這意味著使用這些APIWeb應用程式只能從載入應用程式的同一個域請求HTTP資源,除非響應報文包含了正確CORS響應頭。

    所有CORS相關的的頭都是Access-Control為字首的。下面是每個頭的一些細節。

    欄位 描述
    Access-Control-Allow-Methods 該欄位必需,它的值是逗號分隔的一個字串,表明伺服器支援的所有跨域請求的方法。注意,返回的是所有支援的方法,而不單是瀏覽器請求的那個方法。這是為了避免多次"預檢"請求
    Access-Control-Allow-Headers 如果瀏覽器請求包括Access-Control-Request-Headers欄位,則Access-Control-Allow-Headers欄位是必需的。它也是一個逗號分隔的字串,表明伺服器支援的所有頭資訊欄位,不限於瀏覽器在"預檢"中請求的欄位
    Access-Control-Allow-Credentials 該欄位與簡單請求時的含義相同。
    Access-Control-Max-Age 該欄位可選,用來指定本次預檢請求的有效期,單位為秒。上面結果中,有效期是20天(1728000秒),即允許快取該條迴應1728000秒(即20天),在此期間,不用發出另一條預檢請求。
    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簡單請求,非常容易,對於前端來說無需做任何配置,與傳送普通ajax請求無異。唯一需要注意的是,需要攜帶cookie資訊時,需要將withCredentials設定為true即可。CORS的配置,完全在後端設定,配置起來也比較容易,目前對於大部分瀏覽器相容性也比較好,現在應用最多的就是CORS解決跨域了。

    WebSocket協議跨域

    WebSocketHTML5新推出的一個API,通過WebSocket可以實現客戶端與服務端的即時通訊,如聊天室,服務資料同步渲染等等。WebSocket是點對點通訊,服務端與客戶端可以通過某種標識完成的。WebSocket是不受同源策略限制的所以可以利用這個特性直接與服務端進行點對點通訊。

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

    若若的說一句:其實我一直以為WebSocketAjax一樣是受同源策略限制的,經過學習才發現不是的。真是學到老活到老(關谷口音)。O(∩_∩)O

    服務端:

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

    客戶端:

    $(function () {
        var iosocket = 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既然能支援跨域方法,那就是說,一個開放給公網的Websocket服務任何人都能訪問,這樣的話會使資料變得很不安全,所以可以通過對連線域名進行認證即可。

    伺服器反代

    學習路程首先了解了一下什麼是反代,在計算機網路中,反向代理是代理伺服器的一種。伺服器根據客戶端的請求,從其關聯的一組或多組後端伺服器(如Web伺服器)上獲取資源,然後再將這些資源返回給客戶端,客戶端只會得知反向代理的IP地址,而不知道在代理伺服器後面的伺服器簇的存在。 -- 節選自百度百科

    反向代理伺服器:就nginxhttp請求轉發到另一個或者一些伺服器上。從而輕鬆實現跨域訪問。比如伺服器中分別部署了N個伺服器,當客戶端發起請求時不用直接請求伺服器中N個節點上的服務,只需要訪問我們的代理伺服器就行了,代理伺服器根據請求內容分發到不同伺服器節點。這僅是一種使用場景,當然還可以做負載均衡等。

    反向代理理解起來不是特別的難,平時生活中最常見的例子,當我們撥打人工客服的時候,並不是直接撥打客服的某一個電話號碼,而是撥打總機號碼,當我們撥打然後由總機進行處理,然後再分發給不同的客服人員。r然而當服務人員需要讓你結束通話電話等待回撥的時候,也不是直接撥打到你的電話,同樣是也通過總機之後再轉發到你的電話。其實這個總機也就相當於反代伺服器。雖然這個例子不太貼切但是多多少少就是這個意思。

    由於不太懂Nginx不知道該如何處理這個部分,只是對反向代理做了一個簡單的瞭解,等以後學習了Nginx會補上相關程式碼。

    Nodejs代理跨域

    使用Nodejs進行跨域在我看來,就是使用Node服務做了一箇中間代理轉發,其原理和反向代理差不多,當訪問某一個URL時需要通過伺服器分發到另一個伺服器URL地址中。這裡就不過多的贅述了,直接看程式碼吧。

    示例程式碼入下:

    main.js

    import http from "http";
    import httpProxy from "http-proxy";
      
    // 新建一個代理 Proxy Server 物件  
    const proxy = httpProxy.createProxyServer({});  
      
    // 捕獲異常  
    proxy.on('error', function (err, req, res) {  
      res.writeHead(500, {  
        'Content-Type': 'text/plain'  
      });  
      res.end('error');  
    });  
        
    // 在每次請求中,呼叫 proxy.web(req, res config) 方法進行請求分發  
    const server = http.createServer((req, res) => {  
      // 在這裡可以自定義你的路由分發  
      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);

    如程式碼所示,當訪問www.a.com的時候,請求就被轉發到了3000介面上,訪問www.b.com時就被轉發到了4000這個介面上。這樣就簡單的完成了一個反向代理的工作。

    在使用vue開發的時候難免也會遇到跨域問題,或許你根本就沒有遇到,可能你們正好處於同一個域裡面,還有一種可能就是,後端同學或者運維同學已經處理好有關跨域的相關操作。但是當在開發過程中遇到跨域的時候,什麼前端應該有對應的解決辦法。vue-cli是基於Node服務的,所以我們可以利用這個服務來做代理工作,暫時解決開發中的跨域問題。

    build/webpack.config.js

    module.exports = {
        devServer: {
            historyApiFallback: true,
            proxy: [{
                context: '/login',  //  url以/login為開頭時啟用代理
                target: 'http://www.a.com:8080',  // 代理跨域目標介面
                changeOrigin: true,
                secure: false,  // 當代理某些https服務報錯時用
                cookieDomainRewrite: 'www.b.com'  // 可以為false,表示不修改
            }],
            noInfo: true
        }
    }

    在開發過程中遇到的可以通過這種方式解決,但是到達生產環境時到底使用什麼方法還是需要斟酌的,畢竟要使服務資料變得更加的安全才是最好的。

    總結

    以上講了很多有關跨域的解決方案,有利也有弊,對於我而言可能更加的傾向於後端粑粑或者運維粑粑去解決跨域問題,畢竟前端處理起來畢竟不是很安全,而且後端或者運維處理起來也不是那麼的麻煩。

    很感謝大家利用這麼長時間來讀這篇文章,文章中若有錯誤請在下方留言,會盡快做出修改。

    以上除Node服務以外皆是使用http-server進行測試。

    相關文章