爲何會有跨域?javascript
咱們得先了解下 ==同源策略(SOP, Same Origin Policy)==。css
瀏覽器出於安全方面的考慮,只能訪問與包含它的頁面位於同一個域中的資源,該策略爲通訊設置了「相同的協議、相同的域、相同的端口」這一限制。試圖訪問上述限制以外的資源,都會引起安全錯誤。這種安全策略能夠預防某些惡意行爲。html
簡而言之,前端
Same Protocol && Same Hostname && Same Porthtml5
什麼是跨域?java
==跨域就是採起技術方案突破同源策略的限制,實現不一樣域之間交互(請求響應)。==node
那麼如何實現跨域呢?
有如下幾種方法。git
==方法一==ajax
CORS (Cross-Origin Resource Sharing,跨域源資源共享),是一種ajax跨域請求資源的方式,支持現代瀏覽器,IE支持10以上,經過XMLHttpRequest實現Ajax通訊的一個主要限制就是同源策略。
CORS是W3C的一個工做草案,定義了在必須訪問跨境資源時,瀏覽器和服務器該如何溝通。CORS的基本思想,就時使用自定義的HTTP頭部讓瀏覽器和服務器進行溝通,從而決定請求或者響應應該成功仍是失敗。
實現思路:使用XMLHttpRequest發送請求時,瀏覽器會給該請求自動添加一個請求頭:Origin。服務器通過一系列處理,若是肯定請求來源頁面屬於白名單,則在響應頭部加入首部字段:Access-Control-Allow-Origin。瀏覽器比較請求頭部的Origin 和響應頭部的 Access-Control-Allow-Origin是否一致,一致的話,瀏覽器獲得響應數據。若是服務器沒有設置Access-Control-Allow-Origin 或者這個頭部源信息不匹配,瀏覽器就會駁回請求。json
模擬CORS的實現
步驟1.
如何假裝一個網站(在本地)?
1.編輯hosts文件
蘋果mac: 直接在git bash上輸入命令行操做便可 「sudo vi /etc.hosts」 ,或者下載一些圖形界面應用軟件直接修改。
Windows操做系統:
步驟2.
所需工具
node.js && git bash(模擬服務器),一個簡單的html頁面裏面有個跨域請求的Ajax通訊。
<!-- 前端 --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Google</title> <style type="text/css"> body>h1 { text-align: center; } h1 { margin: 0 auto; } </style> </head> <body> <h1>hello world</h1> <script type="text/javascript"> //CORS的實現 var xhr = new XMLHttpRequest(); xhr.onload = function(){ if( (xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) { var responseData = xhr.responseText; console.log(responseData) //['NBA Break News','CBA Break News'] } } xhr.open('GET', 'http://baidu.com:8080/getNews', true); xhr.send() </script> </body> </html>
//nodeJS模擬後端響應CORS的實現 var http = require('http'); var fs = require('fs'); var url = require('url'); var path = require('path'); http.createServer(function(req, res){ var urlObj = url.parse(req.url, true) switch (urlObj.pathname){ case '/getNews': var news = ['NBA Break News','CBA Break News'] //CORS的實現 res.setHeader('Access-Control-Allow-Origin','http://google.com:8080') /*res.setHeader('Access-Control-Allow-Origin','*') 服務器設置公用接口 */ res.end(JSON.stringify(news)); break; case '/' : if(urlObj.pathname == '/') { urlObj.pathname += 'index.html' } default: var filePath = path.join(__dirname, urlObj.pathname); fs.readFile(filePath,'binary', function(error, fileContent){ if(error){ console.log('404') res.writeHeader(404, 'not found') res.end('<h1>404,not found</h1>') }else { res.write(fileContent, 'binary') } }) } }).listen(8080);
上面代碼就是CORS實現的過程。
固然服務器也能夠設置公用接口, res.setHeader('Access-Control-Allow-Origin','*')
服務器設置公用接口, 任何人均可以使用該服務器這個端口的數據。
==方法二==
JSONP,是JSON with padding的簡寫(填充式JSON或參數式JSON)。
JSONP的原理,經過動態<script>元素,使用時能夠爲該元素的src屬性添加一個跨域URL, <script>元素的src有能力不受限制地從其餘域中,加載資源。
凡是擁有src這個屬性的元素均可以跨域,例如<script><img><iframe>。
JSONP和JSON看起來差很少,只不過是被包含在函數調用中的JSON。
JSONP由兩個部分組成:回調函數和數據。回調函數是當響應到來時應該在頁面中調用的函數。回調函數的名字通常是在請求中指定的,因此須要對應接口的後端配合才能實現。而數據就是傳入回調函數中的JSON數據。
模擬JSONP的實現
HTML
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Google</title> <style type="text/css"> body { text-align: center; } h1 { margin: 0 auto; } ul, li { list-style: none; } </style> </head> <body> <h1>hello world</h1> <ul class="news"> </ul> <!--JSONP的代碼實現方法1--> <!-- <script type="text/javascript"> function getResponseData(jsonData){ document.write(jsonData[0] + ', '); document.write(jsonData[1]); //NBA Break News, CBA Break News } </script> <script src="http://baidu.com:8080/getNews?newsData=getResponseData"> //getResponseData(["NBA Break News","CBA Break News"]) </script> --> <!--JSONP的代碼實現方法2--> <script> var script = document.createElement('script'); script.setAttribute('src', 'http://baidu.com:8080/getNews?newsData=getResponseData'); $('body').appendChild(script); $('body').removeChild(script); function getResponseData(jsonData){ var nodeStock = document.createDocumentFragment(); for(var i = 0; i < jsonData.length; i++) { var newsNode = document.createElement('li'); newsNode.innerText = jsonData[i]; nodeStock.appendChild(newsNode) }; $('.news').appendChild(nodeStock) // <li>NBA Break News</li> <li>CBA Break News</li> }; function $(selector) { return document.querySelector(selector); }; </script> </body> </html>
nodeJS
var http = require('http'); var fs = require('fs'); var path = require('path'); var url = require('url'); http.createServer(function(req,res){ var urlObj = url.parse(req.url, true); switch(urlObj.pathname) { case '/getNews' : var news = ['NBA Break News','CBA Break News']; res.setHeader('Content-Type','text/javascript; charset=utf-8'); if(urlObj.query.newsData){ var data = urlObj.query.newsData + '(' + JSON.stringify(news) + ')' ; res.end(data); } else { res.end(JSON.stringify(news)) } break; case '/' : if(urlObj.pathname == '/') { urlObj.pathname += 'index.html' } default: fs.readFile(path.join(__dirname, urlObj.pathname), function(error, data) { if(error) { res.writeHeader(404, 'not found'); res.end('<h1>404, Not Found</h1>'); } else { res.end(data) } }); }; }).listen(8080);
==方法三==
降域,主要應用場景是同一頁面下不一樣源的框架iframe請求
基於iframe實現的跨域,要求兩個域都必須屬於同一個基礎域, 好比 a.xx.com, b.xx.com,都有一個基礎域xx.com, 使用同一協議和端口,這樣在兩個頁面中同時添加documet.domain,就能夠實現父頁面操控子頁面(框架)。
關於document.domain, 用來獲得當前網頁的域名。在瀏覽器輸入URL,wwww.baidu.com。 http://wwww.baidu.com, document.domain 爲 "www.baidu.com"。 也能夠爲document.domain賦值, 不過有限制,就是前面提到的,只能賦值爲當前的域名或者基礎域名。
範例:
document.domain = "www.baidu.com" //successed 賦值成功, 當前域名。
document.domain = "baidu.com" // successed 賦值成功, 基礎域名。
可是下面的賦值會報錯(參數無效)。
"VM50:1 Uncaught DOMException: Failed to set the 'domain' property on 'Document': 'a.baidu.com' is not a suffix of 'www.baidu.com'.
at <anonymous>:1:17"。
範例
document.domain = "google.com" // fail, 參數無效
document.domain = "a.baidu.com" // fail, 參數無效
由於google.com 和 a.baidu.com不是當前的域名,也不是當前域名的基礎域名。
緣由: 瀏覽器爲了防止惡意修改document.domain來實現跨域偷取數據。
-- --
==模擬降域的實現==
錯誤範例:
hosts 文件設置 win10系統路徑爲 c:windowssystem32driversetchosts
127.0.0.1 a.com
127.0.0.1 b.com
a.com的一個網頁(a.html)裏面 利用iframe引入一個b.com裏的一個網頁(b.html )。在a.html裏面能夠看到b.html的內容,但不能用Javascript來操做它。
緣由: 這兩個頁面屬於不一樣的域,在操做以前,瀏覽器會檢測兩個頁面的域是否相等,相等則容許操做,不相等則報錯。
這個例子裏,不可能把a.html與b.html,利用JS改爲同一個域。緣由:兩個域的基礎域名不相等。
在http://a.com:8080/a.html的控制檯(console), 輸入代碼window.frames[0].document.body //VM150:1 Uncaught DOMException: Blocked a frame with origin "http://a.com:8080" from accessing a cross-origin frame.
at <anonymous>:1:18
<!--a.com的一個網頁(a.html)裏面 利用iframe引入一個b.com裏的一個網頁(b.html ) --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>a.com:8080/a.html</title> </head> <body> <iframe src="http://b.com:8080/b.html" frameborder="1"></iframe> </body> </html>
<!-- b.html--> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>b.com:8080/b.html</title> </head> <body> <h1>this is b.html </h1> <input type="text" placeholder="How are you? this is http://b.com:8080/b.html"> </body> </html>
//nodeJS var http = require('http'); var fs = require('fs'); var path = require('path'); var url = require('url'); http.createServer(function(req,res){ var urlObj = url.parse(req.url, true); switch(urlObj.pathname) { case '/getNews' : var news = ['NBA Break News','CBA Break News']; res.setHeader('Content-Type','text/javascript; charset=utf-8'); if(urlObj.query.newsData){ var data = urlObj.query.newsData + '(' + JSON.stringify(news) + ')' ; res.end(data); } else { res.end(JSON.stringify(news)) } break; case '/' : if(urlObj.pathname == '/') { urlObj.pathname += 'index.html' } default: var filePath = path.join(__dirname, 'static' ,urlObj.pathname); console.log(filePath) fs.readFile(filePath, function(error, data) { if(error) { res.writeHeader(404, 'not found'); res.end('<h1>404, Not Found</h1>'); } else { res.end(data) } }); }; }).listen(8080);
能夠把iframe的src改變爲"http://a.com:8080/b.html",這樣就能夠了,是不會有這個問題的,由於域相等。
控制檯不會報錯,可是這樣沒完成跨域。可使用html5中的postMessage來實現,針對基礎域不一樣的框架,這裏暫且不表, 在方法四,會用到這種方法。
window.frames[0].document.body
<body><h1>this is b.html </h1><input type="text" placeholder="How are you? this is http://b.com:8080/b.html"></body>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>a.com:8080/a.html</title> </head> <body> <!-- <iframe src="http://b.com:8080/b.html" frameborder="1"></iframe> --> <iframe src="http://a.com:8080/b.html" frameborder="1"></iframe> </body> </html>
==正確範例:
降域的實現==
hosts文件設置
基礎域名相同
127.0.0.1 a.shawroc.com
127.0.0.1 b.shawroc.com
a.shawroc.com的裏面一個網頁(a.html)引入b.shawroc.com裏的一個網頁(b.html),a.shawroc.com仍是不能操做b.shawroc.com裏面的內容。
緣由:document.domain不同,a.shawroc.com vs b.shawroc.com。
可是兩個頁面的基礎域名是同樣的,經過JS,將兩個頁面的domain改爲同樣。
在a.html 和 b.html 裏都加入
<script type="text/javascript">
document.domian = shawroc.com
</script>
這樣在兩個頁面中同時添加document.domain, 就能夠實現父頁面操控子頁面(框架)。
控制檯
window.frames[0].document.body
//console輸出
<body> <h1>this is http://b.shawroc.com:8080/b.html </h1> <input type="text" placeholder="How are you? this is http://b.shawroc.com:8080/b.html"> <script> document.domain = 'shawroc.com'; </script> </body>
代碼
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>a.shawroc.com:8080</title> </head> <body> <!-- <iframe src="http://b.com:8080/b.html" frameborder="1"></iframe> --> <iframe src="http://b.shawroc.com:8080/b.html" frameborder="1" height="400px" width="600px"></iframe> <script> document.domain = 'shawroc.com'; </script> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>b.shawroc.com:8080/b.html</title> </head> <body> <h1>this is http://b.shawroc.com:8080/b.html </h1> <input type="text" placeholder="How are you? this is http://b.shawroc.com:8080/b.html"> <script> document.domain = 'shawroc.com'; </script> </body> </html>
==方法四==
html5的postMessage API
html5引入的postMessage()方法,容許來自不一樣源的腳本採用異步方式進行有限的通訊,能夠實現跨文本檔、多窗口、跨域消息傳遞。
postMessage(data, origin) 方法,接受兩個參數。
1.data:要傳遞的數據,html5規範中提到該參數能夠是JavaScript的任意基本類型或可複製的對象,然而並非全部瀏覽器都作到了這點兒,部分瀏覽器只能處理字符串參數,因此咱們在傳遞參數的時候須要使用JSON.stringify()方法對對象參數序列化,在低版本IE中引用json2.js能夠實現相似效果。
2.origin:字符串參數,指明目標窗口的源,協議+主機+端口號[+URL],URL會被忽略,因此能夠不寫,這個參數是爲了安全考慮,postMessage()方法只會將message傳遞給指定窗口,固然若是願意也能夠建參數設置爲"*",這樣能夠傳遞給任意窗口,若是要指定和當前窗口同源的話設置爲"/"。
範例
模擬postMessage的工做機制
改寫hosts文件
127.0.0.1 a.com
127.0.0.1 b.com
<!--a.html--> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>a.com:8080</title> </head> <body> <div class="textMessageInACom"> <input type="text" placeholder="http://a.com:8080/a.html"> </div> <iframe src="http://b.com:8080/b.html" frameborder="1" height="400px" width="600px"></iframe> <script> $('.textMessageInACom input').addEventListener('input', function(){ window.frames[0].postMessage(this.value, 'http://b.com:8080'); }) //步驟1 a.com:8080/a.html頁面下的input發生輸入事件時, 向目標窗口發一個MessageEvent事件<iframe src="http://b.com:8080/b.html">, MessageEvent.data能夠得到this.value的值。接下來切換到b.html頁面 window.addEventListener('message', function(messageEvent){ $('.textMessageInACom input').value = messageEvent.data }) // 步驟4 監聽嵌套頁面b.com:8080的message事件,把b.com:8080的input.value(message.data)賦值給a.com:8080的input.value, 就能夠實現輸入內容的同步啦。 function $(selector){ return document.querySelector(selector); } </script> </body> </html>
<!--b.html--> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>b.shawroc.com:8080/b.html</title> </head> <body> <h1>this is http://b.com:8080/b.html </h1> <input id="input" type="text" placeholder="How are you? this is http://b.com:8080/b.html"> </body> <script> window.addEventListener('message', function(e){ $('#input').value = e.data; }) //步驟2, 在b.com:8080/b.html監聽message事件 $('#input').addEventListener('input', function() { window.parent.postMessage(this.value, 'http://a.com:8080'); }) //步驟3,b.com的input發生輸入事件時,向嵌套頁面的父頁面window.parent, a.com:8080 postMessage,而後切回到a.html, function $(selector){ return document.querySelector(selector); } </script> </html>
//nodeJS 模擬後端 var http = require('http'); var fs = require('fs'); var path = require('path'); var url = require('url'); http.createServer(function(req,res){ var urlObj = url.parse(req.url, true); switch(urlObj.pathname) { case '/getNews' : var news = ['NBA Break News','CBA Break News']; res.setHeader('Content-Type','text/javascript; charset=utf-8'); if(urlObj.query.newsData){ var data = urlObj.query.newsData + '(' + JSON.stringify(news) + ')' ; res.end(data); } else { res.end(JSON.stringify(news)) } break; case '/' : if(urlObj.pathname == '/') { urlObj.pathname += 'index.html' } default: var filePath = path.join(__dirname, 'postMessage' ,urlObj.pathname); fs.readFile(filePath, function(error, data) { if(error) { res.writeHeader(404, 'not found'); res.end('<h1>404, Not Found</h1>'); } else { res.end(data) } }); }; }).listen(8080);
解析代碼
步驟1, a.com:8080/a.html頁面下的input發生輸入事件時, 向目標窗口發一個MessageEvent事件<iframe src="http://b.com:8080/b.html">, MessageEvent.data能夠得到this.value的值。接下來切換到b.html頁面。
步驟2, 在b.com:8080/b.html監聽message事件,在這,就能夠實現 a.com:8080/a. html的input標籤輸入什麼,嵌入在 a.com:8080/a. html的iframe框架(b.com:8080/b.html)同步父頁面(a.com:8080/a. html)的輸入內容了。
步驟3,b.com:8080/b.html的input發生輸入事件時,向嵌套頁面的父頁面window.parent(a.com:8080 )postMessage,而後切回到a.html。
步驟4, 監聽嵌套頁面b.com:8080/b.html的messageEvent事件,把b.com:8080/b.html的input.value(message.data)賦值給a.com:8080的input.value, 實現輸入內容的雙向同步。