因爲安全的緣由,瀏覽器作了不少方面的工做,由此也就引入了一系列的跨域問題,須要注意的是:javascript
跨域並不是瀏覽器限制了發起跨站請求,而是跨站請求能夠正常發起,可是返回結果被瀏覽器攔截了。最好的例子是 CSRF
跨站攻擊原理,請求是發送到了後端服務器不管是否跨域!注意:有些瀏覽器不容許從HTTPS的域跨域訪問HTTP,好比Chrome和Firefox,這些瀏覽器在請求還未發出的時候就會攔截請求,這是一個特例html
JSONP
的全稱是 "JSON With Padding", 詞面意思上理解就是 "填充式的JSON"。它不是一個新鮮的東西,隸屬於 JSON
的一種使用方法,或者說是一種使用模式,能夠解決一些常見的瀏覽器端網頁跨域問題。java
正如他的名稱同樣,它是指被包含在調用函數中的JSON,好比這樣:ajax
callback({"Name": "小明", "Id" : 1823, "Rank": 7})
因爲 jQuery
的一些緣由,使得 JSONP
經常與 Ajax
混淆。實際上,他們沒有任何關係。chrome
因爲瀏覽器的同源策略,使得在網頁端出現了這個「跨域」的問題,然而咱們發現,全部的 src
屬性並無受到相關的限制,好比 img
/ script
等。json
JSONP
的原理就要從 script
提及。script
能夠執行其餘域的js
函數,好比這樣:後端
a.html ... <script> function callback(data) { console.log(data.url) } </script> <script src='b.js'></script> ... b.js callback({url: 'http://www.rccoder.net'})
顯然,上面的代碼是能夠執行的,而且能夠在console裏面輸出http://www.rccoder.net跨域
利用這一點,假如b.js裏面的內容不是固定的,而是根據一些東西自動生成的, 嗯,這就是JSONP的主要原理了。回調函數+數據就是 JSON With Padding
了,回調函數用來響應應該在頁面中調用的函數,數據則用來傳入要執行的回調函數。瀏覽器
至於這個數據是怎麼產生的,說粗魯點無非就是字符串拼接了。安全
簡單總結一下: Ajax 是利用 XMLHTTPRequest 來請求數據的,而它是不能請求不一樣域上的數據的。可是,在頁面上引用不一樣域的 js 文件倒是沒有任何問題的,這樣,利用異步的加載,請求一個 js 文件,而這個文件的內容是動態生成的(後臺語言字符串拼接出來的),裏面包含的是 JSON With Padding(回調函數+數據),以前寫的那個函數就由於新加載進來的這段動態生成的 js 而執行,也就是獲取到了他要獲取的數據。
重複一下,在一個頁面中,a.html這樣寫,獲得 UserId 爲 1823 的信息:
a.html ... src="http://server2.example.com/RetrieveUser?UserId=1823&callback=parseResponse"> ...
請求這個地址會獲得一個能夠執行的 JavaScript。好比會獲得:
parseResponse({"Name": "小明", "Id" : 1823, "Rank": 7})
這樣,a.html裏面的 parseResponse()
這個函數就能執行而且獲得數據了。
等等,jQuery到底作了什麼:
jQuery 讓 JSONP 的使用API和Ajax的如出一轍:
$.ajax({ method: 'jsonp', url: 'http://server2.example.com/RetrieveUser?UserId=1823', success: function(data) { console.log(data) } })
之因此能夠這樣是由於 jQuery 在背後傾注了心血,它會在執行的時候生成函數替換callback=dosomthing
,而後獲取到數據以後銷燬掉這個函數,起到一個臨時的代理器做用,這樣就拿到了數據。
JSONP 的後話:
JSONP的這種實現方式不受同源策略的影響,兼容性也很好;可是它之支持 GET 方式的清楚,只支持 HTTP 請求這種特殊的狀況,對於兩個不一樣域之間兩個頁面的互相調用也是無能爲力。
XMLHttpRequest
的同源策略看起來是如此的變態,即便是同一個公司的產品,也不可能徹底在同一個域上面。還好,網絡設計者在設計的時候考略到了這一點,能夠在服務器端進行一些定義,容許部分網絡訪問。
CORS 的全稱是 Cross-Origin Resource Sharing,即跨域資源共享。他的原理就是使用自定義的 HTTP 頭部,讓服務器與瀏覽器進行溝通,主要是經過設置響應頭的 Access-Control-Allow-Origin
來達到目的的。這樣,XMLHttpRequest 就能跨域了。
值得注意的是,正常狀況下的 XMLHttpRequest 是隻發送一次請求的,可是跨域問題下極可能是會發送兩次的請求(預發送)。
更加詳細的內容能夠參見:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
CORS 的後話:
相比之下,CORS 就支持全部類型的 HTTP 請求了,可是在兼容上面,每每一些老的瀏覽器並不支持 CORS。
Desktop:
瀏覽器 | 版本 |
---|---|
Chrome | 4 |
Firefox (Gecko) | 3.5 |
Internet Explorer | 8 (via XDomainReques) 10 |
Opera | 12 |
Safari | 4 |
Mobile:
設備 | 版本 |
---|---|
Android | 2.1 |
Chrome for Android | yes |
Firefox Mobile (Gecko) | yes |
IE Mobile | ? |
Opera Mobile | 12 |
Safari Mobile | 3.2 |
window.name 在一個窗口(標籤)的生命週期以內是共享的,利用這點就能夠傳輸一些數據。
除此以外,結合 iframe 還能實現更增強大的功能:
須要3個文件: a/proxy/b
a.html <script type="text/javascript"> var state = 0, iframe = document.createElement('iframe'), loadfn = function() { if (state === 1) { var data = iframe.contentWindow.name; // 讀取數據 alert(data); //彈出'I was there!' } else if (state === 0) { state = 1; iframe.contentWindow.location = "http://a.com/proxy.html"; // 設置的代理文件 } }; iframe.src = 'http://b.com/b.html'; if (iframe.attachEvent) { iframe.attachEvent('onload', loadfn); } else { iframe.onload = loadfn; } document.body.appendChild(iframe); </script>
b.html <script type="text/javascript"> window.name = 'I was there!'; // 這裏是要傳輸的數據,大小通常爲2M,IE和firefox下能夠大至32M左右 // 數據格式能夠自定義,如json、字符串 </script>
proxy 是一個代理文件,空的就能夠,須要和 a 在同一域下
在不一樣的子域 + iframe交互的時候,獲取到另一個 iframe 的 window對象是沒有問題的,可是獲取到的這個window的方法和屬性大多數都是不能使用的。
這種現象能夠藉助document.domain
來解決。
example.com <iframe id='i' src="1.example.com" onload="do()"></iframe> <script> document.domain = 'example.com'; document.getElementById("i").contentWindow; </script>
1.example.com <script> document.domain = 'example.com'; </script>
這樣,就能夠解決問題了。值得注意的是:document.domain
的設置是有限制的,只能設置爲頁面自己或者更高一級的域名。
document.domain的後話:
利用這種方法是極其方便的,可是若是一個網站被攻擊以後另一個網站極可能會引發安全漏洞。
這種方法能夠把數據的變化顯示在 url 的 hash 裏面。可是因爲 chrome 和 IE 不容許修改parent.location.hash 的值,因此須要再加一層。
a.html 和 b.html 進行數據交換。
a.html
function startRequest(){
var ifr = document.createElement('iframe');
ifr.style.display = 'none';
ifr.src = 'http://2.com/b.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);
b.html
//模擬一個簡單的參數處理操做
switch(location.hash){
case '#paramdo':
callBack();
break;
case '#paramset':
//do something……
break;
}
function callBack(){
try {
parent.location.hash = 'somedata';
} catch (e) {
// ie、chrome的安全機制沒法修改parent.location.hash,
// 因此要利用一箇中間域下的代理iframe
var ifrproxy = document.createElement('iframe');
ifrproxy.style.display = 'none';
ifrproxy.src = 'http://3.com/c.html#somedata'; // 注意該文件在"a.com"域下
document.body.appendChild(ifrproxy);
}
}
c.html
//由於parent.parent和自身屬於同一個域,因此能夠改變其location.hash的值
parent.parent.location.hash = self.location.hash.substring(1);
這樣,利用中間的 c 層就能夠用 hash 達到 a 與 b 的交互了。
這個方法是 HTML5 的一個新特性,能夠用來向其餘全部的window對象發送消息。須要注意的是咱們必需要保證全部的腳本執行完才發送MessageEvent,若是在函數執行的過程當中調用了他,就會讓後面的函數超時沒法執行。
https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage