前端跨域方法論

前言

本着學習和總結的態度寫的技術輸出,文中有任何錯誤和問題,請你們指出。更多的技術輸出能夠查看個人 github博客javascript

整理了一些前端的學習資源,但願可以幫助到有須要的人,地址: 學習資源彙總html

跨域

跨域指的是協議(protocol ),域名(host),端口號(post)都不相同的資源之間嘗試着進行交互通訊,而因爲受瀏覽器同源策略的限制,沒法正常進行交互通訊。前端

最多見的實際場景就是在項目開發過程當中,會存在請求第三方其餘域下的資源,例如:使用地圖 API 的時候,設置密鑰的時候須要設置白名單才能正常使用地圖 API。java

image

使用 AJAX 請求第三方不一樣域下的數據資源的時候,若是不處理跨域問題,便不能成功發送 HTTP 請求,且瀏覽器會發出錯誤警告。node

同源策略

MDN 解釋: 同源策略限制了從同一個源加載的文檔或腳本如何與來自另外一個源的資源進行交互。這是一個用於隔離潛在惡意文件的重要安全機制。ios

瀏覽器的同源策略的目的就是爲了防止 XSS,CSRF 等惡意攻擊。git

同源策略的交互方式有三種:github

  • 一般容許跨域寫操做,例如連接,重定向等。
  • 一般容許跨域嵌套資源,例如 img,script 標籤等。
  • 一般不容許跨域讀操做。

跨域場景

只有資源之間的協議,域名和端口號都相同,纔是同一個源。web

下面是關於同源以及不一樣源之間的跨域描述。ajax

URL 說明 是否容許通信
www.demo.com/a.html
www.demo.com/b.html
www.demo.com/c.html
同一域名 容許
www.demo.com/news/a.html
www.demo.com/center/b.ht…
www.demo.com/server/c.ht…
同一域名下的不一樣文件夾 容許
www.demo.com/a.html
www.demo.com:80/b.html
不一樣端口號 不容許
www.demo.com/a.html
www.demo.com/b.html
不一樣協議 不容許
www.demo.com/a.html
www.test.com/b.html
不一樣域名 不容許
www.demo.com/a.html
test.demo.com/b.html
主域相同,子域不一樣 不容許

跨域解決方案

1. JSONP

因爲瀏覽器同源策略是容許 script 標籤這樣的跨域資源嵌套的,因此 script 標籤的資源不受同源策略的限制。

JSONP 的解決方案就是經過 script 標籤進行跨域請求。

  • 前端設置好回調函數,並把回調函數當作請求 url 攜帶的參數。
  • 後端接受到請求以後,返回回調函數名和須要的數據。
  • 後端響應並返回數據後,返回的數據傳入到回調函數中並執行。
<!-- 經過原生使用 script 標籤 -->
<script> function jsonpCallback(data) { alert('獲取到的數據了,打開控制檯瞧瞧'); console.log(data); } </script>
<script src="http://127.0.0.1:3000?callback=jsonpCallback"></script>
複製代碼

也可使用 AJAX GET 請求方式來跨域請求(axios GET 方式跨域同理)。

<!-- AJAX GET 請求 -->
<script> function jsonpCallback(data) { alert('獲取到的數據了,打開控制檯瞧瞧'); console.log(data); } $.ajax({ type: 'GET', // 必須是 GET 請求 url: 'http://127.0.0.1:3000', dataType: 'jsonp', // 設置爲 jsonp 類型 jsonpCallback: 'jsonpCallback' // 設置回調函數 }) </script>
複製代碼

優缺點:

  • 兼容性好,低版本的 IE 也支持這種方式。

  • 只能支持 GET 方式的 HTTP 請求。

  • 只支持先後端數據通訊這樣的 HTTP 請求,並不能解決不一樣域下的頁面之間的數據交互通訊問題。

2. CORS

CORS 跨域資源共享容許在服務端進行相關設置後,能夠進行跨域通訊。

服務端未設置 CORS 跨域字段,服務端會拒絕請求並提示錯誤警告。

服務端設置 Access-Control-Allow-Origin 字段,值能夠是具體的域名或者 '*' 通配符,配置好後就能夠容許跨域請求數據。

<script>
    $.ajax({
    type: 'post',
    url: 'http://127.0.0.1:3000',
    success: function(res) {
        alert('獲取到的數據了,打開控制檯瞧瞧');
        console.log(res);
    }
})
</script>
複製代碼

服務端如何設置跨域字段? 後端語言設置跨域的方式都不一致,具體可參考後端語言自己的 API。

Node 端設置

res.writeHead(200, {
    'Access-Control-Allow-Origin': '*'
});

// 或者使用了 Express 這樣的框架
res.header("Access-Control-Allow-Origin", "*");
複製代碼

關於 CORS 的詳細,能夠參考這篇筆記,CORS跨域資源共享

3. Server Proxy

經過服務端代理請求的方式也是解決瀏覽器跨域問題的方案。同源策略只是針對瀏覽器的安全策略,服務端並不受同源策略的限制,也就不存在跨域的問題。具體步驟以下:

  • 前端正常請求服務端提供的接口。好比請求接口:http://localhost:3000
  • 經過服務端設置代理髮送請求,請求到數據後再將須要的數據返回給前端。好比設置的代理請求接口是 cnodejs.org/api/v1/topi… ,服務端代理將數據請求回來以後再將數據 http://localhost:3000 接口返回給前端。
// 服務端代理請求代碼
// 服務端只是簡單的經過正常的 HTTP 請求的方式來代理請求接口數據
// 或者也可使用 proxy 模塊來代理,至於怎麼使用 proxy 模塊,待研究完善
var url = 'https://cnodejs.org/api/v1/topics';        
https.get(url, (resp) => {
    let data = "";
    resp.on('data', chunk => {
        data += chunk;
    });
    resp.on('end', () => {
        res.writeHead(200, {
            'Access-Control-Allow-Origin': '*',
            'Content-Type': 'application/json; charset=utf-8'
        });
        res.end(data);
    });
})
複製代碼

4. location.hash + iframe

location.hash + iframe 跨域通訊的實現是這樣的:

  • 不一樣域的 a 頁面與 b 頁面進行通訊,在 a 頁面中經過 iframe 嵌入 b 頁面,並給 iframe 的 src 添加一個 hash 值。
  • b 頁面接收到了 hash 值後,肯定 a 頁面在嘗試着與本身通訊,而後經過修改 parent.location.hash 的值,將要通訊的數據傳遞給 a 頁面的 hash 值。
  • 但因爲在 IE 和 Chrmoe 下不容許子頁面直接修改父頁面的 hash 值,因此須要一個代理頁面,經過與 a 頁面同域的 c 頁面來傳遞數據。
  • 一樣的在 b 頁面中經過 iframe 嵌入 c 頁面,將要傳遞的數據經過 iframe 的 src 連接的 hash 值傳遞給 c 頁面,因爲 a 頁面與 c 頁面同域,c 頁面能夠直接修改 a 頁面的 hash 值或者調用 a 頁面中的全局函數。

大體流程就是:

a 頁面代碼

<script>
    var iframe = document.createElement('iframe');
    iframe.style.display = 'none';
    iframe.src = "http://localhost:8081/b.html#data";
    document.body.appendChild(iframe);

    function checkHash() {
        try {
            var data = location.hash ? location.hash.substring(1) : '';
            console.log('得到到的數據是:', data);
        }catch(e) {}
    }
    window.addEventListener('hashchange', function(e) {
        console.log('監聽到hash的變化:', location.hash.substring(1));
    })
</script>
複製代碼

b 頁面代碼

<script>
     switch(location.hash) {
         case '#data':
         callback();
         break;
     }
    function callback() {
        var data = "testHash"
        try {
            parent.location.hash = data;
        }catch(e) {
            var ifrproxy = document.createElement('iframe');
            ifrproxy.style.display = 'none';
            ifrproxy.src = 'http://localhost:8080/c.html#' + data;
            document.body.appendChild(ifrproxy);
        }
    }
 </script>  
複製代碼

c 頁面代碼

<script>
    // 修改 a 頁面的 hash 值
    parent.parent.location.hash = self.location.hash.substring(1);
    // 調用 a 頁面的全局函數
    parent.parent.checkHash();
</script>
複製代碼

優缺點:

  • hash 傳遞的數據容量有限。
  • 數據直接暴露在 url 中。

5. document.domain + iframe

該方案只限於主域相同子域不一樣的資源跨域解決方案。

實際應用場景:

以前的項目開發中,常常碰到這樣的跨域問題,大體相似於在開發新產品的產品頁中,在沒有正式上線以前,通常都是上傳到內部的測試環境中,好比測試環境的域名是 test.admin.com/xxx/xxx,而項目… consumer-test.admin.com/xxx/xxx 這樣的,產品頁是單獨分離部署上線,再經過 iframe 嵌套到項目中。在內部測試過程當中,因爲產品頁測試環境和項目測試環境主域相同而子域不一樣,且產品頁中須要用到項目中定義的全局公共資源,因爲跨域問題,這些公共資源是獲取不到的。

這種場景的跨域解決方案就是利用 document.domain 設置。在產品頁和項目中將 document.domain 設置成相同域就能夠實現跨域,嵌套的產品頁就能夠訪問父頁面的公共資源了。須要注意的一點就是,document.domain 的設置是有限制的,只能設置成自身或者更高級的父域,且主域必須相同。

項目頁面

<iframe src="test.admin.com/xxx/xxx"></iframe>
<script>
    document.domain = 'admin.com';
</script>
複製代碼

產品頁

<script>
    // 設置以後就可獲取項目頁面中定義的公共資源了
    document.domain = 'admin.com';
</script>
複製代碼

6. window.name + iframe

window.name 指的是當前瀏覽器窗口的名稱,默認爲空字符串,每一個窗口的 window.name 都是獨立的。iframe 嵌套的頁面中也有屬於本身的 window 對象,這個 window 是top window 的子窗口,也一樣擁有 window.name 的屬性。

window.name 的獨特之處在於當在頁面設置 window.name 的值,其實就是至關於給這個窗口設置了名稱,然後在這個窗口加載其餘頁面(甚至不一樣域的頁面),window.name 的值依然存在(若是沒有從新設置那麼值不會變化),而且 window.name 的值支持比較大的存儲(2MB)。

例如: 隨便找個頁面打開控制檯,給當前窗口設置名稱。

window.name = 'test-name';
複製代碼

設置好以後能夠在這個窗口下跳轉到其餘頁面

window.location = 'https://www.baidu.com';
複製代碼

頁面跳轉到了百度首頁,可是 window.name 的值依然是以前設置的值,由於是在一個窗口中跳轉的頁面,窗口名稱並不會被修改。

具體的跨域解決方式以下。

http://localhost:8080/a.htmlhttp://localhost:8081/b.html 跨域通訊,a 頁面經過 iframe 嵌套 b 頁面,b 頁面中設置好 window.name 的值,因爲是不一樣域,a 頁面不能直接訪問到 b 頁面設置的 window.name 的值,須要一個與 a 頁面同域的中間頁來代理做爲 a 頁面與 b 頁面通訊的橋樑。

a.html

<script>
	var data = null;
	var state = 0;
	var iframe = document.createElement('iframe');
	iframe.src = "http://localhost:8081/b.html";
	iframe.style.display = 'none';
	document.body.appendChild(iframe);
	
	// 第一次加載先加載 b.html,b.html 設置好了 window.name 的值
	// 然後加載 c.html,c.html 的 window.name 的值就是以前 b.html 設置的值
	// 同域的狀況下,a.html 能夠經過 iframe.contentWindow.name 獲取到 b.html 中 windoa.name 的值
	iframe.onload = function() {
    	if(state === 0) {
    	    iframe.src = "http://localhost:8080/c.html";
    	    state = 1;
    	}else if(state === 1) {
    	    data = iframe.contentWindow.name;
    	    console.log('收到數據:', data);
    	}
    }
</script>
複製代碼

b.html

<script>
    window.name = '這是傳遞的數據';
</script>
複製代碼

中間代理頁,只須要跟 a 頁面保持同域就能夠了,例如: http://localhost:8080/c.html

7. window.postMessage

postMessage 是 HTML5 的新特性,用於頁面之間跨域通訊。

postMessage 方法接受兩個必要的參數:

  • message: 須要傳遞的數據。
  • targetOrigin: 數據傳遞的目標窗口域名,值能夠是具體的域名或者 '*' 通配符。

a.html

<iframe src="http://localhost:8081/b.html" style='display: none;'></iframe>
<script>
	window.onload = function() {
	    var targetOrigin = 'http://localhost:8081';
	    var data = {
	    	name: '武林外傳',
	        time: 2005,
	        length: 81,
	        address: '同福客棧'
	    };
	    // 向 b.html 發送消息
	    window.frames[0].postMessage(data, targetOrigin);

	    // 接收 b.html 發送的數據
	    window.addEventListener('message', function(e) {
	        console.log('b.html 發送來的消息:', e.data);
	    })
	}
</script>
複製代碼

b.html

<script>
	var targetOrigin = 'http://localhost:8080';
	window.addEventListener('message', function(e) {
	    if(e.source != window.parent) {
	        return;
	    }
	    // 接收 a.html 發送的數據
	    console.log('a.html 發送來的消息:', e.data);
	    // 向 a.html 發送消息
	    parent.postMessage('哈哈,我是b頁面,我收到你的消息了', targetOrigin);
	})
</script>
複製代碼

總結

  • 協議,域名,端口號不相同的資源之間相互通訊,就會產生跨域問題。

  • 處於安全考慮,瀏覽器的同源策略限制了不一樣域之間相互通訊。

  • JSONP,CORS,Server Proxy 跨域解決方式的應用場景都是用於先後端之間的數據通訊,其餘跨域解決方案主要是解決窗口頁面之間的數據通訊。

  • JSONP 只支持 GET 方式的 HTTP 請求。

  • CORS 跨域資源請求須要後端支持。

  • Server Proxy 直接讓後端代理髮送請求。

後記

全部的跨域解決方案都有對應的 DEMO 實例,可在 DEMO 中查看。想要看運行效果,能夠全局安裝 http-server 模塊。

npm install -g http-server
複製代碼

本着學習和總結的態度寫的技術輸出,文中有任何錯誤和問題,請你們指出。更多的技術輸出能夠查看個人 github博客

整理了一些前端的學習資源,但願可以幫助到有須要的人,地址: 學習資源彙總

參考

相關文章
相關標籤/搜索