經常使用跨域方法總結

經常使用跨域方法總結

爲何要跨域?

由於瀏覽器的一種安全機制——同源策略的限制,致使不能直接獲取不一樣源的資源,因此要跨域。javascript

同源策略限制了從同一個源加載的文檔或腳本如何與來自另外一個源的資源進行交互。這是一個用於隔離潛在惡意文件的重要安全機制。

限制之一是不能經過ajax的方法去請求不一樣源中的文檔。
第二個限制是瀏覽器中不一樣域的框架(iframe)之間是不能進行js的交互操做的。html

那麼什麼才叫「同源」呢?前端

  • 協議相同
  • 域名相同
  • 端口號相同

clipboard.png

圖來自MDN,參見最後Reference.java

下面介紹經常使用的幾種跨域方法。node

jsonp 跨域

原理:git

  • 利用了<script>標籤不受瀏覽器同源限制的影響,
  • 由於借用<script>發起的請求,因此會把請求到的內容看成js代碼來解析執行。

缺點:只能發送get請求
下面是一個簡單的例子github

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script>
function callback (msg) {
  console.log('json:', msg)
}
</script>
<script src="http://localhost:8080?cb=callback ">
</script>
</body>
</html>

如上,定義了一個全局的callback 函數,而後利用<script>來發起get請求,注意把函數名稱callback 一同發送給服務端,爲何呢,接着往下看服務端代碼ajax

//nodeJs
var http = require('http');
var url = require('url');

/**
 * 客戶端請求的函數
 * @param req
 * @param res
 */
function onRequest (req, res) {
  console.log("獲取到的請求參數的路徑:" + req.url);

  //獲得鍵值對
  var arg = url.parse(req.url, true).query;
  //打印鍵值對中的值
  console.log(arg.cb);

  res.writeHead(200);
  res.write(arg.cb + '("我今天要用jsonp來跨域獲取數據")');
  res.end();
}

http.createServer(onRequest).listen(8080);

服務端簡單解析cb參數(咱們傳的函數名稱),而後返回一段字符串callback("我今天要用jsonp來跨域獲取數據")
瀏覽器會收到響應以下:chrome

clipboard.png

clipboard.png

發現什麼了嗎?響應內容callback("我今天要用jsonp來跨域獲取數據")會被看成js代碼來執行,正好調用了咱們以前定義的callback函數。
由此,咱們成功的利用jsonp經過跨域獲取到了想要的數據。json

document.domain跨域(子域名不一樣的框架之間)

開頭咱們說到不一樣源的框架之間是不能進行js交互操做的,實際上是能夠獲取window對象,但不能獲取window的屬性。
原理:

  • document.domain的值是能夠設置的,但僅限於設置爲自身或是更高一級的父級域名(主域名相同)。

那麼主域名相同,子域名不一樣的框架之間跨域獲取數據的思路就來了,咱們把它們的document.domain都設置成主域名不就完事了?
好比有一個頁面a.google.com/1.html
這裏參考了其餘文章的代碼。出處見本文最下方

<iframe id = "iframe" src="http://b.google.com/2.html" onload = "test()"></iframe>
<script type="text/javascript">
    document.domain = 'google.com';//設置成主域
    function test(){
        alert(document.getElementById('iframe').contentWindow);//contentWindow 可取得子窗口的 window 對象
    }
</script>

另外一個頁面b.google.com/2.html只需設置主域名爲google.com,兩個框架間就能夠進行交互啦。

<script type="text/javascript">
document.domain = 'google.com';//設置成主域
</script>

location.hash跨域(不一樣源的框架之間)

原理:

  • hash字段(常常用於錨點定位)不屬於http協議的部分,請求不會攜帶hash信息,因此改變不會從新請求資源(可是會產生新的瀏覽器歷史記錄,許多前端路由也是借用這個原理實現的)
  • 父窗口能夠對iframe進行URL讀寫,iframe也能夠讀寫父窗口的URL(不一樣源的話,IE、Chrome不容許修改parent.location.hash的值,但咱們仍有處理方式)

思路:

  • 若是是父窗口向子窗口跨域傳遞數據,直接修改子窗口url的hash就能夠了,比較簡單這裏就不貼代碼了。
  • 子窗口向父親窗口跨域傳遞數據就須要多加一個代理窗口(由於IE,Chrome),這個代理窗口和父親窗口同源就能夠了,在這個代理窗口改變父親窗口的hash,父親窗口監聽hashchange就能夠了。

代碼以下:
父親窗口頁面
http://localhost:63342/test-field/cross-origin/local-test/hash-parent.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<iframe src="http://blank121.github.io/test-field/cross-origin/hash.html">
</iframe>
<script>
console.log('old hash:', location.hash)
window.addEventListener('hashchange', function (ev) {
  console.log('new hash:', location.hash)
})
</script>
</body>
</html>

子窗口頁面
http://blank121.github.io/tes...

try{
  parent.location.hash='今天我要用hash跨域'
  //chrome ie 直接修改parent.location.hash報錯
}catch (e){
  var iframe = document.createElement('iframe')
  iframe.src = 'https://localhost:63342/test-field/cross-origin/local-test/hash-proxy.html'+'#今天我要用hash跨域'
  document.body.appendChild(iframe);
}

代理窗口頁面
http://localhost:63342/test-field/cross-origin/local-test/hash-proxy.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script>
console.log('proxy',location.hash)
parent.parent.location.hash = location.hash
</script>
</body>
</html>

如上,在子窗口頁面內修改了代理窗口的hash值,代理窗口又修改了父親窗口的hash值,父親窗口監聽hashchange就能夠獲取到不一樣源的子窗口傳來的數據啦。
控制檯結果以下:

clipboard.png

window.name跨域(不一樣源框架間)

原理:

window對象有個name屬性,該屬性有個特徵:即在一個窗口(window)的生命週期內,窗口載入的全部的頁面都是共享一個window.name的,每一個頁面對window.name都有讀寫的權限,window.name是持久存在一個窗口載入過的全部頁面中的,並不會因新頁面的載入而進行重置。

利用一個窗口的生命週期內,載入不一樣頁面window.name不變的特性
貼代碼前首先去作個有趣的實驗
在必應的首頁設置一下window.name

clipboard.png

而後輸入location.href = 'http://www.baidu.com',跳轉到百度後再看一下name

clipboard.png

666,window.name果真沒騙我,沒有變化。

接下來先貼代碼
父親窗口:
http://localhost:63342/test-field/cross-origin/local-test/name-parent.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<iframe id="frame" src="https://blank121.github.io/test-field/cross-origin/name.html"></iframe>
<script>
var iframe = document.getElementById('frame')
var loaded = false
iframe.onload = function () {
  if (!loaded) {
    loaded = true
    iframe.src = 'http://localhost:63342/test-field/cross-origin/local-test/name-proxy.html'
  } else {
    console.log(iframe.contentWindow.name)
  }
}
</script>
</body>
</html>

子窗口:
https://blank121.github.io/te...

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>window.name跨域</title>
</head>
<body>
<script>
window.name='今天,我要用window.name來實現跨域'
</script>
</body>
</html>

代理窗口(啥也沒作。主要是要和父親窗口同源來傳遞name)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
</body>
</html>

代碼如上,主要思路就是利用window.name不變性,子窗口設置了name以後,來加載一個和父親窗口同源的代理窗口,以此來獲取name(注意子窗口和代理窗口是同一個iframe,加載不一樣頁面而已,因此window.name不變)

clipboard.png

postMessage跨域

首先了解一下postMessage
otherWindow.postMessage(message, targetOrigin);
otherWindow:指目標窗口,也就是給哪一個window發消息,是 window.frames 屬性的成員或者由 window.open 方法建立的窗口
message: 是要發送的消息,類型爲 String、Object (IE八、9 不支持)
targetOrigin: 是限定消息接收範圍,不限制請使用 *

postMessage是HTML5新特性,跨域簡直太方便了,就是兼容性要注意一下。

發送方頁面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<iframe id="frame" src="https://blank121.github.io/test-field/cross-origin/receive.html">
</iframe>
<script>
var iframe = document.getElementById('frame')
iframe.onload = function () {
    iframe.contentWindow.postMessage("我今天就是要用postMessage跨域","https://blank121.github.io")
}
</script>
</body>
</html>

接收方頁面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script>
function receiveMessage(event)
{
  var p = document.createElement('p')
  p.innerText = event.data
  document.body.appendChild(p)
}
window.addEventListener("message", receiveMessage, false);
</script>
</body>
</html>

接收方監聽一下message事件,就能夠接收到信息啦。

clipboard.png

WebSocket跨域

WebSocket 是一種在客戶端與服務器之間保持TCP長鏈接的網絡協議,這樣它們就能夠隨時進行信息交換(雙工通信)。
雖然任何客戶端或服務器上的應用均可以使用WebSocket,但原則上仍是指瀏覽器與服務器之間使用。經過WebSocket,服務器能夠直接向客戶端發送數據,而無須客戶端週期性的請求服務器,以動態更新數據內容。

WebSocket 很是強大,筆者在這方面也是小白級別的,之後有時間會詳細研究學習。

跨域代碼以下
頁面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
</body>
</html>
<script>
// Create WebSocket connection.
const socket = new WebSocket('ws://127.0.0.1:8080');

// Connection opened
socket.addEventListener('open', function (event) {
  socket.send('Hello Server!');
});

// Listen for messages
socket.addEventListener('message', function (event) {
  console.log('Message from server', event.data);
});
</script>

服務端nodeJs代碼:

var WebSocketServer = require('ws').Server;
var wss = new WebSocketServer({ port: 8080 });

wss.on('connection', function connection(ws) {
  ws.on('message', function incoming(message) {
    console.log('received: %s', message);
     ws.send("hello"+message);
  });
});

結果如圖:
瀏覽器端:

clipboard.png

服務端:

clipboard.png

完美實現了跨域。

CORS(cross origin resource sharing 最經常使用)

老生常談的CORS,優秀的文章已經很是多了,你們能夠搜一下,很是重要,有機會我會專門寫一篇文章來學習總結,在此就再也不詳述了

最後

不得不說,這些方法仍是比較巧妙的,在此寫下一篇文章來總結一下,感受本身面對跨域絲絕不慌啦。

Reference

前端跨域整理
正確面對跨域,別慌
瀏覽器的同源策略

相關文章
相關標籤/搜索