1. 程式人生 > 實用技巧 >跨域的幾種實現方式

跨域的幾種實現方式

講到跨域不得不說一說,為什麼要跨域?--瀏覽器的同源策略。

瀏覽器的同源策略(Same Origin Policy)

源(Origin)是由 URL 中協議、主機名(域名 domain)以及埠共同組成的部分。在上面的網址中,源由協議 http、主機名www.baidu.com 和預設埠 8080 共同組成。

如果兩個 URL 的源相同,我們就稱之為同源。下面的 3 個 URL 和示例 URL 都是不同的源。

  • https://www.baidu.com:8080/other 協議不同
  • http://www.baidu.com:80/other 埠不同
  • http://www.tengxun.com:8080/other 主機名(域名)不同

當一個源訪問另一個源的資源時就會產生跨源。同源策略就是用來限制其中一些跨源訪問的,包括訪問 iframe 中的頁面、其他頁面的 cookie 訪問以及傳送 AJAX 請求。最常見的跨源場景是域名不同,即常說的“跨域”。

跨域解決方案

  • 跨域資源共享

  跨域資源共享(CORS,Cross-Origin Resource Sharing)是瀏覽器為 AJAX 請求設定的一種跨域機制,讓其可以在服務端允許的情況下進行跨域訪問。

  主要通過 HTTP 響應頭來告訴瀏覽器服務端是否允許當前域的指令碼進行跨域訪問。

  跨域資源共享將 AJAX 請求分成了兩類:簡單請求非簡單請求

  簡單請求

    • 請求方法為 GET、POST、HEAD。
    • 請求頭只能使用下面的欄位:Accept(瀏覽器能夠接受的響應內容型別)、Accept-Language(瀏覽器能夠接受的自然語言列表)、Content-Type (請求對應的型別,只限於 text/plain、multipart/form-data、application/x-www-form-urlencoded)、Content-Language(瀏覽器希望採用的自然語言)、Save-Data(瀏覽器是否希望減少資料傳輸量)。

    處理流程:

    1. 瀏覽器發出簡單請求的時候,會在請求頭部增加一個 Origin 欄位,對應的值為當前請求的源資訊;
    2. 當服務端收到請求後,會根據請求頭欄位 Origin 做出判斷後返回相應的內容。
    3. 瀏覽器收到響應報文後會根據響應頭部欄位 Access-Control-Allow-Origin 進行判斷,這個欄位值為服務端允許跨域請求的源,其中萬用字元“*”表示允許所有跨域請求。如果頭部資訊沒有包含 Access-Control-Allow-Origin 欄位或者響應的頭部欄位 Access-Control-Allow-Origin 不允許當前源的請求,則會丟擲錯誤。

  非簡單請求

    只要有一條要求不符合的即為非簡單請求。

    處理流程:

    當處理非簡單的請求時,瀏覽器會先發出一個預檢請求(Preflight)。這個預檢請求為 OPTIONS 方法,並會添加了 1 個請求頭部欄位 Access-Control-Request-Method,值為跨域請求所使用的請求方法。

  • JSONP

  JSONP(JSON with Padding)的大概意思就是用 JSON 資料來填充,怎麼填充呢?結合它的實現方式可以知道,就是把 JSON 數填充到一個回撥函式中。

  依賴的是 script 標籤跨域引用 js 檔案不會受到瀏覽器同源策略的限制。

  具體實現方式:

  假設我們要在 http://www.1.com 中向 http://www.2.com 請求資料。

    1.申明一個回撥函式fn,引數為請求返回的資料

function fn(result) {
  console.log(result)
}

    2.將函式名與其他引數一併寫入 URL 中

var url = 'http://www.2.com?callback=fn&params=...';

    3.建立一個 script 標籤,把 URL 賦值給 script 的 src

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

    4.當伺服器接收到請求後,解析 URL 引數並進行對應的邏輯處理,得到結果後將其寫成回撥函式的形式並返回給瀏覽器

fn({
 attribut: '',
  ...
})

    5.在瀏覽器收到請求返回的 js 指令碼之後會立即執行檔案內容,即在控制檯列印傳入的資料內容

  JSONP存在以下幾個問題:

  1. 只能傳送 GET 請求,限制了引數大小和型別;
  2. 請求過程無法終止,導致弱網路下處理超時請求比較麻煩;
  3. 無法捕獲服務端返回的異常資訊。
  • Websocket

  它是HTML5 規範提出的一個應用層的全雙工協議,適用於瀏覽器與伺服器進行實時通訊場景。

  “雙工”是指從客戶端到服務端,以及從服務端到客戶端兩個方向都可以通訊,“全”指的是通訊雙方可以同時向對方傳送資料,“工”指的是通訊方向。

  案例:在 a 網站直接建立一個 WebSocket 連線,連線到 b 網站即可,然後呼叫 WebScoket 例項 ws 的 send() 函式向服務端傳送訊息,監聽例項 ws 的 onmessage 事件得到響應內容。

var ws = new WebSocket("ws://b.com");
ws.onopen = function(){
 // ws.send(...);
}
ws.onmessage = function(e){
 // console.log(e.data);
}
  • 代理轉發

  跨域是為了突破瀏覽器的同源策略限制,既然同源策略只存在於瀏覽器,那可以換個思路,在服務端進行跨域,比如設定代理轉發。這種在服務端設定的代理稱為“反向代理”,對於使用者而言是無感知的。

  另一種在客戶端使用的代理稱為“正向代理”,主要用來代理客戶端傳送請求,使用者使用時必須配置代理伺服器的網址,比如常用的 VPN 工具就屬於正向代理。

  代理轉發實現起來非常簡單,在當前被訪問的伺服器配置一個請求轉發規則就行了。

  下面的程式碼是 webpack-dev-server 配置代理的示例程式碼。當瀏覽器發起字首為 /api 的請求時都會被轉發到 http://localhost:1000 這個網址,然後將響應結果返回給瀏覽器。對於瀏覽器而言還是請求當前網站,但實際上已經被服務端轉發。

// webpack.config.js
module.exports = {
  //...
  devServer: {
    proxy: {
      '/api': 'http://localhost:1000'
    }
  }
};

頁面跨域解決方案

  除了瀏覽器請求跨域之外,頁面之間也會有跨域需求,例如使用 iframe 時父子頁面之間進行通訊。

  HTML5 推出了一個新的函式 postMessage() 用來實現父子頁面之間通訊,而且不論這兩個頁面是否同源。

  舉例來說,如果父頁面 http://a.com要向子頁面 http://b.com發訊息,可以通過下面的程式碼實現。

// http://a.com
var child = window.open('http://b.com');
child.postMessage('hi', 'http://b.com');

  通過 window.open() 函式打開了子頁面,然後呼叫 child.postMessage() 函式傳送了字串資料“hi”給子頁面。

  在子頁面中,只需要監聽“message”事件即可得到父頁面的資料。程式碼如下:

// http://b.com
window.addEventListener('message', function(e) {
 console.log(e.data);
},false);

  同樣的,父頁面也可以監聽“message”事件來接收子頁面傳送的資料。子頁面傳送資料時則要通過 window.opener 物件來呼叫 postMessage() 函式。

// http://b.com
window.opener.postMessage('hello', 'http://a.com');

完結。