定義
跨域是指從一個(gè)域名的網(wǎng)頁去請(qǐng)求另一個(gè)域名的資源。比如從http://www.baidu.com/ 頁面去請(qǐng)求 http://www.google.com 的資源??缬虻膰?yán)格一點(diǎn)的定義是:只要 協(xié)議,域名,端口有任何一個(gè)的不同,就被當(dāng)作是跨域。
目前學(xué)到的四種跨域的解決方式
JSONP
jsonp是一種跨域通信的手段,它的原理其實(shí)很簡單:
1.首先是利用script標(biāo)簽的src屬性來實(shí)現(xiàn)跨域。
2.通過將前端方法作為參數(shù)傳遞到服務(wù)器端,然后由服務(wù)器端注入?yún)?shù)之后再返回,實(shí)現(xiàn)服務(wù)器端向客戶端通信。
3.由于使用script標(biāo)簽的src屬性,因此只支持get方法。
前端代碼:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>news</title>
<style type="text/css">
.container{
width: 900px;
maigin: 0 auto;
}
</style>
</head>
<body>
<div class="container">
<ul class="news">
</ul>
<button class="show">show news</button>
</div>
<script type="text/javascript">
$('.show').addEventListener('click',function(){
var script = document.createElement('script');
script.src = 'http://b.ji.com:8080/getNews?callback=appendHtml';
document.body.appendChild(script);
document.body.removeChild(script);
})
function appendHtml(news){
var html = '';
for( var i=0; i<news.length; i++){
html += '<li>' + news[i] + '</li>'
}
console.log(html);
$('.news').innerHTML = html
}
function $(id){
return document.querySelector(id);
}
</script>
</body>
</html>
后端代碼:
var http = require('http')
var fs = require('fs')
var path = require('path')
var url = require('url')
http.createServer(function(req,res){
var pathObj = url.parse(req.url,true)
switch(pathObj.pathname){
case '/getNews':
var news = [
"第11日前瞻:中國沖擊4金 博爾特再戰(zhàn)200米羽球",
"正直播柴飚/洪煒出戰(zhàn) 男雙力爭會(huì)師決賽",
"女排將死磕巴西!郎平安排男陪練模仿對(duì)方核心"
]
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:
fs.readFile(path.join(__dirname,pathObj.pathname),function(e,data){
if(e){
res.writeHead(404,'not found')
res.end('<h1>404 Not Found</h1>')
}else{
res.end(data)
}
})
}
}).listen(8080)
- 優(yōu)點(diǎn)
1.兼容性很好,在古老的瀏覽器也能很好的運(yùn)行
2.不需要 XMLHttpRequest 或 ActiveX 的支持;并且在請(qǐng)求完畢后可以通過調(diào)用 callback 的方式回傳結(jié)果。
3.不受同源策略的限制 - 缺點(diǎn)
1.只支持 GET 請(qǐng)求而不支持 POST 等其它類型的 HTTP 請(qǐng)求。
2.只支持跨域HTTP請(qǐng)求這種情況,不能解決不同域的兩個(gè)頁面之間如何進(jìn)行JavaScript調(diào)用的問題。
3.容易遭受XSS攻擊,因?yàn)槲覀兡玫降氖菍?duì)方接口的數(shù)據(jù)作為js執(zhí)行,如果得到的是一個(gè)很危險(xiǎn)js,獲取了用戶信息和cookies,這時(shí)執(zhí)行了js就會(huì)出現(xiàn)安全問題。
CORS
CORS 是一個(gè) W3C 標(biāo)準(zhǔn),全稱是"跨域資源共享"(Cross-origin resource sharing)它允許瀏覽器向跨源服務(wù)器,發(fā)出 XMLHttpRequest 請(qǐng)求,從而克服了 ajax 只能同源使用的限制。
CORS需要瀏覽器和服務(wù)器同時(shí)支持。目前,所有瀏覽器都支持該功能,IE瀏覽器不能低于IE10。整個(gè)CORS通信過程,都是瀏覽器自動(dòng)完成,不需要用戶參與。對(duì)于開發(fā)者來說,CORS通信與同源的AJAX通信沒有差別,代碼完全一樣。瀏覽器一旦發(fā)現(xiàn)AJAX請(qǐng)求跨源,就會(huì)自動(dòng)添加一些附加的頭信息,有時(shí)還會(huì)多出一次附加的請(qǐng)求,但用戶不會(huì)有感覺。
因此,實(shí)現(xiàn)CORS通信的關(guān)鍵是服務(wù)器。只要服務(wù)器實(shí)現(xiàn)了CORS接口,就可以跨源通信。
CORS 分為簡單請(qǐng)求和非簡單請(qǐng)求,本文在這里實(shí)現(xiàn)簡單請(qǐng)求。(非簡單請(qǐng)求請(qǐng)參考)
前端代碼:
<!DOCTYPE html>
<html>
<head>
<title>news</title>
<style type="text/css">
.container{
width: 900px;
margin: 0 auto;
}
</style>
</head>
<body>
<div class="container">
<ul class="news">
</ul>
<button class="show">show news</button>
</div>
<script type="text/javascript">
$('.show').addEventListener('click',function(){
var xhr = new XMLHttpRequest();
xhr.open('GET','http://a.ji.com:8080/getNews',true);
xhr.send();
xhr.onreadystatechange = function(){
if(xhr.readyState === 4 && xhr.status === 200){
appendHtml(JSON.parse(xhr.responseText))
}
}
})
function appendHtml(news){
var html = '';
for(var i=0;i<news.length;i++){
html+='<li>' + news[i] + '</li>';
}
console.log(html);
$('.news').innerHTML = html;
}
function $(selector){
return document.querySelector(selector)
}
</script>
</body>
</html>
后端代碼:
var http = require('http')
var fs = require('fs')
var path = require('path')
var url = require('url')
http.createServer(function(req,res){
var pathObj = url.parse(req.url,true)
switch(pathObj.pathname){
case '/getNews':
var news = [
"第11日前瞻:中國沖擊4金 博爾特再戰(zhàn)200米羽球",
"正直播柴飚/洪煒出戰(zhàn) 男雙力爭會(huì)師決賽",
"女排將死磕巴西!郎平安排男陪練模仿對(duì)方核心"
]
res.setHeader('Access-Control-Allow-Origin','http://b.ji.com:8080')
res.end(JSON.stringify(news))
break;
default:
fs.readFile(path.join(__dirname,pathObj.pathname),function(e,data){
if(e){
res.writeHead(404,'not found')
res.end('<h1>404 Not Found</h1>')
}else{
res.end(data)
}
})
}
}).listen(8080)
Access-Control-Allow-Origin字段是必須的,它的值要么是請(qǐng)求時(shí)Origin字段的值,要么是一個(gè)*,表示接受任意域名的請(qǐng)求。
降域
當(dāng)這兩個(gè)域名都屬于同一個(gè)基礎(chǔ)域名,而且所用的協(xié)議,端口都一致的時(shí)候,可以使用降域來實(shí)現(xiàn)跨域;當(dāng)以上所述成立的時(shí)候,可以通過將domain改成一樣的來實(shí)現(xiàn)。
a.html代碼:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>降域a</title>
<style type="text/css">
.ct{
width: 910px;
margin: auto;
}
.clearfix:after{
content: "";
display: block;
clear: both;
}
.main{
float: left;
width: 450px;
height: 300px;
border: 1px solid #ccc;
}
.main input{
margin: 20px;
width: 200px;
}
.iframe{
float: right;
}
iframe{
width: 450px;
height: 300px;
border: 1px dashed #ccc;
}
</style>
</head>
<body>
<div class="ct clearfix">
<h1>使用降域?qū)崿F(xiàn)跨域</h1>
<div class="main">
<input type="text" placeholder="http://a.ji.com:8080/a.html">
</div>
<iframe src="http://b.ji.com:8080/b.html" frameborder="0"></iframe>
</div>
<script type="text/javascript">
document.querySelector('.main input').addEventListener('input',function(){
console.log(this.value);
window.frame[0].document.querySelector('input').value = this.value;
})
document.domain ='ji.com'
</script>
</body>
</html>
b.html代碼:
<!DOCTYPE html>
<html>
<head>
<title>降域b</title>
<style type="text/css">
html,body{
margin:0;
}
input{
margin: 20px;
width: 200px;
}
</style>
</head>
<body>
<input type="text" id="input" placeholder="http://b.ji.com:8080/b.html">
<script type="text/javascript">
document.querySelector('#input').addEventListener('input',function(){
window.parent.document.querySelector('input').value = this.value;
})
document.domain = 'ji.com'
</script>
</body>
</html>
啟動(dòng)本地服務(wù)器,打開a.ji.com:8080/a.html ,window.frames[0].document.body可以取到body中的元素,
在非同源情況下,因?yàn)橥床呗缘南拗?,無法取到相應(yīng)元素,會(huì)報(bào)錯(cuò)。
postMessage
HTML5引入了一個(gè)全新的API:跨文檔通信 API(Cross-document messaging)。
這個(gè)API為window對(duì)象新增了一個(gè)window.postMessage方法,允許跨窗口通信,不論這兩個(gè)窗口是否同源。
a.html代碼:
<html>
<style>
.ct{
width: 910px;
margin: auto;
}
.main{
float: left;
width: 450px;
height: 300px;
border: 1px solid #ccc;
}
.main input{
margin: 20px;
width: 200px;
}
.iframe{
float: right;
}
iframe{
width: 450px;
height: 300px;
border: 1px dashed #ccc;
}
</style>
<div class="ct">
<h1>使用postMessage實(shí)現(xiàn)跨域</h1>
<div class="main">
<input type="text" placeholder="http://a.ji.com:8080/a.html">
</div>
<iframe src="http://localhost:8080/b.html" frameborder="0" ></iframe>
</div>
<script>
//URL: http://a.jrg.com:8080/a.html
$('.main input').addEventListener('input', function(){
console.log(this.value);
window.frames[0].postMessage(this.value,'*');
})
window.addEventListener('message',function(e) {
$('.main input').value = e.data
console.log(e.data);
});
function $(id){
return document.querySelector(id);
}
</script>
</html>
b.html代碼:
<html>
<style>
html,body{
margin: 0;
}
input{
margin: 20px;
width: 200px;
}
</style>
<input id="input" type="text" placeholder="http://b.ji.com:8080/b.html">
<script>
// URL: http://b.jrg.com:8080/b.html
$('#input').addEventListener('input', function(){
window.parent.postMessage(this.value, '*');
})
window.addEventListener('message',function(e) {
$('#input').value = e.data
console.log(e.data);
});
function $(id){
return document.querySelector(id);
}
</script>
</html>
postMessage方法的第一個(gè)參數(shù)是具體的信息內(nèi)容,第二個(gè)參數(shù)是接收消息的窗口的源(origin),即“協(xié)議 + 域名 + 端口”。也可以設(shè)為*,表示不限制域名,向所有窗口發(fā)送。
github