【跨域】JSONP/CORS/降域/postMessage

瀏覽器的同源策略

瀏覽器出于安全方面的考慮,只允許與本域下的接口交互。不同源的客戶端腳本在沒有明確授權(quán)的情況下,不能讀寫對方的資源。

本域指的是?

如:

http://zeeliu.com/a/b.js 和 http://zeeliu.com/index.php (同源)

不同源的例子:

http://zeeliu.com/main.js 和 https://zeeliu.com/a.php (協(xié)議不同)
http://zeeliu.com/main.js 和 http://bbs.zeeliu.com/a.php (域名不同,域名必須完全相同才可以)
http://zeeliu.com/main.js 和 http://zeeliu.com:8080/a.php (端口不同,第一個是80)

四中跨域方法

JSONP

JSONP是服務(wù)器與客戶端跨源通信的常用方法。最大特點就是簡單適用,老式瀏覽器全部支持,服務(wù)器改造非常小。

html中script標(biāo)簽可以引入其他域下的js,比如引入線上的jquery庫。利用這個特性,可實現(xiàn)跨域訪問接口。需要后端支持

  • 定義數(shù)據(jù)處理函數(shù):_fn
  • 創(chuàng)建script標(biāo)簽,src的地址執(zhí)行后端接口,最后加個參數(shù)callback=_fun
  • 服務(wù)端在收到請求后,解析參數(shù),計算返還數(shù)據(jù),輸出 fun(data) 字符串。
  • fun(data)會放到script標(biāo)簽做為js執(zhí)行。此時會調(diào)用fun函數(shù),將data做為參數(shù)。

代碼如下

index.html代碼:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>jsonp跨域</title>
  <style>
    .container {
      width: 500px;
      margin: 0 auto;
    }

  </style>
</head>
<body>
  <div class="container">
    <ul class="news">
      <li>海軍首批空中女戰(zhàn)勤加入戰(zhàn)斗序列</li>
      <li>運20完成首次人員空運試驗:乘客均為研發(fā)團(tuán)隊成員</li>
      <li>英媒稱中國將引領(lǐng)科技變革:新四大發(fā)明走向全球</li>        
    </ul>
    <button class="change">換一組</button>
  </div>

  <script>
  
    $('.change').addEventListener('click', function(){
      //點擊按鈕后創(chuàng)建一個<script>標(biāo)簽;且標(biāo)簽的src=“http://127.0.0.1:8080/getNews?callback=appendHtml”
      var script = document.createElement('script')
      script.src = 'http://127.0.0.1:8080/getNews?callback=appendHtml';
      document.head.appendChild(script) //把這個標(biāo)簽插入頭部;就會發(fā)送src請求
      document.head.removeChild(script) //發(fā)送請求后,這個script標(biāo)簽就沒用了,就立即刪除;
    })
    
    //由于發(fā)送的請求里面有關(guān)鍵字callback=appendHtml
    //所以當(dāng)后臺處理請求時候會把數(shù)據(jù)用這個關(guān)鍵詞和括號鏈接如:“appendHtml(數(shù)據(jù))”
    //下面這個函數(shù)就是用來解析數(shù)據(jù)的;
    function appendHtml(news){
      var html = ''
      for(var i=0; i<news.length; i++){
        html += '<li>' + news[i] + '</li>'
      }
      console.log(html)
      $('.news').innerHTML = html
    }
    
    //封裝的document選擇器
    function $(selector){
      return document.querySelector(selector)
    }

  </script>

</body>
</html>

router.js的代碼

// 注意這里的代碼要放在單獨的 router.js 文件中


router.get('/getNews', function (req, res) {

  var news = [
    "海軍首批空中女戰(zhàn)勤加入戰(zhàn)斗序列",
    "運20完成首次人員空運試驗:乘客均為研發(fā)團(tuán)隊成員",
    "英媒稱中國將引領(lǐng)科技變革:新四大發(fā)明走向全球",
    "印巴軍隊交火未平息 兩國代表聯(lián)合國又上演舌戰(zhàn)",
    "朝鮮的這樣?xùn)|西比“地震”更加震動美國人",
    "鄭家概履新武警部隊參謀長 接替秦天",
    "從仕途起點清除遺毒 這個落馬副部問題多嚴(yán)重?",
    "蔡英文'樸實'午宴曝光:等于退休人員全家4天菜錢"
  ]

  var data = []
  for (var i = 0; i < 3; i++) {
    var index = parseInt(Math.random() * news.length)
    data.push(news[index])
    news.splice(index, 1) //為了避免隨機(jī)到重復(fù)元素,所以push完一個就刪除當(dāng)前
  }

  var cb = req.query.callback
  if (cb) {
    res.send(cb + '(' + JSON.stringify(data) + ')')
  } else {
    res.send(data)
  }
})

上面index.html中<script src="http://127.0.0.1:8080/getNews?callback=appendHtml">標(biāo)簽的src就是一個請求
把這串地址在瀏覽器打開可以看到返回的數(shù)據(jù)如下圖:

WX20170924-220702.png

其實其他域名地址下的js就是利用這調(diào)串?dāng)?shù)據(jù)調(diào)用寫好的js函數(shù)實現(xiàn)解析數(shù)據(jù);實現(xiàn)跨域。


CORS

參考文章:阮一峰-跨域資源共享 CORS 詳解

CORS是一個W3C標(biāo)準(zhǔn),全稱是"跨域資源共享"(Cross-origin resource sharing)。
它允許瀏覽器向跨源服務(wù)器,發(fā)出XMLHttpRequest請求,從而克服了AJAX只能同源使用的限制。
本文詳細(xì)介紹CORS的內(nèi)部機(jī)制。

簡介

CORS需要瀏覽器和服務(wù)器同時支持。
目前,所有瀏覽器都支持該功能,IE瀏覽器不能低于IE10。
整個CORS通信過程,都是瀏覽器自動完成,不需要用戶參與。對于開發(fā)者來說,CORS通信與同源的AJAX通信沒有差別,代碼完全一樣。瀏覽器一旦發(fā)現(xiàn)AJAX請求跨源,就會自動添加一些附加的頭信息,有時還會多出一次附加的請求,但用戶不會有感覺。
因此,實現(xiàn)CORS通信的關(guān)鍵是服務(wù)器。只要服務(wù)器實現(xiàn)了CORS接口,就可以跨源通信。

基本流程

就是說當(dāng)我發(fā)送請求瀏覽器會默認(rèn)幫請求添加一個請求頭如圖下所示
(這都是由瀏覽器自己完成)

WX20170924-220929.png

上面的頭信息中,Origin字段用來說明,本次請求來自哪個源(協(xié)議 + 域名 + 端口)。服務(wù)器根據(jù)這個值,決定是否同意這次請求。

因此只要后端代碼里面同意著了來源的網(wǎng)站請求數(shù)據(jù)就可以了!
后代碼只要在原來的基礎(chǔ)上加一個響應(yīng)頭表示我同意這個來源的網(wǎng)站向我請求數(shù)據(jù)(如下圖)

WX20170924-221005.png

此時http://api.bob.com這個網(wǎng)站就可以跨域向這個后端請求數(shù)據(jù)了

CORS與JSONP的比較

CORS與JSONP的使用目的相同,但是比JSONP更強(qiáng)大。
JSONP只支持GET請求,CORS支持所有類型的HTTP請求。JSONP的優(yōu)勢在于支持老式瀏覽器,以及可以向不支持CORS的網(wǎng)站請求數(shù)據(jù)。


降域

由于第一次看視頻沒看懂;最終原因是里面新出現(xiàn)的標(biāo)簽不了解這里順便把新的知識點也記錄一下;

首先什么是降域?

首先我們知道瀏覽器會阻止兩個不同的域名之間進(jìn)行數(shù)據(jù)訪問操作;
但是當(dāng)我有一個域名為:zeeliu.com 還有兩個子域名:a.zeeliu.com和b.zeeliu.com
由于瀏覽器的特性a.zeeliu.com和b.zeeliu.com之間也是不能互相訪問數(shù)據(jù)的(同一個爸爸也沒用)
但是現(xiàn)實情況是我們希望這兩個域名之間互相訪問
所以就有了一個`document.domain`的api

例如:

a.zeeliu.com下的文件問index.html
b.zeeliu.com下的文件問index.html
在兩個index.html下同時寫入js代碼document.domain="zeeliu.com"
這是域名都降為zeeliu.com
所以網(wǎng)頁a.zeeliu.com(a.zeeliu.com:80/index.html)
和網(wǎng)頁b.zeeliu.com(b.zeeliu.com:80/index.html)
之間就可以跨域數(shù)據(jù)交互了

下面我們通過代碼在瀏覽器中測試

由于我們在同一個文件夾下測試所以兩個index.html文件風(fēng)別用a.html和b.html代替

代碼如下(里面有解釋)

a.html

<body>
    <style>
        .ct{
          width: 910px;
          margin: auto;
        }
        .main{
          float: left;
          width: 450px;
          height: 300px;
          border: 1px solid #ccc;
        }
        .main input{
          margin: 20px;
          width: 200px;
        }
        .iframe{
          float: right;
        }
        iframe{
          width: 450px;
          height: 300px;
          border: 1px dashed #ccc;
        }
      </style>
      
<!-- //面是樣式從下面開始解讀 -->
      <div class="ct">
        <h1>使用降域?qū)崿F(xiàn)跨域</h1>
        <div class="main">
          <input type="text" placeholder="http://a.zeeliu.com:8080/a.html我是輸入框默認(rèn)顯示">
        </div>
        
        <!-- //ifame標(biāo)簽可以在當(dāng)前窗口建立一個子窗口;窗口顯示b.zeeliu.com:8080/b.html這個域名的內(nèi)容 -->
        <iframe src="http://b.zeeliu.com:8080/b.html" frameborder="0" ></iframe>
      </div>

      <script>
      //URL: http://a.zeeliu.com:8080/a.html
      //添加一個事件輸入框內(nèi)容變化出發(fā)(input事件)
      document.querySelector('.main input').addEventListener('input', function(){
        console.log(this.value);//在控制臺打印輸入框輸入的內(nèi)容
        //window.frames獲取當(dāng)前窗口中的所有子窗口得到一個類數(shù)組對象,其實就是針對選中<ifame>創(chuàng)建的子窗口
        //下面的代碼是選中窗口后在通過document.querySelector('input')選中b.html中的輸入框(input)然后把當(dāng)前窗口輸入的值(this.value)賦給b.html中的input
        //就是a.html中輸入什么b.html中就顯示什么
        window.frames[0].document.querySelector('input').value = this.value;
      })
      
      //降域
      //只有a.html和b.html中同時降域為zeeliu.com上面的input事件中的賦值操作才生效
      //也就是a.html通過跨域操作了b.html中的內(nèi)容
      document.domain = "zeeliu.com"
      </script>
</body>

b.html

<body>
        <style>
                html,body{
                    margin: 0;
                }
                input{
                    margin: 20px;
                    width: 200px;
                }
            </style>
            
                <input id="input" type="text"  placeholder="http://b.zeeliu.com:8080/b.html我是輸入框默認(rèn)顯示">
            <script>
            // URL: http://b.zeeliu.com:8080/b.html
             
            document.querySelector('#input').addEventListener('input', function(){
                //同a.html中相同這邊這邊輸入框輸入上面同樣賦值給那邊
                //window.parent選擇當(dāng)前前窗口的父窗口一般只有一個
                window.parent.document.querySelector('input').value = this.value;
            })
            //降域
            document.domain = 'zeeliu.com';
            </script>
</body>

看上面的代碼基本可以知道降域是怎么回事了;


下面是如何在本地建立服務(wù)器測試這個代碼(包括修改hosts的內(nèi)容)
首先把a(bǔ).html和b.html放在同一個文件夾下
通過前面學(xué)到修改hosts的方法添加兩個域名(如下圖)

WX20170924-221140.png

然后在當(dāng)前文件夾mock start創(chuàng)建模擬服務(wù)器

沒有降域情況下(document.domain被注釋)

如下圖
輸入的地址為a.zeeliu.com:8080/a.html
虛線框窗口引用的地址是b.zeeliu.com:8080/b.html
所以左邊輸入aaaa右邊沒有變化
【這是域不同 且沒有降域】

WX20170924-221303.png

如下圖
當(dāng)輸入的地址為b.zeeliu.com:8080/a.html及時沒用降域也能實現(xiàn)左右同步
【這是因為他們的域相同】

WX20170924-221346.png

降域情況下

如下圖
通過document.domain降域后及時域名不同也可以實現(xiàn)效果

WX20170924-221443.png

postMessage--------------------------------------

其實就是在不降域的情況a.html向b.html發(fā)送自己想給b.html的內(nèi)容;然后b.html在接受這個內(nèi)容(你情我愿);反正b向a也是一樣

看代碼

a.html代碼

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <title>降域</title>
  <style>
    .ct{
      width: 910px;
      margin: auto;
    }
    .main{
      float: left;
      width: 450px;
      height: 300px;
      border: 1px solid #ccc;
    }
    .main input{
      margin: 20px;
      width: 200px;
    }
    .iframe{
      float: right;
    }
    iframe{
      width: 450px;
      height: 300px;
      border: 1px dashed #ccc;
    }
  </style>
</head>
<body>
  <div class="ct">
    <h1>使用postMessage實現(xiàn)跨域</h1>
    <div class="main">
      <input type="text" placeholder="http://a.zeeliu.com:8080/a.html">
    </div>
    <iframe src="http://b.zeeliu.com:8080/b.html" frameborder="0" ></iframe>
  </div>
  <script>

  //當(dāng)輸入框內(nèi)發(fā)生變化觸發(fā)事件  
  $('.main input').addEventListener('input', function(){
    console.log(this.value);
    //將當(dāng)前窗口的值通過.postMessage發(fā)送給window.frames[0]所選中的窗口
    //this.value是要發(fā)送的值;(可以使其他的值)
    //'*':代表任何網(wǎng)站;(當(dāng)輸入b.zeeliu.com:8080/b.html則只發(fā)給這個域名)
    window.frames[0].postMessage(this.value,'*');
  })

  //這個事件監(jiān)聽從b.html發(fā)送過來的數(shù)據(jù);
  //e.data就是接收到的數(shù)據(jù)(b.html中的.postMessage()發(fā)送過來的)
  window.addEventListener('message',function(e) {
      $('.main input').value = e.data
      console.log(e.data);
  });
  function $(id){
    return document.querySelector(id);
  }
  </script>
</body>
</html>

b.html代碼

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <title>降域</title>
    <style>
        html,body{
            margin: 0;
        }
        input{
            margin: 20px;
            width: 200px;
        }
    </style>
</head>
<body>
    <input id="input" type="text"  placeholder="http://b.zeeliu.com:8080/b.html">
    <script>
    $('#input').addEventListener('input', function(){
        //將當(dāng)前窗口的值通過.postMessage發(fā)送給window.parent所選中的窗口
        //this.value是要發(fā)送的值;(可以使其他的值)
        //'*':代表任何網(wǎng)站;(當(dāng)輸入a.zeeliu.com:8080/a.html則只發(fā)給這個域名)
        window.parent.postMessage(this.value, '*');
    })

    //這個事件監(jiān)聽從a.html發(fā)送過來的數(shù)據(jù);
    //e.data就是接收到的數(shù)據(jù)(a.html中的.postMessage()發(fā)送過來的)
    window.addEventListener('message',function(e) {
            $('#input').value = e.data
        console.log(e.data);
    });
    function $(id){
        return document.querySelector(id);
    }
    </script>
</body>
</html>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 什么是同源策略 瀏覽器出于安全方面的考慮,只允許與本域下的接口交互。不同源的客戶端腳本在沒有明確授權(quán)的情況下,不能...
    謝夢揚_閱讀 452評論 0 0
  • 1.什么是同源策略瀏覽器出于安全方面的考慮,只允許與本域下的接口交互。不同源的客戶端腳本在沒有明確授權(quán)的情況下,不...
    24_Magic閱讀 584評論 0 0
  • 1: 什么是同源策略 最初,它的含義是指,A網(wǎng)頁設(shè)置的 Cookie,B網(wǎng)頁不能打開,除非這兩個網(wǎng)頁"同源",所謂...
    好奇而已閱讀 350評論 0 0
  • 今天,我放下那么多的家務(wù),就想為你寫點東西,留下我們點點滴滴的回憶。未曾想,為你寫的文字竟然得不到你的鼓...
    37度女王閱讀 349評論 0 0
  • 我只是害怕有那么一天我不再憤世嫉俗,也活成了別人的附屬品,而那一天到來之際我還沒有強(qiáng)大到跟韓寒一樣可以恣意...
    凌洛依閱讀 1,126評論 0 0

友情鏈接更多精彩內(nèi)容