什么是跨域?
跨域,指的是瀏覽器不能執(zhí)行其他網(wǎng)站的腳本。它是由瀏覽器的同源策略造成的,是瀏覽器對javascript施加的安全限制。
所謂同源是指,域名,協(xié)議,端口均相同,不明白沒關(guān)系,舉個栗子:
http://theartemis.cn/index.html 調(diào)用 http://www.365jz.com/server.php (非跨域)
http://www.365jz.com/index.html 調(diào)用 http://www.365yanshi.com/server.php (主域名不同:365jz/365jz,跨域)
http://abc.365jz.com/index.html 調(diào)用 http://def.365jz.com/server.php (子域名不同:abc/def,跨域)
http://theartemis.cn:8080/index.html 調(diào)用 http://www.365jz.com:8081/server.php (端口不同:8080/8081,跨域)
http://www.365jz.com/index.html 調(diào)用 https://www.365jz.com/server.php (協(xié)議不同:http/https,跨域)
請注意:localhost和127.0.0.1雖然都指向本機,但也屬于跨域。
瀏覽器執(zhí)行javascript腳本時,會檢查這個腳本屬于哪個頁面,如果不是同源頁面,就不會被執(zhí)行。
特別注意兩點:
第一,如果是協(xié)議和端口造成的跨域問題“前臺”是無能為力的,
第二:在跨域問題上,域僅僅是通過“URL的首部”來識別而不會去嘗試判斷相同的ip地址對應(yīng)著兩個域或兩個域是否在同一個ip上。
“URL的首部”指window.location.protocol +window.location.host,也可以理解為“Domains, protocols and ports must match”。
解決辦法:
1、JSONP:
jsonp 全稱是JSON with Padding,是為了解決跨域請求資源而產(chǎn)生的解決方案,是一種依靠開發(fā)人員創(chuàng)造出的一種非官方跨域數(shù)據(jù)交互協(xié)議。
一個是描述信息的格式,一個是信息傳遞雙方約定的方法。
jsonp的產(chǎn)生:
1.AJAX直接請求普通文件存在跨域無權(quán)限訪問的問題,不管是靜態(tài)頁面也好.
2.不過我們在調(diào)用js文件的時候又不受跨域影響,比如引入jquery框架的,或者是調(diào)用相片的時候
3.凡是擁有scr這個屬性的標(biāo)簽都可以跨域例如<script><img><iframe>
4.如果想通過純web端跨域訪問數(shù)據(jù)只有一種可能,那就是把遠(yuǎn)程服務(wù)器上的數(shù)據(jù)裝進(jìn)js格式的文件里.
5.而json又是一個輕量級的數(shù)據(jù)格式,還被js原生支持
6.為了便于客戶端使用數(shù)據(jù),逐漸形成了一種非正式傳輸協(xié)議,人們把它稱作JSONP,該協(xié)議的一個要點就是允許用戶傳遞一個callback 參數(shù)給服務(wù)端
demo1:基于script標(biāo)簽實現(xiàn)跨域
舉個例子:我在http://365jz.com/json/jsonp/jsonp_2.html下請求一個遠(yuǎn)程的js文件
<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Insert title here</title><script type="text/javascript">var message = function(data) {alert(data[1].title);};</script><script type="text/javascript" src="http://web.cn/js/message.js"></script></head><body><div id='testdiv'></div></body></html>
遠(yuǎn)程的message.js文件是:
message([{"id":"1", "title":"天津新聞聯(lián)播,雷人搞笑的男主持人"},{"id":"2", "title":"樓市告別富得流油 專家:房價下跌是大概率事件"},{"id":"3", "title":"法國人關(guān)注時事 八成年輕人每天閱讀新聞"},{"id":"4", "title":"新聞中的歷史,歷史中的新聞"},{"id":"5", "title":"東陽新聞20140222"},{"id":"6", "title":"23個職能部門要增加新聞發(fā)布頻次"},{"id":"7", "title":"《貴州新聞聯(lián)播》 中國美麗鄉(xiāng)村"},{"id":"8", "title":"朝韓離散家屬團(tuán)聚首輪活動結(jié)束"},{"id":"9", "title":"索契冬奧會一天曝出兩例興奮劑事件"},{"id":"10", "title":"今天中國多地仍將出現(xiàn)中度霾"}]);
這個時候我們得到的相應(yīng)頭是:
這樣就實現(xiàn)跨域成功了,因為服務(wù)端返回數(shù)據(jù)時會將這個callback參數(shù)(message)作為函數(shù)名來包裹住JSON數(shù)據(jù),這樣客戶端就可以隨意定制自己的函數(shù)來自動處理返回數(shù)據(jù)了
demo2: 基于jquery跨域
那么如何用jquery來實現(xiàn)我們的跨域呢???jquery已經(jīng)把跨域封裝到ajax上了,而且封裝得非常的好,使用起來也特別方便
jsonp形式的ajax請求:并且通過get請求的方式傳入?yún)?shù),注意:跨域請求是只能是get請求不能使用post請求
<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Insert title here</title><script type="text/javascript" src="./js/jquery.js"></script><script type="text/javascript">$(document).ready(function(){var name = 'chenshishuo';var sex = 'man';var address = 'shenzhen';var looks = 'handsome ';$.ajax({type : 'get',url:'http://192.168.31.137/train/test/testjsonp',data : {name : name,sex : sex,address : address,looks : looks,},cache :false,jsonp: "callback",jsonpCallback:"success",dataType : 'jsonp',success:function(data){alert(data);},error:function(data){alert('error');}});});</script></head><body><input id='inputtest' value='546' name='inputtest'><div id='testdiv'></div></body></html>
jsonp 傳遞給請求處理程序或頁面的,用以獲得jsonp回調(diào)函數(shù)名的參數(shù)名(默認(rèn)為:callback)
jsonpCallback 自定義的jsonp回調(diào)函數(shù)名稱,默認(rèn)為jQuery自動生成的隨機函數(shù)名
看看請求頭和相應(yīng)頭吧
請求頭:jquery會自動帶入callback參數(shù),當(dāng)服務(wù)端獲取到這個參數(shù)后,返回回來.(響應(yīng)頭)
現(xiàn)在是不是明白了跨域的基本原理,和基本的使用方法呢??
上面我們說到img中的src可以自動調(diào)用遠(yuǎn)程圖片的(這個比較簡單我在這里就不說了)
還有ifram請求:
基于iframe實現(xiàn)的跨域要求兩個域具有aa.xx.com,bb.xx.com 這種特點,
也就是兩個頁面必須屬于一個基礎(chǔ)域(例如都是xxx.com),使用同一協(xié)議和同一端口,這樣在兩個頁面中同時添加document.domain,就可以實現(xiàn)父頁面調(diào)用子頁面的函數(shù)
要點就是 :通過修改document.domain來跨子域
demo3: 通過iframe來跨子域
http://a.study.cn/a.html 請求 http://b.study.cn/b.html
在a.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript">
document.domain = 'study.cn';
function test() {
alert(document.getElementById('a').contentWindow);
}
</script>
</head>
<body>
<iframe id='a' src='http://b.study.cn/b.html' onload='test()'>
</body>
</html>
在b.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script type="text/javascript">
document.domain = 'study.cn';
</script>
</head>
<body>
我是b.study.cn的body
</body>
</html>
運行效果截圖:
我們就可以通過js訪問到iframe中的各種屬性和對象了
如果你想在http://a.study.cn/a.html頁面中通過ajax直接請求頁面http://b.study.cn/b.html,即使你設(shè)置了相同的document.domain也還是不行的.
所以修改document.domain的方法只適用于不同子域的框架(父類與子類)間的交互。
如果想通過使用ajax的方法去與不同子域間的數(shù)據(jù)交互或者是js調(diào)用,只有兩種方法,一種是使用jsonp的方法外,還有一種是使用iframe來做一個代理。
原理就是讓這個 iframe載入一個與你想要通過ajax獲取數(shù)據(jù)的目標(biāo)頁面處在相同的域的頁面,所以這個iframe中的頁面是可以正常使用ajax去獲取你要的數(shù)據(jù) 的,
然后就是通過我們剛剛講得修改document.domain的方法,讓我們能通過js完全控制這個iframe,這樣我們就可以讓iframe去發(fā) 送ajax請求,然后收到的數(shù)據(jù)我們也可以獲得了。
2、代理:
例如www.123.com/index.html需要調(diào)用www.456.com/server.php,可以寫一個接口www.123.com/server.php,由這個接口在后端去調(diào)用www.456.com/server.php并拿到返回值,然后再返回給index.html,這就是一個代理的模式。相當(dāng)于繞過了瀏覽器端,自然就不存在跨域問題。
3、PHP端修改header(XHR2方式)
在php接口腳本中加入以下兩句即可:
header('Access-Control-Allow-Origin:*');//允許所有來源訪問
header('Access-Control-Allow-Method:POST,GET');//允許訪問的方式
4、 location.hash + iframe
原理是利用location.hash來進(jìn)行傳值。
假設(shè)域名a.com下的文件cs1.html要和cnblogs.com域名下的cs2.html傳遞信息。
1) cs1.html首先創(chuàng)建自動創(chuàng)建一個隱藏的iframe,iframe的src指向cnblogs.com域名下的cs2.html頁面
2) cs2.html響應(yīng)請求后再將通過修改cs1.html的hash值來傳遞數(shù)據(jù)
3) 同時在cs1.html上加一個定時器,隔一段時間來判斷l(xiāng)ocation.hash的值有沒有變化,一旦有變化則獲取獲取hash值
注:由于兩個頁面不在同一個域下IE、Chrome不允許修改parent.location.hash的值,所以要借助于a.com域名下的一個代理iframe
代碼如下:
先是a.com下的文件cs1.html文件:
function startRequest(){
var ifr = document.createElement('iframe');
ifr.style.display = 'none';
ifr.src = 'http://www.cnblogs.com/lab/cscript/cs2.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);
cnblogs.com域名下的cs2.html:
//模擬一個簡單的參數(shù)處理操作
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,
// 所以要利用一個中間的cnblogs域下的代理iframe
var ifrproxy = document.createElement('iframe');
ifrproxy.style.display = 'none';
ifrproxy.src = 'http://a.com/test/cscript/cs3.html#somedata'; // 注意該文件在"a.com"域下
document.body.appendChild(ifrproxy);
}
}
a.com下的域名cs3.html
//因為parent.parent和自身屬于同一個域,所以可以改變其location.hash的值
parent.parent.location.hash = self.location.hash.substring(1);
5、 window.name + iframe
window.name 的美妙之處:name 值在不同的頁面(甚至不同域名)加載后依舊存在,并且可以支持非常長的 name 值(2MB)。
1) 創(chuàng)建a.com/cs1.html
2) 創(chuàng)建a.com/proxy.html,并加入如下代碼
<head>
<script>
function proxy(url, func){
var isFirst = true,
ifr = document.createElement('iframe'),
loadFunc = function(){
if(isFirst){
ifr.contentWindow.location = 'http://a.com/cs1.html';
isFirst = false;
}else{
func(ifr.contentWindow.name);
ifr.contentWindow.close();
document.body.removeChild(ifr);
ifr.src = '';
ifr = null;
}
};
ifr.src = url;
ifr.style.display = 'none';
if(ifr.attachEvent) ifr.attachEvent('onload', loadFunc);
else ifr.onload = loadFunc;
document.body.appendChild(iframe);
}
</script>
</head>
<body>
<script>
proxy('http://www.baidu.com/', function(data){
console.log(data);
});
</script>
</body>
3 在b.com/cs1.html中包含:
<script>
window.name = '要傳送的內(nèi)容';
</script>
6、 postMessage(HTML5中的XMLHttpRequest Level 2中的API)
1) a.com/index.html中的代碼:
<iframe id="ifr" src="b.com/index.html"></iframe>
<script type="text/javascript">
window.onload = function() {
var ifr = document.getElementById('ifr');
var targetOrigin = 'http://b.com'; // 若寫成'http://b.com/c/proxy.html'效果一樣
// 若寫成'http://c.com'就不會執(zhí)行postMessage了
ifr.contentWindow.postMessage('I was there!', targetOrigin);
};
</script>
2) b.com/index.html中的代碼:
<script type="text/javascript">
window.addEventListener('message', function(event){
// 通過origin屬性判斷消息來源地址
if (event.origin == 'http://a.com') {
alert(event.data); // 彈出"I was there!"
alert(event.source); // 對a.com、index.html中window對象的引用
// 但由于同源策略,這里event.source不可以訪問window對象
}
}, false);
</script>
7、 CORS
CORS背后的思想,就是使用自定義的HTTP頭部讓瀏覽器與服務(wù)器進(jìn)行溝通,從而決定請求或響應(yīng)是應(yīng)該成功,還是應(yīng)該失敗。
IE中對CORS的實現(xiàn)是xdr
var xdr = new XDomainRequest();
xdr.onload = function(){
console.log(xdr.responseText);
}
xdr.open('get', 'http://www.baidu.com');
......
xdr.send(null);
其它瀏覽器中的實現(xiàn)就在xhr中
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if(xhr.readyState == 4){
if(xhr.status >= 200 && xhr.status < 304 || xhr.status == 304){
console.log(xhr.responseText);
}
}
}
xhr.open('get', 'http://www.baidu.com');
......
xhr.send(null);
實現(xiàn)跨瀏覽器的CORS
function createCORS(method, url){
var xhr = new XMLHttpRequest();
if('withCredentials' in xhr){
xhr.open(method, url, true);
}else if(typeof XDomainRequest != 'undefined'){
var xhr = new XDomainRequest();
xhr.open(method, url);
}else{
xhr = null;
}
return xhr;
}
var request = createCORS('get', 'http://www.baidu.com');
if(request){
request.onload = function(){
......
};
request.send();
}
8、 web sockets
web sockets是一種瀏覽器的API,它的目標(biāo)是在一個單獨的持久連接上提供全雙工、雙向通信。(同源策略對web sockets不適用)
web sockets原理:在JS創(chuàng)建了web socket之后,會有一個HTTP請求發(fā)送到瀏覽器以發(fā)起連接。取得服務(wù)器響應(yīng)后,建立的連接會使用HTTP升級從HTTP協(xié)議交換為web sockt協(xié)議。
只有在支持web socket協(xié)議的服務(wù)器上才能正常工作。
var socket = new WebSockt('ws://www.baidu.com');//http->ws; https->wss
socket.send('hello WebSockt');
socket.onmessage = function(event){
var data = event.data;
}
如對本文有疑問,請?zhí)峤坏浇涣髡搲?,廣大熱心網(wǎng)友會為你解答!! 點擊進(jìn)入論壇
websocket 好像不是跨域解決方案把,它本身也會有跨域問題把,它本身只是實現(xiàn)了服務(wù)器推送把!~!~