文章列出解決方案以及對應的demo, 拒絕說概念,不在稀裏糊塗。
同一個 origin 下,父頁面能夠經過 iframe.contentWindow 直接訪問 iframe 的全局變量、DOM 樹等,iframe 能夠也經過 parent/top 對父頁面作一樣的事情。 javascript
domain.htmlhtml
<body> <iframe id="ifr" src="http://b.tblog.com:3004/domain2.html"></iframe> <script> document.domain = 'tblog.com'; function aa(str) { console.log(str); } window.onload = function () { document.querySelector('#ifr').contentWindow.bb('aaa'); } </script>
domain2.htmlhtml5
<body> 2222222222 <script> document.domain = 'tblog.com'; function bb(str) { console.log(str); } parent.aa('bbb'); </script> </body>
完整demojava
html5新增API, 支持IE8+。node
otherWindow.postMessage(message, targetOrigin, [transfer]);
傳遞過來的message的屬性有:git
下面index.html和index2.html通訊
index.htmlgithub
<body> <input type="text" placeholder="http://b.tblog.com:3004/index2.html"> <iframe src="http://192.168.101.5: 3004/index2.html" frameborder="0"></iframe> <script> const input = document.querySelector('input'); input.addEventListener('input', function () { window.frames[0].postMessage(this.value, '*'); // window.frames[0].postMessage(this.value, 'http://192.168.101.5'); // window.frames[0].postMessage(this.value, 'http://192.168.101.5:3004'); }); // 接收消息 window.addEventListener('message', function (e) { input.value = e.data; console.log('父窗口', e.data); console.log('父窗口', e.source); console.log('父窗口', e.origin); }); </script> </body>
index2.htmljson
<body> 子窗口 <input id="input" type="text" placeholder="http://a.tblog.com:3004/index.html"> <script> const input = document.querySelector('#input'); input.addEventListener('input', function () { window.parent.postMessage(this.value, '*'); }); // 接收消息 window.addEventListener('message', function (e) { input.value = e.data; console.log('子窗口', e.data); console.log('子窗口', e.source); console.log('子窗口', e.origin); }); </script> </body>
完整democanvas
原理是利用location.hash來進行傳值。改變hash並不會致使頁面刷新,因此能夠利用hash值來進行數據傳遞,固然數據容量是有限的。
例如:假設a.tblog.com:3004 和 192.168.101.5:3004/index2.html
通訊
原理:a.tblog.com:3004中index.html以iframe將192.168.101.5:3004/index2.html
頁面引入,在192.168.101.5:3004/index2.html
中插入新的iframe, 此iframe引入的頁面和a.tblog.com:3004同源,就可將192.168.101.5:3004/index2.html
的hash數據傳入a.tblog.com:3004頁面的hash值中。parent.parent.location.hash = self.location.hash.substring(1);
a.tblog.com:3004/index.htmlsegmentfault
<script> var ifr = document.createElement('iframe'); ifr.style.display = 'none'; ifr.src = 'http://192.168.101.5:3004/ index2.html#paramdo'; document.body.appendChild(ifr); function checkHash() { try { var data = location.hash ? location.hash.substring(1) : ''; if (console.log) { console.log('Now the data is ' + data); } } catch (e) { }; } setInterval(checkHash, 2000); </script>
192.168.101.5:3004/ index2.html
<body> <script> //模擬一個簡單的參數處理操做 switch (location.hash) { case '#paramdo': callBack(); break; case '#paramset': //do something…… break; } function callBack() { try { parent.location.hash = 'somedata'; } catch (e) { var ifrproxy = document.createElement('iframe'); ifrproxy.style.display = 'none'; ifrproxy.src = 'http://a.tblog.com:3004/index3.html#somedata'; // 注意該文件在"a.com"域下 document.body.appendChild(ifrproxy); } } </script> </body>
a.tblog.com:3004/index3.html
<body> <script> //由於parent.parent和自身屬於同一個域,因此能夠改變其location.hash的值 parent.parent.location.hash = self.location.hash.substring(1); </script> </body>
完整demo
window.name 獲取/設置窗口的名稱。
窗口的名字主要用於爲超連接和表單設置目標(targets)。窗口不須要有名稱。
window.name屬性可設置或者返回存放窗口名稱的一個字符串, name值在不一樣頁面或者不一樣域下加載後依舊存在,沒有修改就不會發生變化,而且能夠存儲很是長的name(2MB)。
場景1 - 同源
a.html
<body> <script type="text/javascript"> const iframe = document.createElement('iframe'); iframe.src = 'http://a.tblog.com:3004/b.html'; iframe.style.display = 'none'; document.body.appendChild(iframe); iframe.onload = function () { console.log(iframe.contentWindow.name) }; </script> </body>
b.html
<body> <script> window.name = '子頁面的數據'; </script> </body>
場景2 - 不一樣源
利用iframe中window.name在不一樣頁面或者不一樣域下加載後依舊存在的特性。a.tblog.com:3004/a.html
中經過iframe添加192.168.0.103:3004/b.html
(數據頁面, 指定window.name 的值),監聽iframe的load, 改變iframe的src與a.tblog.com:3004/a.html
同源代理頁面a.tblog.com:3004/c.html
(空頁面)。
a.tblog.com:3004/a.html
const iframe = document.createElement('iframe'); iframe.style.display = 'none'; let state = 0; iframe.onload = function () { console.log('iframe.onload', state, iframe.contentWindow); if (state === 1) { const data = JSON.parse(iframe.contentWindow.name); console.log(data, state); iframe.contentWindow.document.write(''); iframe.contentWindow.close(); document.body.removeChild(iframe); } else if (state === 0) { state = 1; console.log('數據', window.name) iframe.contentWindow.location = 'http://a.tblog.com:3004/c.html'; } }; iframe.src = 'http://192.168.0.103:3004/b.html'; document.body.appendChild(iframe);
完整demo
jsonp原理:
因爲使用script標籤的src屬性,所以只支持get方法
客戶端代碼
<body> <button class="get">get data</button> <script> const btn = document.querySelector('.get'); btn.addEventListener('click', function () { const script = document.createElement('script'); script.setAttribute('src', 'http://127.0.0.1:8080/getNews?callback=getData'); document.head.appendChild(script); document.head.removeChild(script); }) function getData(news) { console.log(news) } </script> </body>
服務端代碼
const http = require('http'); const fs = require('fs'); const path = require('path'); const url = require('url'); http.createServer(function(req, res){ const pathObj = url.parse(req.url, true); switch(pathObj.pathname){ case '/getNews': const news = [{id: 678}]; res.setHeader('Content-type', 'text/json; charset=utf-8'); if(pathObj.query.callback){ res.end(pathObj.query.callback + '(' + JSON.stringify(news) + ')'); }else { res.end(JSON.stringify(news)); } break; default: res.writeHead(404, 'not found'); } }).listen(8080);
完整demo
原理
跨域資源共享(CORS) 是一種機制,它使用額外的 HTTP 頭來告訴瀏覽器 讓運行在一個 origin (domain) 上的Web應用被准許訪問來自不一樣源服務器上的指定的資源。跨域資源共享( CORS )機制容許 Web 應用服務器進行跨域訪問控制,從而使跨域數據傳輸得以安全進行。
什麼狀況下須要CORS
功能概述
跨域資源共享標準新增了一組 HTTP 首部字段,容許服務器聲明哪些源站經過瀏覽器有權限訪問哪些資源。容許服務器聲明哪些源站經過瀏覽器有權限訪問哪些資源。對於get之外的請求,瀏覽器必須首先使用 OPTIONS 方法發起一個預檢請求(preflight request),從而獲知服務端是否容許該跨域請求。服務器確認容許以後,才發起實際的 HTTP 請求。 真個過程瀏覽器自動完成,服務器會添加一些附加的頭信息, 所以,實現CORS通訊的關鍵是服務器。只要服務器實現了CORS接口,就能夠跨源通訊。
簡單請求
某些請求不會觸發 CORS 預檢請求。本文稱這樣的請求爲「簡單請求」,請注意,該術語並不屬於 Fetch (其中定義了 CORS)規範。只要同時知足如下兩大條件,就屬於簡單請求:
(1) 請求方法是如下三種方法之一: HEAD GET POST (2)HTTP的頭信息不超出如下幾種字段: Accept Accept-Language Content-Language Last-Event-ID Content-Type:只限於三個值application/x-www-form-urlencoded、multipart/form-data、text/plain
請求響應結果多出的字段:
Access-Control-Allow-Origin: http://api.bob.com Access-Control-Allow-Credentials: true Access-Control-Expose-Headers: FooBar Content-Type: text/html; charset=utf-8
Access-Control-Allow-Origin: <origin> | *
; 該字段是必須的。它的值要麼是請求時Origin字段的值,要麼是一個*,表示接受任意域名的請求, 有次響應頭字段就能夠跨域
Access-Control-Allow-Credentials: true
; 當瀏覽器的credentials設置爲true時, 此響應頭表示是否容許瀏覽器讀取response的內容,返回true則能夠,其餘值均不能夠,Credentials能夠是 cookies, authorization headers 或 TLS client certificates。
Access-Control-Allow-Credentials 頭 工做中與XMLHttpRequest.withCredentials 或Fetch API中的Request() 構造器中的credentials 選項結合使用。Credentials必須在先後端都被配置(即the Access-Control-Allow-Credentials header 和 XHR 或Fetch request中都要配置)才能使帶credentials的CORS請求成功。 若是withCredentials 爲false,服務器贊成發送Cookie,瀏覽器也不會發送,或者,服務器要求設置Cookie,瀏覽器也不會處理。
須要注意的是,若是要發送Cookie,Access-Control-Allow-Origin就不能設爲星號,必須指定明確的、與請求網頁一致的域名。同時,Cookie依然遵循同源政策,只有用服務器域名設置的Cookie纔會上傳,其餘域名的Cookie並不會上傳,且(跨源)原網頁代碼中的document.cookie也沒法讀取服務器域名下的Cookie。
// 容許credentials: Access-Control-Allow-Credentials: true // 使用帶credentials的 XHR : var xhr = new XMLHttpRequest(); xhr.open('GET', 'http://example.com/', true); xhr.withCredentials = true; xhr.send(null); // 使用帶credentials的 Fetch : fetch(url, { credentials: 'include' })
在跨域訪問時,XMLHttpRequest對象的getResponseHeader()方法只能拿到一些最基本的響應頭,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma, 若是想拿到其餘字段,就必須在Access-Control-Expose-Headers裏面指定。上面的例子指定,getResponseHeader('FooBar')能夠返回FooBar字段的值。
代碼以下:
<!-- 服務端 --> http.createServer(function(req, res){ const pathObj = url.parse(req.url, true); switch(pathObj.pathname){ case '/user': const news = [{id: 678}]; res.setHeader('Content-type', 'text/json; charset=utf-8'); res.setHeader('Access-Control-Allow-Origin', req.headers.origin); // res.setHeader('Access-Control-Allow-Origin', '*'); // 須要cookie等憑證是必須 res.setHeader('Access-Control-Allow-Credentials', true); res.end(JSON.stringify(news)); break; default: res.writeHead(404, 'not found'); } }).listen(8080, (err) => { if (!err) { console.log('8080已啓動'); } }); <!-- 客戶端 --> <body> <script> const xhr = new XMLHttpRequest(); xhr.open('GET', 'http://localhost:8080/user', true); // 須要cookie等憑證是必須 xhr.withCredentials = true; xhr.onreadystatechange = (e) => { console.log('onreadystatechange', e) } xhr.send(); </script> </body>
完整demo
非簡單請求
非簡單請求的CORS請求,會在正式通訊以前,增長一次HTTP查詢請求,稱爲"預檢"請求(preflight)。
以獲知服務器是否容許該實際請求。"預檢請求「的使用,能夠避免跨域請求對服務器的用戶數據產生未預期的影響。
當請求知足下述任一條件時,即應首先發送預檢請求:
使用了下面任一 HTTP 方法:
人爲設置了對cors安全首部字段集合外的其餘首部字段, 該集合爲:
Content-Type的值不屬於下列之一:
以下是一個須要執行預檢請求的 HTTP 請求:
<body> <script> const invocation = new XMLHttpRequest(); const url = 'http://localhost:8080/user'; const body = JSON.stringify({ name: 'toringo' }); function callOtherDomain() { if (invocation) { invocation.open('POST', url, true); invocation.setRequestHeader('X-PINGOTHER', 'pingpong'); invocation.setRequestHeader('Content-Type', 'application/json'); invocation.onreadystatechange = (e) => { console.log('onreadystatechange', e) }; invocation.send(body); } } callOtherDomain(); </script> </body> <!-- 服務端 --> http.createServer(function(req, res){ const pathObj = url.parse(req.url, true); switch(pathObj.pathname){ case '/user': const news = {id: 678}; res.setHeader('Content-type', 'text/json; charset=utf-8'); res.setHeader('Access-Control-Allow-Origin', req.headers.origin); // res.setHeader('Access-Control-Allow-Origin', '*'); // 須要cookie等憑證是必須 res.setHeader('Access-Control-Allow-Credentials', true); res.end(JSON.stringify(news)); break; default: res.writeHead(404, 'not found'); } }).listen(8080, (err) => { if (!err) { console.log('8080已啓動'); } });
瀏覽器請求結果:
cors2.html:1 Access to XMLHttpRequest at 'http://localhost:8080/user' from origin 'http://127.0.0.1:3004' has been blocked by CORS policy: Request header field x-pingother is not allowed by Access-Control-Allow-Headers in preflight response.
如圖所示發起了預檢請求,請求頭部多了兩個字段:
Access-Control-Request-Method: POST; // 該字段是必須的,用來列出瀏覽器的CORS請求會用到哪些HTTP方法. Access-Control-Request-Headers: Content-Type, X-PINGOTHER; 告知服務器,實際請求將攜帶兩個自定義請求首部字段:X-PINGOTHER 與 Content-Type。服務器據此決定,該實際請求是否被容許。
上例須要成功響應數據,服務端須要贊成:
http.createServer(function(req, res){ const pathObj = url.parse(req.url, true); switch(pathObj.pathname){ case '/user': const news = {id: 678}; res.setHeader('Content-type', 'text/json; charset=utf-8'); res.setHeader('Access-Control-Allow-Origin', req.headers.origin); // 新增的 res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'X-PINGOTHER, Content-Type'); res.setHeader('Access-Control-Max-Age', 86400); res.end(JSON.stringify(news)); break; default: res.writeHead(404, 'not found'); } }).listen(8080, (err) => { if (!err) { console.log('8080已啓動'); } });
服務段新增的字段:
Access-Control-Allow-Origin: req.headers.origin Access-Control-Allow-Methods: POST, GET, OPTIONS // 代表服務器容許客戶端使用 POST, GET 和 OPTIONS 方法發起請求。該字段與 HTTP/1.1 Allow: response header 相似,但僅限於在須要訪問控制的場景中使用。這是爲了不屢次"預檢"請求。 Access-Control-Allow-Headers: X-PINGOTHER, Content-Type // 若是瀏覽器請求包括Access-Control-Request-Headers字段,則Access-Control-Allow-Headers字段是必需的。它也是一個逗號分隔的字符串,代表服務器支持的全部頭信息字段,不限於瀏覽器在"預檢"中請求的字段。 Access-Control-Max-Age: 86400 // 代表該響應的有效時間爲 86400 秒,也就是 24 小時。在有效時間內,瀏覽器無須爲同一請求再次發起預檢請求。請注意,瀏覽器自身維護了一個最大有效時間,若是該首部字段的值超過了最大有效時間,將不會生效。
node中間件實現跨域代理,是經過一個代理服務器,實現數據的轉發,也能夠經過設置cookieDomainRewrite參數修改響應頭中cookie中域名,實現當前域的cookie寫入,方便接口登錄認證。
原理:服務器之間數據請求不存在跨域限制(同源策略是瀏覽器行爲), 因此先將請求代理到代理服務器, 代理服務器在內部請求真實的服務器獲得結果後end鏈接。
<!-- 服務 --> http.createServer(function(req, res){ const pathObj = url.parse(req.url, true); console.log('server', pathObj.pathname) switch(pathObj.pathname){ case '/user': const news = {id: 678}; res.end(JSON.stringify(news)); break; default: res.setHeader('Content-type', 'text/json; charset=utf-8'); res.end('未知錯誤'); } }).listen(4000, (err) => { if (!err) { console.log('4000已啓動'); } }); <!-- 代理 --> http.createServer(function(req, res){ const pathObj = url.parse(req.url, true); switch(pathObj.pathname){ case '/user': res.setHeader('Content-type', 'text/json; charset=utf-8'); res.writeHead(200, { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', 'Access-Control-Allow-Headers': 'X-PINGOTHER, Content-Type', }); console.log('proxy', req.method, pathObj.pathname); // 請求真實服務器 const proxyRequest = http.request({ host: '127.0.0.1', port: 4000, url: '/', path: pathObj.pathname, method: req.method, headers: req.headers }, (proxyRes) => { let body = ''; proxyRes.on('data', (chunk) => { body += chunk; }); proxyRes.on('end', () => { console.log('響應的數據 ' + body ); res.end(body); }) }).end(); break; default: res.writeHead(404, 'not found'); res.end(body); break; } }).listen(8080, (err) => { if (!err) { console.log('8080已啓動'); } }); <!-- 客戶端 index.html --> <body> <script> const invocation = new XMLHttpRequest(); const url = 'http://localhost:8080/user'; const body = JSON.stringify({ name: 'toringo' }); function callOtherDomain() { if (invocation) { invocation.open('POST', url, true); // invocation.setRequestHeader('X-PINGOTHER', 'pingpong'); invocation.setRequestHeader('Content-Type', 'application/json'); invocation.onreadystatechange = (e) => { console.log('onreadystatechange', e) }; invocation.send(body); } } callOtherDomain(); </script> </body>
注意:
服務器和瀏覽器數據交互也須要遵循同源策略
-- 持續更新 --
Tips:
代碼地址。~ github
WeChat
參考文章
https://developer.mozilla.org...
http://vinc.top/2017/02/09/%E...
http://www.ruanyifeng.com/blo...
https://segmentfault.com/a/11...
https://developer.mozilla.org...
https://developer.mozilla.org...
http://www.ruanyifeng.com/blo...