那些年曾談起的跨域 80% 你不知道的解決方式

一. 前言

對於前端開發來講跨域應該是最不陌生的問題了,不管是開發過程當中仍是在面試過程當中都是一個常常遇到的一個問題,在開發過程當中遇到這個問題的話通常都是找後端同窗去解決,以致於不少人都忽略了對跨域的認識。爲何會致使跨域?遇到跨域又怎麼去解決呢?本文會對這些問題一一的介紹。javascript

JavaScript中,在不一樣的域名下面進行數據交互,就會遇到跨域問題,說到跨域首先要從同源提及,瀏覽器爲了提供一種安全的運行環境,各個瀏覽器廠商協定使用同源策略。css

二.什麼是跨域?

什麼同源策略

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

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

Url組成部分

瞭解同源策略之後,一樣須要對url的組成部分也順帶了解一下吧,只有瞭解url以後當出現跨域的時候才知道哪裏出了問題,這樣才能和後端、運維開懟,懟天懟地對空氣。O(∩_∩)O哈哈~其實不是啦,固然是爲了可以和其餘同事能作到良好的溝通,說的的有理有據,以理服人嘛,這纔是王道。vue

從上圖中可以清晰的看出url中每一個部分的含義:html5

  • protocol:協議經常使用的協議是http
  • auth:驗證,由於明文傳輸用戶名和密碼,非HTTPS環境下很不安全,通常用的很是少。
  • hostname:主機地址,能夠是域名,也能夠是IP地址
  • port:端口http協議默認端口是:80端口,若是不寫默認就是:80端口
  • pathname:路徑網絡資源在服務器中的指定路徑
  • serarch:查詢字符串若是須要從服務器那裏查詢內容,在這裏編輯
  • hash:哈希網頁中可能會分爲不一樣的片斷,若是想訪問網頁後直接到達指定位置,能夠在這部分設置

項目過程過程當中常常會用到一些緩存,瀏覽器爲了網頁的安全在緩存的時候,因爲同源策略的問題對其緩存內容進行了限制,其實想一想也是對的,若是不使用同源策略的話,很容易沖掉緩存的東西。java

  • Cookie、LocalStorage和IndexDB等沒法讀取。
  • DOM沒法得到。
  • AJAX請求不能發送。

固然瀏覽器也沒有把全部的東西都限制了,好比圖片、互聯網資源等仍是容許跨域請求的。容許跨域請求都是使用標籤,只有三個標籤是容許跨域加載資源:webpack

  • <img src=XXX>
  • <link href=XXX>
  • <script src=XXX>

在項目開發過程當中時不時的就會遇到下面這樣拋出了錯誤,有的人可能在開發過程當中沒有遇到過,若是是的話,你可能遇到一個很不錯的後端或者運維。ios

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

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

說明 是否容許通訊
同一域名下 容許
同一域名下不一樣文件夾 容許
同一域名,不一樣端口 不容許
同一域名,不一樣協議 不容許
域名和域名對應ip 不容許
主域名相同,子域名不一樣 不容許
同一域名,不一樣二級域名 不容許
不一樣域名 不容許

三. 跨域解決方案

因爲瀏覽器的限制形成了不少的跨域問題,一樣也是爲了安全,既然出現了跨域就一定要有一些對應的解決方案,總不能遇到跨域以後項目就不作了啊,可能瞬間就涼了。閒話就很少扯了。

1.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能夠分爲兩個部分來解讀,JSON與padding,JSON當然就不用解釋了,只是一種數據格式,padding在css中是內填充的意思,其實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標籤中就執行了這個方法一定報錯的,這點很重要哦。

2.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請求,而後收到的數據咱們也能夠得到了。

3.location.hash

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

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

hash:通常翻譯作散列、雜湊,或音譯爲哈希,是把任意長度的輸入(又叫作預映射pre-image)經過散列算法變換成固定長度的輸出,該輸出就是散列值。這種轉換是一種壓縮映射,也就是,散列值的空間一般遠小於輸入的空間,不一樣的輸入可能會散列成相同的輸出,因此不可能從散列值來肯定惟一的輸入值。簡單的說就是一種將任意長度的消息壓縮到某一固定長度的消息摘要的函數。 -- 節選自百度百科

讀完以後感受本身整我的都很差了,有些似懂非懂的意思,我所理解的哈希是指一個過程,這個過程就是把任意長度的輸入,經過哈希算法,變換成固定長度的輸出,所輸出的稱爲哈希值。這種變換是一種壓縮映射,也即哈希值所佔的空間通常來講遠小於輸入值的空間,不一樣的輸入可能會哈希出相同的輸出(機率很小)。

哈希有以下特色:

  • 若是兩個哈希值是不相同的(根據同一函數),那麼這兩個散列值的原始輸入必定是不相同的。
  • 若是兩個哈希值相同,兩個輸入值極可能(極大機率)是相同的,但也可能不一樣,這種狀況稱爲「哈希碰撞」
  • 抗篡改能力:對於一個數據塊,哪怕只改動其一個比特位,其hash值的改動也會很是大。
  • 它是一種單向函數是「非對稱」的,即它是一個從明文到密文的不可逆的映射,只有加密過程,沒有解密過程。 那麼哈希在平時項目開發中有什麼用途呢?能夠用哈希來作什麼事情?對於前端來講用到哈希最多的時候可能就是錨點定位。經過不一樣的哈希值定位到描點指定的元素位置上。
<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="http://localhost:7000/b.html#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.html的b.html,且傳遞hash值給b.htmlb.html獲取hash值,生成data值,而後動態建立iframe,該iframe將data值傳給與a.html同域的c.html 由於c.html與a.html同域,能夠傳值當然也就解決了跨域問題。

4.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是沒有改變的。這樣就能夠拿到咱們想要的數據了。

5.postMessage(HTML5)

可能不少不知道postMessage整個API,在HTML5中新增了postMessage方法容許來自不一樣源的腳本採用異步方式進行有限的通訊,能夠實現跨文本檔、多窗口、跨域消息傳遞,postMessage在不少瀏覽器中都已經獲得了良好的支持,因此可放心的使用。該方法能夠經過綁定windowmessage事件來監聽發送跨文檔消息傳輸內容。

postMessage()方法接受兩個參數

  • data:要傳遞的數據,html5規範中提到該參數能夠是JavaScript的任意基本類型或可複製的對象,然而並非全部瀏覽器都作到了這點兒,部分瀏覽器只能處理字符串參數,因此咱們在傳遞參數的時候須要使用JSON.stringify()方法對對象參數序列化,在低版本IE中引用json2.js能夠實現相似效果。
  • 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對象,這個對象中包含不少東西。

  • data:顧名思義,是傳遞來的message
  • source:發送消息的窗口對象
  • origin:發送消息窗口的源(協議+主機+端口號) 使用postMessage方法比以上方法用起來要輕便,沒必要有太多的繁瑣操做,能夠說postMessage是對於解決跨域來講是一個比較好的解決方案,不會顯得代碼特別的臃腫,而且各個瀏覽器又有良好的支持。

6.跨域資源共享(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 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解決跨域了。

在開發過程當中因爲各個公司的差別選用的接口風格也是不一樣的,不少公司會採用RESTful風格去編寫接口,不免就會有些騷操做,在跨域請求時自定義的請求頭是不容許這樣操做的,由於瀏覽器根據Response Headers判斷請求是否容許。

跨域時默認容許的方法

  • GET
  • HEAD
  • POST 由於瀏覽器但願在網頁進行跨域請求操做的時候是保證服務端的安全的,不容許任何隨便進行跨域,不容許隨便的方法進行跨域,以防數據被惡意篡改。因此提供這些限制以後,就能夠進行一些很是有利的判斷。針對如上問題須要服務端進行特殊處理才行
const http = request("http");
http.createServer(function(request,response){
    response.writeHead(200,{
        // 設置容許跨域的訪問地址
        'Access-Control-Allow-Origin':'*',
        // 設置容許訪問的自定義請求頭
        'Access-Control-Allow-Headers':'X-Test-Cors',
        // 設置容許跨域的methods
        'Access-Control-Allow-Methods':'POST,Put,Delete',
        // 容許以上三個方式進行跨域的最長時間,1000秒內不須要發送預請求驗證了
        'Access-Control-Max-Age':'1000'
    })
    response.end('123')
}).listen(3000)
複製代碼

這樣設置,這個請求就成功了,可是能夠觀測到多了一個請求,這個多出來的請求就是預請求,告訴瀏覽器,這個自定義請求是容許的,而後再正式的發送這個請求,經過驗證以後就請求成功了,瀏覽器爲何要作這些限制,是由於保證安全,不容許隨便一我的均可以進行跨域。

7.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服務任何人都能訪問,這樣的話會使數據變得很不安全,因此能夠經過對鏈接域名進行認證便可。

8.服務器反向代理

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

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

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

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

9.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
    }
}
複製代碼

在開發過程當中遇到的能夠經過這種方式解決,可是到達生產環境時到底使用什麼方法仍是須要斟酌的,畢竟要使服務數據變得更加的安全才是最好的。

四.總結

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

來源:segmentfault.com/a/119000001…
整理:李浩東的博客

相關文章
相關標籤/搜索