八種方式實現跨域請求

瀏覽器的同源策略

提到跨域不能不先說一下」同源策略」。

何爲同源?只有當協議、端口、和域名都相同的頁面,則兩個頁面具備相同的源。只要網站的 協議名protocol、 主機host、 端口號port 這三個中的任意一個不一樣,網站間的數據請求與傳輸便構成了跨域調用,會受到同源策略的限制。 html

同源策略限制從一個源加載的文檔或腳本如何與來自另外一個源的資源進行交互。這是一個用於隔離潛在惡意文件的關鍵的安全機制。瀏覽器的同源策略,出於防範跨站腳本的攻擊,禁止客戶端腳本(如 JavaScript)對不一樣域的服務進行跨站調用(一般指使用XMLHttpRequest請求)。nginx

跨域請求方式


解決跨域問題,最簡單的莫過於經過nginx反向代理進行實現,可是其須要在運維層面修改,且有可能請求的資源並再也不咱們控制範圍內(第三方),因此該方式不能做爲通用的解決方案,下面闡述了常常用到幾種跨域方式:跨域

方式一:圖片ping或script標籤跨域

圖片ping經常使用於跟蹤用戶點擊頁面或動態廣告曝光次數。
script標籤能夠獲得從其餘來源數據,這也是JSONP依賴的根據。
缺點:只能發送Get請求 ,沒法訪問服務器的響應文本(單向請求)瀏覽器

方式二:JSONP跨域


JSONP(JSON with Padding)是數據格式JSON的一種「使用模式」,可讓網頁從別的網域要數據。根據 XmlHttpRequest 對象受到同源策略的影響,而利用 <script>元素的這個開放策略,網頁能夠獲得從其餘來源動態產生的JSON數據,而這種使用模式就是所謂的 JSONP。用JSONP抓到的數據並非JSON,而是任意的JavaScript,用 JavaScript解釋器運行而不是用JSON解析器解析。全部,經過Chrome查看全部JSONP發送的Get請求都是js類型,而非XHR。 安全

缺點:服務器

只能使用Get請求
不能註冊success、error等事件監聽函數,不能很容易的肯定JSONP請求是否失敗
JSONP是從其餘域中加載代碼執行,容易受到跨站請求僞造的攻擊,其安全性沒法確保app

方式三:CORS


Cross-Origin Resource Sharing(CORS)跨域資源共享是一份瀏覽器技術的規範,提供了 Web 服務從不一樣域傳來沙盒腳本的方法,以避開瀏覽器的同源策略,確保安全的跨域數據傳輸。現代瀏覽器使用CORS在API容器如XMLHttpRequest來減小HTTP請求的風險來源。與 JSONP 不一樣,CORS 除了 GET 要求方法之外也支持其餘的 HTTP 要求。服務器通常須要增長以下響應頭的一種或幾種:運維

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400

跨域請求默認不會攜帶Cookie信息,若是須要攜帶,請配置下述參數:dom

"Access-Control-Allow-Credentials": true
// Ajax設置
"withCredentials": true

方式四:window.name+iframe


window.name經過在iframe(通常動態建立i)中加載跨域HTML文件來起做用。而後,HTML文件將傳遞給請求者的字符串內容賦值給window.name。而後,請求者能夠檢索window.name值做爲響應。ide

  • iframe標籤的跨域能力;
  • window.name屬性值在文檔刷新後依舊存在的能力(且最大容許2M左右)。

每一個iframe都有包裹它的window,而這個window是top window的子窗口。contentWindow屬性返回<iframe>元素的Window對象。你可使用這個Window對象來訪問iframe的文檔及其內部DOM。

<!-- 
 下述用端口 
 10000表示:domainA
 10001表示:domainB
-->

<!-- localhost:10000 -->
<script>
  var iframe = document.createElement('iframe');
  iframe.style.display = 'none'; // 隱藏

  var state = 0; // 防止頁面無限刷新
  iframe.onload = function() {
      if(state === 1) {
          console.log(JSON.parse(iframe.contentWindow.name));
          // 清除建立的iframe
          iframe.contentWindow.document.write('');
          iframe.contentWindow.close();
          document.body.removeChild(iframe);
      } else if(state === 0) {
          state = 1;
          // 加載完成,指向當前域,防止錯誤(proxy.html爲空白頁面)
          // Blocked a frame with origin "http://localhost:10000" from accessing a cross-origin frame.
          iframe.contentWindow.location = 'http://localhost:10000/proxy.html';
      }
  };

  iframe.src = 'http://localhost:10001';
  document.body.appendChild(iframe);
</script>

<!-- localhost:10001 -->
<!DOCTYPE html>
...
<script>
  window.name = JSON.stringify({a: 1, b: 2});
</script>
</html>

注意:

  1. 直接嵌入其餘域(localhots:10001)下的URL會報錯,因此須要加載完成替換爲當前域的URL(localhots:10000),proxy.html爲空白頁面,只爲解決該問題;
  2. 從新設置src(http://localhost:10000/proxy.html)後致使頁面不斷刷新,因此經過state來控制;
  3. 所有獲取完結果後,清除該iframe。

方式五:window.postMessage()


HTML5新特性,能夠用來向其餘全部的 window 對象發送消息。須要注意的是咱們必需要保證全部的腳本執行完才發送 MessageEvent,若是在函數執行的過程當中調用了它,就會讓後面的函數超時沒法執行。

下述代碼實現了跨域存儲localStorage

<!-- 
 下述用端口 
 10000表示:domainA
 10001表示:domainB
-->

<!-- localhost:10000 -->
<iframe src="http://localhost:10001/msg.html" name="myPostMessage" style="display:none;">
</iframe>

<script>
  function main() {
      LSsetItem('test', 'Test: ' + new Date());
      LSgetItem('test', function(value) {
          console.log('value: ' + value);
      });
      LSremoveItem('test');
  }

  var callbacks = {};
  window.addEventListener('message', function(event) {
      if (event.source === frames['myPostMessage']) {
          console.log(event)
          var data = /^#localStorage#(\d+)(null)?#([\S\s]*)/.exec(event.data);
          if (data) {
              if (callbacks[data[1]]) {
                  callbacks[data[1]](data[2] === 'null' ? null : data[3]);
              }
              delete callbacks[data[1]];
          }
      }
  }, false);

  var domain = '*';
  // 增長
  function LSsetItem(key, value) {
      var obj = {
          setItem: key,
          value: value
      };
      frames['myPostMessage'].postMessage(JSON.stringify(obj), domain);
  }
  // 獲取
  function LSgetItem(key, callback) {
      var identifier = new Date().getTime();
      var obj = {
          identifier: identifier,
          getItem: key
      };
      callbacks[identifier] = callback;
      frames['myPostMessage'].postMessage(JSON.stringify(obj), domain);
  }
  // 刪除
  function LSremoveItem(key) {
      var obj = {
          removeItem: key
      };
      frames['myPostMessage'].postMessage(JSON.stringify(obj), domain);
  }
</script>

<!-- localhost:10001 -->
<script>
  window.addEventListener('message', function(event) {
    console.log('Receiver debugging', event);
    if (event.origin == 'http://localhost:10000') {
      var data = JSON.parse(event.data);
      if ('setItem' in data) {
        localStorage.setItem(data.setItem, data.value);
      } else if ('getItem' in data) {
        var gotItem = localStorage.getItem(data.getItem);
        event.source.postMessage(
          '#localStorage#' + data.identifier +
          (gotItem === null ? 'null#' : '#' + gotItem),
          event.origin
        );
      } else if ('removeItem' in data) {
        localStorage.removeItem(data.removeItem);
      }
    }
  }, false);
</script>

注意Safari下會報錯:

Blocked a frame with origin 「 http://localhost:10001」 from accessing a frame with origin 「 http://localhost:10000「. Protocols, domains, and ports must match.

避免該錯誤,能夠在Safari瀏覽器中勾選開發菜單==>停用跨域限制。或者只能使用服務器端轉存的方式實現,由於Safari瀏覽器默認只支持CORS跨域請求。

方式六:修改document.domain跨子域


前提條件:這兩個域名必須屬於同一個基礎域名!並且所用的協議,端口都要一致,不然沒法利用document.domain進行跨域,因此只能跨子域

​在根域範圍內,容許把domain屬性的值設置爲它的上一級域。例如,在」aaa.xxx.com」域內,能夠把domain設置爲 「xxx.com」 但不能設置爲 「xxx.org」 或者」com」。

如今存在兩個域名aaa.xxx.com和bbb.xxx.com。在aaa下嵌入bbb的頁面,因爲其document.name不一致,沒法在aaa下操做bbb的js。能夠在aaa和bbb下經過js將document.name = 'xxx.com';設置一致,來達到互相訪問的做用。

方式七:WebSocket

WebSocket protocol 是HTML5一種新的協議。它實現了瀏覽器與服務器全雙工通訊,同時容許跨域通信,是server push技術的一種很棒的實現。相關文章,請查看:WebSocket、WebSocket-SockJS

須要注意:WebSocket對象不支持DOM 2級事件偵聽器,必須使用DOM 0級語法分別定義各個事件。

方式八:代理

同源策略是針對瀏覽器端進行的限制,能夠經過服務器端來解決該問題

DomainA客戶端(瀏覽器) ==> DomainA服務器 ==> DomainB服務器 ==> DomainA客戶端(瀏覽器)

相關文章
相關標籤/搜索