同源策略與跨域技術

1、同源策略

瀏覽器的同源策略限制了 從一個源加載的文檔或腳本與來自另外一個源的資源的交互。它是隔離潛在惡意文檔的關鍵安全機制。php

具體限制:css

  1. 不能經過ajax的方法去請求不一樣源的資源。
  2. 瀏覽器中不一樣域的框架之間是不能進行js的交互操做的。

1. 同源的定義

若是兩個頁面具備相同的協議、域名和端口(若是有指定),則這兩個頁面具備相同的源。html

Tips:http協議默認端口是80,https默認端口是443。前端

2. 源的更改

腳本能夠將 document.domain的值設置爲當前域或當前域的父域。若是設置爲超級域,那麼超級域將用於後續的源檢查。html5

Eg:對頁面 http://store.company.com/dir/other.html 進行域的修改:git

document.domain = 'company.com';

該js執行後,該頁面將會成功地經過對http://company.com/dir/page.html的同源檢測。github

注意:

  1. 只能將當前域設置爲其父域,不能設置爲其餘域,如store.company.com能夠設置爲company.com,不能設置爲othercompany.com
  2. 對document.domain的賦值操做會致使端口號被重寫爲null。因此store.company.com:8080即便設置了document.domain = 'company.com',也仍是不能和company.com通訊。由於此時store.company.com的域名雖然是company.com,但端口號是null;而company.com的端口號多是80;兩者端口號不一致,仍是不一樣源。因此必須在頁面company.com也進行document.domain = document.domain,即雙方都必須進行賦值操做,以確保端口號都爲null
  3. 使用document.domain來修改子域域名以訪問其父域時,須要在父域和子域中設置document.domain都爲父域的值。這樣作是必要的,緣由除了2.中所述緣由之外,還由於不這樣作可能會致使權限問題。
  4. 修改document.domain的方法 只適用於不一樣子域的框架(即iframe/frame)間的交互不能用於Ajax——即便設置了相同的document.domain,仍是不能進行Ajax請求

3.正常的跨域網絡訪問

一般容許跨源資源嵌入(Cross-origin embedding)。web

有如下這些狀況:ajax

(1)script標籤嵌入跨源腳本

<script src="...">
</script>

(2)rel=stylesheet的link標籤嵌入css

<link rel="stylesheet" href="...">

css跨域須要設置一個正確的Content-Type消息頭。不一樣瀏覽器有不一樣限制。通常均可以成功跨源獲取css資源。json

(3)img嵌入圖片,video/audio/object嵌入多媒體資源

(4) @font-face引入字體

一些瀏覽器容許跨域字體( cross-origin fonts),一些須要同源字體(same-origin fonts)。

(5)frame和iframe載入的任何資源

iframe自己就是能夠跨域的

站點可使用 X-Frame-Origins消息頭來阻止這種跨域。

Tips:關於X-Frame-Origins

X-Frame-Origins,是一個HTTP響應頭,用來指示瀏覽器是否容許一個頁面能夠在iframe/frame/object中展現。網站可使用此功能,來確保本身網站的內容沒有被嵌到別人的網站中去。

可能值:

  • DENY:不容許該頁面在其餘頁面的frame/iframe中展現,不管那個其餘頁面和該頁面是不是同源。
  • SAMEORIGIN:該頁面能夠在同源頁面的frame/iframe中展現。
  • ALLOW-FROM uri:該頁面能夠在指定源的頁面的frame/iframe中展現。
測試
Test1: 嵌入baidu.com:
<iframe width="1000" height="800" src="https://www.baidu.com"></iframe>

該頁面能夠正確地嵌入百度首頁。可是控制檯會報以下錯誤信息:

Uncaught DOMException: Blocked a frame with origin "https://www.baidu.com" from accessing a cross-origin frame
  at HTMLDocument.t...

能夠發現,這些跨域錯誤信息都是因爲須要進行js交互纔出現的。

Test2: 嵌入ftchinese.com:
<iframe width="1000" height="800" src="http://www.ftchinese.com"></iframe>

iframe區域展示的是空白。而後console控制檯報錯的信息爲:

Refused to display 'http://www.ftchinese.com/' in a frame because it set 'X-Frame-Options' to 'deny'.

4. 不容許的跨域網絡訪問

一般不容許跨域讀操做(Cross-origin reads)。通常不能經過ajax的方法去請求不一樣源的資源。 瀏覽器中不一樣域的框架之間也是不能進行js的交互操做的。

可是一般能夠經過內嵌資源等方式來巧妙的進行讀寫訪問。Ajax通過特殊設置也能夠實現跨域Ajax通訊。不一樣源的框架間在必定條件限制下也能夠經過必定手段實現js交互。

2、跨域技術

1. CORS

理論概述

CORS(Cross-Origin Resource Sharing,跨域資源共享)定義了在必須訪問跨域資源時,瀏覽器與服務器應該如何溝通。

CORS的基本思想是設置某些HTTP頭部字段讓瀏覽器和服務器進行溝通,從而決定請求或響應是應該成功仍是失敗。

須要關注的HTTP頭部字段:
  • 請求頭 Origin:在發送跨域請求時,請求頭會附加一個Origin字段,其值是發送請求的頁面的源(包括協議、域名、端口)。具體操做:
    • 若是使用XMLHttpRequest,那麼在xhr.send()方法中填入請求目標地址的絕對url便可;
    • 若是使用Fetch,那麼須要設置請求參數'mode'爲'cors'。
  • 響應頭 Access-Control-Allow-Origin: 若是服務器認爲該請求該跨域請求能夠被接受,就爲響應設置Access-Control-Allow-Origin響應頭。具體操做爲:
    • 若是將其設置爲'*', 則代表來自全部源的請求均可以被接受;
    • 若是將其設置爲 對應請求的源的url,即請求的Origin字段值,則代表針對來自該源的請求能夠被容許。
CORS標準容許的常見的使用跨域請求的場景有:
  • XMLHttpRequest發起的跨域HTTP請求
  • Fetch發起的跨域HTTP請求
CORS請求的特色:

請求和響應都 默認不包含cookie信息

就是說跨源請求不提供憑據(包括cookie、HTTP認證及客戶端SSL證實等)。

若是跨源請求須要發送憑據,那麼解決辦法爲:

  • 客戶端若使用XMLHttpRequst,那麼須要設置 xhr.withCredentials爲true;若使用Fetch,那麼須要設置請求參數 credentials爲'include'。

  • 服務端設置響應頭 Access-Control-Allow-Credentials爲true

實踐:XMLHttpRequest發起跨源請求

發送Ajax請求的頁面地址爲:http://localhost:3000/a;
請求目標的地址爲:http://sub.localhost:3001/b, 該地址提供一段json數據。

頁面a客戶端代碼:

<div>我是a</div>
<button type="button" id="sendBtn">點我發送Ajax請求</button>
<script>
  const sendBtn = document.getElementById('sendBtn');
  sendBtn.addEventListener('click', function() {
    const xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
      if (xhr.readyState === 4) {
        if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
          console.log(xhr.responseText);
        }
      }
    }
    xhr.open('get', '{{reqDest}}', true);
    xhr.send(null);
  });

頁面b的服務端重點代碼(by koa):

router.get('/b', ctx => {
    ctx.set('Access-Control-Allow-Origin','http://localhost:3000');
    ctx.body = {
      'name':'bonne',
      'age':26
    }
  });

現象:

在a頁面點擊按鈕能夠看到控制檯輸出了'{"name":"bonne","age":26}',即成功地獲取到了跨源數據資源。

實踐: Fetch發起跨源請求

將a頁面請求代碼作以下修改:

<div>我是a</div>
<button type="button" id="sendBtn">點我發送Ajax請求</button>
<script>
  const sendBtn = document.getElementById('sendBtn');
  sendBtn.addEventListener('click', function() {
    fetch('{{reqDest}}', {
      mode: 'cors'
    }).then( res => {
      if (res.ok) {
        return res.json();
      } else {
        throw new Error('Network response was not ok');
      }
    }).then( resData => {
      console.log(resData);
    }).catch(err => {
      console.Error(err.message);
    })
  });
</script>

其餘不變。

現象:

在a頁面點擊按鈕依然能夠看到控制檯輸出了'{"name":"bonne","age":26}',即便用該Fetch方式也成功地獲取到了跨源數據資源。

2.圖像Ping

理論概述

咱們都知道,img標籤能夠從任何網頁中加載圖像,不管是否跨域。圖像Ping就是利用了img標籤的這一功能。

圖像Ping是與服務器進行簡單、單向的跨域通訊的一種方式。數據能夠經過src地址的查詢字符串發送到服務器。瀏覽器能夠經過監聽load和error事件,判斷服務器是什麼時候接收到響應。

實踐:圖像ping幫助客戶追蹤廣告曝光次數

最經常使用於跟蹤用戶點擊頁面的行爲或廣告曝光次數。

例如咱們網站就是使用圖像Ping給廣告客戶的服務器發送圖像Ping來是的廣告客戶獲取廣告曝光次數的數據:

var track = new Image();
track.onload = function() {
    window.parent.ga('send', 'event', 'iPhone web app launch ad', 'Sent', imp, {'nonInteraction':1});
};     
track.onerror = function() {
    window.parent.ga('send', 'event', 'iPhone web app launch ad', 'Fail', imp, {'nonInteraction':1});
};
track.src = imp;//imp爲廣告客戶的廣告曝光追蹤地址,實際上是一個白色小圓點圖片

3.JSONP

理論概述

script元素和img相似,都有能力不受限制地從其餘域加載資源。JSONP就是利用了script元素的這一功能。

JSONP是JSON with Padding(參數式JSON或填充式JSON),就是被包含在函數中調用的JSON。

JSONP由兩部分組成:數據回調函數。 數據就是傳入回調函數中的JSON數據。

JSONP的工做過程:爲script標籤的src指定一個跨域的URL(即JSONP服務的地址),並在URL中指定回調函數名稱。由於JSONP服務最終返回的是有效的JavaScript代碼,請求完成後會當即執行咱們在url參數中指定的函數,而且會把咱們須要的json數據做爲參數傳入。因此,jsonp是須要服務器端進行相應的配合的

示例:用koa和中間件koa-jsonp實現jsonP服務

發起jsonp請求的前端頁面相關代碼爲:

<script>
    function doSomething(jsonpData) {
      console.log(jsonpData);
    }
  </script>
  <script src="http://localhost:3000/?cb=doSomething"></script>

使用動態方式加載script亦可:

<script>
  function doSomething(jsonpData) {
    console.log(jsonpData);
  }
  var scriptElem = document.createElement('script');
  script.src = 'http://localhost:3000/?cb=doSomething';
  document.body.append(scriptElem);
<script>

cb就是url中指定回調函數名稱的參數,一般是callback,這個是須要在服務端設置的。

展現該前端頁面的服務代碼:

const path = require('path');
const Koa = require('koa');
const Router = require('koa-router');
const logger = require('koa-logger');
const views = require('koa-views');

const app = new Koa();
const router = new Router();

app.use(logger());

app.use(views(path.resolve(__dirname,'views')));

async function showText(ctx) {
  await ctx.render('test');
}
router.get('/', showText);

app.use(router.routes());

app.listen(3001, () => {
  console.log('Listening 3001');
});

jsonp服務的代碼:

const Koa = require('koa');
const Router = require('koa-router');
const jsonp = require('koa-jsonp');
const logger = require('koa-logger');
const app = new Koa();
const router = new Router();

app.use(logger());
app.use(jsonp({
  callbackName:'cb'//指定回調函數名稱的參數, defaults to 'callback'
}));

router.get('/', ctx => {
   ctx.body = {
    name:'Bonnie',
    age:26
  }
});

app.use(router.routes());

app.listen(3000, () => {
  console.log('Listening 3000');
});

頁面的端口爲3001,jsonp服務端口爲3000,造成跨域,可是頁面能夠完美獲取到jsonpData。

具體可參見我寫的用koa和中間件koa-jsonp實現jsonP服務的例子:

4.經過修改document.domain來跨域

理論概述

瀏覽器中不一樣域的框架之間是不能進行js的交互操做的。腳本試圖訪問的框架內容必須遵照同源策略。也就是說:

對於同源的框架來講:

不一樣域的框架之間能夠進行js交互。

  • 父頁面訪問子頁面:經過 contentWindow屬性,父頁面的腳本能夠訪問iframe元素所包含的子頁面的window對象。contentDocument屬性則引用了iframe中的文檔元素(等同於使用contentWindow.document),但IE8-不支持。

  • 子頁面訪問父頁面:經過訪問 window.parent,腳本能夠從框架中引用它的父框架的window。

對於非同源的框架來講:

腳本沒法訪問非同源的window對象的幾乎全部屬性。

該同源策略即適於父窗體訪問子窗體的window對象,也適用於子窗體訪問父窗體的window對象。

實踐:同源和跨源iframe交互操做的驗證

(1)驗證同源的框架間js互相訪問window對象毫無障礙:

頁面a的地址爲http://localhost:3000/a;
頁面b的地址爲http://localhost:3000/b;
頁面a中經過iframe嵌入頁面b。

頁面a代碼:

<div>我是a</div>
<script>
  window.name = 'parentFrame';
  window.globalvarA = 'aaa';
  function onLoad() {
    const otherFrame = document.getElementById('otherFrame');
    console.log('Parent console: Values of props from the child frame window:')
    console.log(`win:${otherFrame.contentWindow}`);
    console.log(`window.postMessage:${otherFrame.contentWindow.postMessage}`);

    console.log(`dom:${otherFrame.contentWindow.document}`);
    console.log(`name:${otherFrame.contentWindow.name}`);
    console.log(`globalvarB:${otherFrame.contentWindow.globalvarB}`);
  }
</script>
<iframe id="otherFrame" name="otherFrame" src='http://localhost:3000/b' onload="onLoad()"></iframe>

頁面b代碼:

<div>我是b</div>
  <script>
    window.globalvarB = 'bbb';
    console.log('Child console:Values of props from the parent frame window:')
    console.log(`win:${window.parent}`);
    console.log(`win.postMessage:${window.parent.postMessage}`);
    console.log(`dom:${window.parent.document}`);
    console.log(`name:${window.parent.name}`);
    console.log(`globalvarA:${window.parent.globalvarA}`);
  </script>

在http://localhost:3000/a的瀏覽器窗口能夠看到a頁面正確載入了b頁面的內容。

在http://localhost:3000/a的控制檯能夠看到,a和b框架都有輸出,b框架輸出在a框架以前:

b框架輸出結果:

Child console:Values of props from the parent frame window:
  win:[object Window]
  win.postMessage:function () { [native code] }
  dom:[object HTMLDocument]
  name:parentFrame
  globalvarA:aaa

a框架輸出結果:

win:[object Window]
  window.postMessage:function () { [native code] }
  dom:[object HTMLDocument]
  name:otherFrame
  globalvarB:bbb

可見同源的父子框架之間js互相訪問window對象確實很是順暢。

(2)驗證不一樣源的框架間js互相訪問window對象存在障礙:

頁面a的地址爲http://localhost:3000/a;
頁面b的地址爲http://sub.localhost:3001/b;
頁面a、b的嵌套關係不變,代碼也不變,除了a中iframe的src值修改成b的新地址http://sub.localhost:3001/b

頁面a代碼:

<div>我是a</div>
<script>
  window.name = 'parentFrame';
  window.globalvarA = 'aaa';
  function onLoad() {
    const otherFrame = document.getElementById('otherFrame');
    console.log('Parent console: Values of props from the child frame window:')
    try {
      console.log(`win:${otherFrame.contentWindow}`);
    } catch(err) {
      console.log('cannot get otherFrame.contentWindow');
    }

    try {
      console.log(`window.postMessage:${otherFrame.contentWindow.postMessage}`);
    } catch(err) {
      console.log('cannot get otherFrame.contentWindow.postMessage');
    }
    
    try {
      console.log(`dom:${otherFrame.contentWindow.document}`);
    } catch(err) {
      console.log('cannot get otherFrame.contentWindow.document');
    }

    try {
      console.log(`name:${otherFrame.contentWindow.name}`);
    } catch(err) {
      console.log('cannot get otherFrame.contentWindow.name');
    }

    try {
      console.log(`globalvarB:${otherFrame.contentWindow.globalvarB}`);
    } catch(err) {
      console.log('cannot get otherFrame.contentWindow.glovalvarB');
    }
  }
</script>
<iframe id="otherFrame" name="otherFrame" src='http://sub.localhost:3001/b' onload="onLoad()"></iframe>

頁面b代碼:

<div>我是b</div>
  <script>
    window.globalvarB = 'bbb';
    console.log('Child console:Values of props from the parent frame window:')
    try {
      console.log(`win:${window.parent}`);
    } catch(err) {
      console.log('cannot get window.parent');
    }

    try {
      console.log(`window.postMessage:${window.parent.postMessage}`);
    } catch(err) {
      console.log('cannot get window.parent.postMessage');
    }
    
    try {
      console.log(`dom:${window.parent.document}`);
    } catch(err) {
      console.log('cannot get window.parent.document');
    }

    try {
      console.log(`name:${window.parent.name}`);
    } catch(err) {
      console.log('cannot get window.parent.name');
    }

    try {
      console.log(`globalvarB:${window.parent.globalvarA}`);
    } catch(err) {
      console.log('cannot get window.parent.globalvarA');
    }
  </script>

Tips1: koa中間件koa-subdomain能夠完成對子域名的劃分。

Tips2:使用try{} catch() {}能夠在報錯的時候不影響後續代碼執行,因此這裏把每一個window相關屬性的獲取都放在try{} catch(){}語句中

現象與結論

(1) 在http://localhost:3000/a的瀏覽器窗口, 能夠看到a頁面依然正確載入了b頁面的內容。
由此能夠再次說明,使用iframe載入html頁面自己是能夠跨域的(若是沒有對 'X-Frame-Options'響應頭進行限制)。

(2) 在http://localhost:3000/a控制檯,能夠看到:

b框架輸出:

Child console:Values of props from the parent frame window:
cannot get window.parent
window.postMessage:function () { [native code] }
cannot get window.parent.document
cannot get window.parent.name
cannot get window.parent.globalvarA

框架a輸出:

Parent console: Values of props from the child frame window:
cannot get otherFrame.contentWindow
window.postMessage:function () { [native code] }
cannot get otherFrame.contentWindow.document
cannot get otherFrame.contentWindow.name
cannot get otherFrame.contentWindow.glovalvarB

可見:

  • 不一樣源的父子框架之間js 不能獲取到對方window對象及其屬性和方法
  • html5中的postMessage方法是一個例外,即使 獲取不到對方的window對象,也能獲取到對方的window.postMessage方法

參見我寫的同源和跨源iframe交互操做的示例

實踐:修改document.domain來實現跨域

將頁面的 document.domain的值設置爲當前域或當前域的父域。詳見 1、中 2. 源的更改。

注意: 使用此方法實現跨域僅針對不一樣框架間js的交互有效,對於Ajax仍是無效。

在上述a.html和b.html中的script標籤中的第一行加上:

document.domain = 'localhost'

理論上在本地測試應該能夠成功了。但事實上會報錯:

Failed to set document.domain to localhost. 'localhost' is a top-level domain

實際上是由於localhost這個域名很特殊,這樣設置不合法。

解決辦法是經過修改C:\Windows\System32\drivers\etc\hosts文件,加上一行:

127.0.0.1   test.com

將localhost換成合法域名。

修改域名後,a能夠經過http://test.com:3000/a訪問。
可是,koa-subdomain中間件會失效,訪問b仍是隻有http://sub.localhost:3001/b,http://sub.test.com:3001/b並不會生效。

若是想要看到跨域結果,仍是去非本地的服務器上測試吧~~~

5.使用window.name來進行跨域

原理概述

window有一個屬性name。window.name用於獲取或設置window的名稱。

window.name的特性:在一個window的生命週期內,該window載入的全部頁面都是共享一個window.name的。每一個被載入的頁面對該window.name都有讀寫的權限。若是新載入的頁面沒有對window.name進行重寫,那麼每個新載入的頁面均可以獲取到相同的window.name。

實踐:對上述準備知識的驗證

頁面a的地址爲http://localhost:3000/a;
頁面b的地址爲http://localhost:3000/b;
在頁面a中過5s將window.location改成頁面b的地址。

頁面a代碼:

<div>我是a</div>
<script>
  window.name = '頁面a';
  setTimeout(function() {
    window.location = 'http://localhost:3000/b';
  }, 5000);
</script>

頁面b代碼:

<div>我是b</div>
  <script>
     console.log(window.name);
  </script>

在http://localhost:3000/a能夠看到5s事後載入了頁面b,且控制檯輸出'頁面a'。即window.name並無由於載入新的頁面b而發生變化。

將a、b頁面的地址作以下修改:

頁面a的地址爲http://localhost:3000/a;
頁面b的地址爲http://sub.localhost:3001/b。

看到的結果和以前同樣。

因此,對於一個window, window.name不會由於window.location的改變而改變(除非新載入的頁面修改了這個值),不管新載入的頁面和以前的頁面是否存在跨域

NOTE: window.name的值只能是 字符串的形式,這個字符串的大小最大能容許 2M左右甚至更大的一個容量,具體取決於不一樣的瀏覽器,但通常是夠用了。

跨域原理

若是上述a.html和b.html是跨域的,咱們知道a在經過修改window.location的方式載入b後,b依然能夠獲取以前a的window.name,但是這樣a頁面本身的內容已經丟失了。那麼如今假如b的window.name裏存儲有咱們須要的數據,如何在a頁面中得到來自b的window.name的數據呢?

咱們能夠在a中經過一個隱藏的iframe引入頁面b。iframe自己是能夠跨域的,因此這一點不用擔憂。然而咱們須要的是獲取這個iframe的name,由於跨域因此沒法進行js交互,因此天然也沒法獲取b的window.name。可是咱們能夠利用上述window.name的特性,在a中用js將該iframe的src修改成一個同源的頁面地址(假設爲c),這樣就能夠獲取c的window.name了。又由於這個iframe以前是b,只是從新又載入了c,因此c的window.name就是以前b的window.name,因此a就能夠經過獲取c的window.name獲取b的window.name了。

實踐代碼以下:

實踐:使用window.name實現跨域

頁面a的地址爲http://localhost:3000/a;
頁面b的地址爲http://sub.localhost:3001/b;
頁面c的地址爲http://localhost:3000/c

頁面a代碼:

<div>我是a</div>
  <iframe style="display: none;" id="dataSource" src='http://sub.localhost:3001/b'></iframe>
  <script>
    const dataSourceIframe = document.getElementById('dataSource');
    dataSourceIframe.onload = function() {
      dataSourceIframe.onload = function() {
        const data = dataSourceIframe.contentWindow.name;
        console.log(data);
      }
      dataSourceIframe.src = '/c';
    }
  </script>

頁面b代碼:

<div>我是b</div>
  <script>
     window.name = JSON.stringify({
       name:'Bonnie',
       age:26
     })
  </script>

在http://localhost:3000/a的控制檯打印出了數據 {"name":"Bonnie","age":26},實現了跨域。

tips: 每修改一次iframe的src都會觸發一次iframe的onload事件。

具體代碼參見我寫的經過window.name實現跨域的例子。

5.使用HTML5中新引進的window.postMessage方法來跨域傳送數據

跨域原理

window.postMessage() 是html5引進的新方法,可使用它來向其它的window對象發送消息,不管這個window對象是屬於同源或不一樣源

一般,對於兩個不一樣頁面的腳本,只有當執行它們的頁面位於具備相同的協議、端口號、主機時,這兩個腳本才能相互通訊。window.postMessage() 方法提供了一種受控機制來規避此限制,只要正確的使用,這種方法就能夠安全地實現跨源通訊。

postMessage

發送數據的頁面調用postMessage方法:

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

params:

  • otherWindow: 其餘窗口的一個引用,好比iframe的contentWindow屬性、執行window.open返回的窗口對象、或者是命名過或數值索引的window.frames。

  • message:將要發送到otherWindow的數據。能夠是string或object。

  • targetOrigin: 指定哪些window能接收到消息事件,即otherWindow的地址,其值能夠是字符串"*"(表示無限制)或者一個URI。 **若是你明確的知道消息應該發送到哪一個窗口,那麼請始終提供一個有確切值的targetOrigin,而不是*。不提供確切的目標將致使數據泄露到任何對數據感興趣的惡意站點。**

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

window.postMessage() 方法被調用時,會在全部頁面腳本執行完畢以後向目標window派發一個 MessageEvent 消息,即觸發目標window的message事件。

message事件

接收數據的頁面監聽message事件。

message事件有的event對象有一些特殊的屬性:

  • event.data: 從發送數據的 window 中傳遞過來的對象。

  • event.origin: 調用 postMessage 時消息發送方窗口的 origin . 這個字符串由 協議、「://「、域名、「 : 端口號」拼接而成。例如 「https://example.org (隱含端口 443)」、「http://example.net (隱含端口 80)」、「http://example.com:8080」。請注意,這個origin不能保證是該窗口的當前或將來origin,由於postMessage被調用後可能被導航到不一樣的位置。

  • event.source:對發送數據的window對象的引用。使用它能夠在具備不一樣origin的兩個窗口之間創建雙向通訊。

實踐:使用window.postMessage實現跨域

頁面a的地址爲http://localhost:3000/a;
頁面b的地址爲http://sub.localhost:3001/b;

頁面a代碼:

<div>我是a</div>
<script>
  const data = {
    name:'Bonnie',
    age:26
  }
  function onLoad() {
    const otherFrame = document.getElementById('otherFrame');
    otherFrame.contentWindow.postMessage(data,'http://sub.localhost:3001');
  }
</script>
<iframe id="otherFrame" name="otherFrame" src='http://sub.localhost:3001/b' onload="onLoad()"></iframe>

頁面b代碼:

<div>我是b</div>
  <div id="messageResult"></div>
  <script>
     window.onmessage = function(e) {
       const messageResult = document.getElementById('messageResult');
       messageResult.innerHTML = JSON.stringify(e.data);
       console.log(e.data);
       console.log(e.origin);
       console.log(e.source);
     }
  </script>

在http://localhost:3000/a的瀏覽器窗口能夠看到加載的頁面b中的messageresult部分輸出了正確的數據。

在http://localhost:3000/a的控制檯,能夠看到b框架的輸出:

{name: "Bonnie", age: 26}
http://localhost:3000 
global {window: global, self: global, location: Location, closed: false, frames: global, …}

即 e.origin是 http://localhost:3000 , e.source是global {window: global, self: global, location: Location, closed: false, frames: global, …}

具體可參見我寫的使用postMessage實現跨域的例子。

6. Web Sockets

理論概述

Web Sockets是一種基於ws協議的技術。使用它能夠在客戶端和服務器之間創建一個單獨的、持久的、全雙工的、雙向的通訊。

在JavaScript中建立了Web Socket以後,會有一個HTTP請求發送到瀏覽器以發起鏈接。在取得服務器響應後,創建的鏈接會使用HTTP升級從HTTP協議換爲Web Socket協議。 也就是說,標準的HTTP服務器沒法實現Web Sockets,只有支持Web Socket協議的服務器才能實現

因爲Web Sockets使用了 自定義協議,因此其URL的模式也有一些不一樣。未加密的鏈接是 ws://,而非http:// ; 加密的鏈接是 wss://, 而非https://。

Web Sockets的特色
  • 鏈接持久
  • 全雙工通訊
  • 通訊數據開銷小:使用自定義協議而非HTTP協議,使得客戶端和服務器之間能夠發送很是少的數據,而沒必要像HTTP那樣是字節級的開銷。
  • 不受同源策略限制:能夠經過它打開到任何站點的鏈接

而HTTP的特色是:

  • 單向通訊
  • 無鏈接(響應頭 Connection: keep-alive 可使鏈接持續有效)
  • 無狀態
示例代碼
var socket=new WebSocket("ws://www.example.com/server.php");
socket.send("Hello world!");
socket.onmessage=function(event){
    var data=event.data;
    //處理數據,能夠用這些數據更新頁面的某部分
}

參考文檔與博客

https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/X-Frame-Options

https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/iframe

https://www.jianshu.com/p/b587dd1b7086
https://mp.weixin.qq.com/s/asmzA8a1HuYQxyx8K0q-9g?

https://www.techwalla.com/articles/how-to-change-your-local-host-name

https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage

https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket

《JavaScript高級程序設計》21.5

相關文章
相關標籤/搜索