跨域通訊

>>>點擊獲取更多文章<<<javascript

同源策略限制範圍

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

跨域圖表

那到底什麼是跨域,簡單地理解就是由於JavaScript同源策略的限制,a.com 域名下的js沒法操做b.com或是c.a.com域名下的對象。更詳細的說明能夠看下錶:php

跨域圖表

特別注意兩點:html

  • 第一,若是是協議和端口形成的跨域問題「前臺」是無能爲力的,
  • 第二:在跨域問題上,域僅僅是經過「URL的首部」來識別而不會去嘗試判斷相同的ip地址對應着兩個域或兩個域是否在同一個ip上。

「URL的首部」指window.location.protocol +window.location.host,也能夠理解爲「Domains, protocols and ports must match」(本段來自網絡,我的以爲這段對js跨域描述得在清晰不過了)。java

跨域請求無處不在,下面來看看咱們都是如何處理跨域請求的:jquery

方法1 動態建立script

雖然瀏覽器默認禁止了跨域訪問,但並不由止在頁面中引用其餘域的JS文件,script標籤的src屬性引用指向接收方的一個處理地址(後臺),該地址返回的javascript方法會被執行,另外URL中能夠傳入一些參數,該方法只支持GET方式提交參數。咱們經常使用FloadJS方法用的就是這種跨域方式。web

alt text

其中jquery的getScript 方法 就是相似那樣的方法(經過 GET 方式請求載入並執行一個 JavaScript 文件, 至關於經過src的形式的導入一個外部的js)。ajax

語法:jQuery.getScript(url,success(response,status)),該函數是簡寫的 Ajax 函數,等價於:算法

$.ajax({
      Type: get,
      url: url,
      dataType: "script",
      success: success
    });

方法2 利用JSONP

JSONP是服務器與客戶端跨源通訊的經常使用方法。最大特色就是簡單適用,老式瀏覽器所有支持,服務器改造很是小。json

它的基本思想是,網頁經過添加一個script元素,向服務器請求JSON數據,這種作法不受同源政策限制;服務器收到請求後,將數據放在一個指定名字的回調函數裏傳回來。跨域

首先,網頁動態插入script元素,由它向跨源網址發出請求。

function addScriptTag(src) {
  var script = document.createElement('script');
  script.setAttribute("type","text/javascript");
  script.src = src;
  document.body.appendChild(script);
}

window.onload = function () {
  addScriptTag('http://example.com/ip?callback=foo');
}

function foo(data) {
  console.log('Your public IP address is: ' + data.ip);
};

上面代碼經過動態添加script元素,向服務器example.com發出請求。注意,該請求的查詢字符串有一個callback參數,用來指定回調函數的名字,這對於JSONP是必需的。

服務器收到這個請求之後,會將數據放在回調函數的參數位置返回。

foo({
  "ip": "8.8.8.8"
});

因爲script元素請求的腳本,直接做爲代碼運行。這時,只要瀏覽器定義了foo函數,該函數就會當即調用。做爲參數的JSON數據被視爲JavaScript對象,而不是字符串,所以避免了使用JSON.parse的步驟。

其中 jQuery.getJSON(url,data,success(data,status,xhr)) 都是利用jsonp的道理 ,該函數是簡寫的 Ajax 函數,等價於:

$.ajax({
      url: url,
      data: data,
      success: callback,
      dataType: json
    });

另外,jsonp目前只支持get請求方式,對post請求不支持。JSONP的優點在於支持老式瀏覽器,以及能夠向不支持CORS的網站請求數據。

方法3 document.domain(只適用於Cookie和iframe窗口)

針對cookie

舉例來講,A網頁是http://w1.example.com/a.html,B網頁是http://w2.example.com/b.html,那麼只要設置相同的document.domain,兩個網頁就能夠共享Cookie。

document.domain = 'example.com';

如今,A網頁經過腳本設置一個 Cookie。

document.cookie = "test1=hello";

B網頁就能夠讀到這個 Cookie。

var allCookie = document.cookie;
  • 兩個網頁一級域名必須相同,只是二級域名不一樣。
  • 兩個網頁設置相同的document.domain
  • 服務器也能夠在設置Cookie的時候,指定Cookie的所屬域名爲一級域名,好比.example.com。代碼: Set-Cookie: key=value; domain=.example.com; path=/ 這樣的話,二級域名和三級域名不用作任何設置,均可以讀取這個Cookie。

針對iframe

若是兩個網頁不一樣源,就沒法拿到對方的DOM。典型的例子是iframe窗口和window.open方法打開的窗口,它們與父窗口沒法通訊。好比,父窗口運行下面的命令,若是iframe窗口不是同源,就會報錯。

document.getElementById("myIFrame").contentWindow.document
// Uncaught DOMException: Blocked a frame from accessing a cross-origin frame.

上面命令中,父窗口想獲取子窗口的DOM,由於跨源致使報錯。反之亦然,子窗口獲取主窗口的DOM也會報錯。

window.parent.document.body
// 報錯

若是兩個窗口一級域名相同,只是二級域名不一樣,那麼設置上一節介紹的document.domain屬性,就能夠規避同源政策,拿到DOM。

方法4 window.postMessage

語法

otherWindow.postMessage(message, targetOrigin, [transfer]);

__otherWindow__,其餘窗口的一個引用,好比iframe的contentWindow屬性、執行window.open返回的窗口對象、或者是命名過或數值索引的window.frames。好比document.getElementsByTagName('iframe')[0].contentWindow; window.open('http://bbb.com', 'iframeName'); window.frames[0];

__message__,將要發送到其餘 window的數據。它將會被結構化克隆算法序列化。這意味着你能夠不受什麼限制的將數據對象安全的傳送給目標窗口而無需本身序列化。

__targetOrigin__,經過窗口的origin屬性來指定哪些窗口能接收到消息事件,其值能夠是字符串"*"(表示無限制)或者一個URI。在發送消息的時候,若是目標窗口的協議、主機地址或端口這三者的任意一項不匹配targetOrigin提供的值,那麼消息就不會被髮送;只有三者徹底匹配,消息纔會被髮送。

__transfer__,是一串和message 同時傳遞的 Transferable 對象. 這些對象的全部權將被轉移給消息的接收方,而發送一方將再也不保有全部權。

如何監聽

父窗口和子窗口均可以經過message事件,監聽對方的消息。message事件的事件對象event,提供如下三個屬性。

  • event.source:發送消息的窗口
  • event.origin: 消息發向的網址
  • event.data: 消息內容

代碼以下:

window.addEventListener('message', receiveMessage);
function receiveMessage(event) {
  if (event.origin !== 'http://aaa.com') return;
  if (event.data === 'Hello World') {
      event.source.postMessage('Hello', event.origin);
  } else {
    console.log(event.data);
  }
}

詳細例子

其中,本身作了一個例子,父窗口按期發送隨機消息給子窗口,子窗口接收隨機信息,再反饋給父窗口進行跨域通訊,詳情效果請點擊觀看。具體代碼以下:

//http://www.zhangbing.club/images/file/postmessage.html頁面代碼:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>

</head>
<body>
    
        <div id="parentdiv"></div>

    
        <iframe name="childframe" id="childframe" src="http://js.zhangbing.name/demoa12.html" frameborder="1" width="700px" height="300px"></iframe>

        <script type="text/javascript">


                window.onload=function(){
                  
                    setInterval(function(){
                        window.frames[0].postMessage('我是來自dotClub域名的消息; | 隨機數:'+Math.random(),'http://js.zhangbing.name');
                    },1000)
                }


                window.addEventListener('message', function(e) {
                    document.getElementById('parentdiv').innerHTML = e.data;
                },false);
        
        
            </script>

</body>
</html>


//http://js.zhangbing.name/demoa12.html頁面代碼:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>child</title>

</head>
<body>
    
        <div id="childdiv"></div>

        <script type="text/javascript">


            function receiveMessage(event)
            {
                // 咱們能信任信息來源嗎?
                console.log(event.origin);
                if (event.origin !== "http://www.zhangbing.club") return;

                   
                document.getElementById('childdiv').innerHTML = event.data;


                // 假設你已經驗證了所受到信息的origin (任什麼時候候你都應該這樣作), 一個很方便的方式就是把enent.source
                // 做爲回信的對象,而且把event.origin做爲targetOrigin
                var a = event.data.split("|"); 
                event.source.postMessage("我是來自dotName域名的消息 |  接收的隨機數是:"+a[1]+"| 如今反饋給你",event.origin);
            }

            window.addEventListener("message", receiveMessage, false);


        </script> 
</body>
</html>

方法5 WebSocket

WebSocket是一種通訊協議,使用ws://(非加密)和wss://(加密)做爲協議前綴。該協議不實行同源政策,只要服務器支持,就能夠經過它進行跨源通訊。

下面是一個例子,瀏覽器發出的WebSocket請求的頭信息(摘自維基百科)。

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com

上面代碼中,有一個字段是Origin,表示該請求的請求源(origin),即發自哪一個域名。

正是由於有了Origin這個字段,因此WebSocket纔沒有實行同源政策。由於服務器能夠根據這個字段,判斷是否許可本次通訊。若是該域名在白名單內,服務器就會作出以下回應。

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat

方法6 架設服務器代理

基本原理就是,由客戶端將請求發給同域服務器,再由同域服務器的代理來請求數據並將響應返回給客戶端。

方法7 CORS

CORS是一個W3C標準,全稱是"跨域資源共享"(Cross-origin resource sharing)。__它容許瀏覽器向跨源服務器,發出XMLHttpRequest(Level2)請求,從而克服了XMLHttpRequest老版本只能向同一域名的服務器請求數據__。

CORS須要瀏覽器和服務器同時支持。目前,全部瀏覽器都支持該功能,IE瀏覽器不能低於IE10。整個CORS通訊過程,都是瀏覽器自動完成,不須要用戶參與。對於開發者來講,CORS通訊與同源的AJAX通訊沒有差異,代碼徹底同樣。瀏覽器一旦發現AJAX請求跨源,就會自動添加一些附加的頭信息,有時還會多出一次附加的請求,但用戶不會有感受。所以,__實現CORS通訊的關鍵是服務器__。只要服務器實現了CORS接口,就能夠跨源通訊。

下面是具體php例子的服務器支持代碼:

// 指定容許其餘域名訪問  
$origin = isset($_SERVER['HTTP_ORIGIN'])? $_SERVER['HTTP_ORIGIN'] : '';  
  
$allow_origin = array(  
    'http://localhost:1234',
    'http://cms.cjwsc.com'
);  

if(in_array($origin, $allow_origin)){  
    header('Access-Control-Allow-Origin:'.$origin);       
}

// 響應類型  
header("Access-Control-Allow-Methods: GET, POST, DELETE");  
// 容許跨域請求帶cookie
header("Access-Control-Allow-Credentials: true");
// 響應頭設置  
header("Access-Control-Allow-Headers: Content-Type, X-Requested-With, Cache-Control,Authorization");

CORS能夠作不少事情和選擇,能夠支持全部類型的HTTP請求,和是否帶上cookie等!

更多詳細理解CORS原理,請移步到個人文章CORS跨域

相關文章
相關標籤/搜索