什么是跨域?
一个域名地址的组成:
http:// | www | abc.com | :8080 | /script/iquire.js |
---|---|---|---|---|
协议 | 子域名 | 主域名 | 端口号 | 请求资源地址 |
当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同的域。
不同域之间相互请求资源,就算 “跨域”。
例如:
a)同一域名,不同协议 不允许通信
http://www.a.com/a.js
https://www.a.com/b.js
b)主域相同,子域不同 不允许通信
http://www.a.com/a.js
http://script.a.com/b.js
c)同一域名,不同二级域名 不允许
http://www.a.com/a.js
http://a.com/b.js
注意:
1)如果是协议和端口造成的跨域问题“前台”是无能为力的
2)在跨域问题上,域仅仅是通过“URL的首部”来识别,而不会去尝试判断相同的ip地址对应着两个域或两个域是否在同一个ip上。
“URL的首部”指window.location.protocol +window.location.host,也可以理解为“Domains, protocols and ports must match”。
例如:localhost和127.0.0.1虽然都指向本机,但也属于跨域。
为什么要解决跨域问题?
跨域问题是由浏览器的同源策略造成的,是浏览器对 JavaScript 施加的安全限制。
所谓同源策略,指的是浏览器对不同源的脚本或者文本的访问方式进行的限制。
从一个域上加载的脚本不允许访问另外一个域的文档属性。
同源策略限制了不同源之间的交互。
如果两个URL的协议、域名和端口都相同,则表示它们同源。
注意:
同源策略限制的不同源之间的交互主要针对的是js中的XMLHttpRequest等请求,下面这些情况是完全不受同源策略限制的:
a)在浏览器中,<script>、<img>、<iframe>、<link>
等标签都可以加载跨域资源,而不受同源限制,但浏览器限制了Javascript不能读写加载的内容。。
b)页面中的链接,重定向以及表单提交是不会受到同源策略限制的。
解决跨域问题的几种方式
1. JSONP跨域访问
Jsonp(JSON with Padding) 是 json 的一种”使用模式”,可以让网页从别的域名(网站)那获取资料,即跨域读取数据。
JSONP也是开发中常见到的内容,在jquery中就有封装,通过ajax请求多带上一个jsonp的参数即可。它的实现原理其实跟ajax没有多少关系。
实现:动态的创建<script>
标签,跨域的地址+需要传送的数据+回调函数为<script>
标签的 src 地址,执行完之后再动态的删除之前创建的<script>
.
回调函数是当响应到来时要放在当前页面被调用的函数。
数据是传入回调函数中的json数据
原理:
1)首先在客户端注册一个回调函数, 然后把回调函数的名字传给服务器;
2)服务器调用这个函数并且将JSON 数据形式作为参数传递,返回给客户端,完成回调。
在页面中,返回的JSON作为参数传入回调函数中,我们通过回调函数来来操作数据。
动态实现JSONP请求
// a.html
<script>
function jsonCallback(data) {
alert(data.message);
}
function addScriptTag(src){
var script = document.createElement('script');
script.src = src;
document.body.appendChild(script);
}
window.onload = function(){
addScriptTag("http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=apple&callback=jsonCallback");
}
</script>
JQuery中$.ajax
的跨域实现:
$.ajax({
async : true,
url : "https://api.douban.com/v2/book/search",
type : "GET",
dataType : "jsonp", // 返回的数据类型,设置为JSONP方式
jsonp : 'callback', //指定一个查询参数名称来覆盖默认的 jsonp 回调参数名 callback
jsonpCallback: 'handleResponse', //设置回调函数名
data : {
q : "javascript",
count : 1
},
success: function(response, status, xhr){
console.log('状态为:' + status + ',状态是:' + xhr.statusText);
console.log(response);
}
});
JQuery中$.getJSON
的跨域实现:
<script type="text/javascript">
$.getJSON("http://crossdomain.com/services.php?callback=?",
function(result) {
for(var i in result) {
alert(i+":"+result[i]);//循环输出a:1,b:2,etc.
}
});
</script>
缺点:
只能用 get
方式请求,因为是使用 src
来传送数据;
src 对请求的地址没有限制,会出现安全性的问题;
数据中出现中文需要编码。因为通过 url 传参数。
2. document.domain + iframe
适用于:主域相同而子域不同的情况。
实现:
1)在 http://www.a.com/a.html 和 http://script.a.com/b.html 两个文件中分别加上document.domain = ‘a.com’
;
2)在 a.html 文件中创建一个 iframe,去控制 iframe 的contentDocument。
//在www.a.com/a.html中
document.domain = 'a.com';
var ifr = document.createElement('iframe');
ifr.src = 'http://script.a.com/b.html';
ifr.style.display = 'none';
document.body.appendChild(ifr);
ifr.onload = function(){
var doc = ifr.contentDocument || ifr.contentWindow.document;
// 在这里操纵b.html(doc)
};
//在 script.a.com/b.html 中
document.domain = 'a.com';
注意:
domain只能设置为主域名,不可以在b.a.com中将domain设置为c.a.com。
缺点:
1)安全性。当一个站点(b.a.com)被攻击后,另一个站点(c.a.com)会引起安全漏洞。
2)如果一个页面中引入多个iframe,要想能够操作所有iframe,必须都得设置相同domain。
3.动态创建script
script 标签不受同源策略的限制。因此 ,通过创建script节点的方法来实现完全跨域的通信。
function loadScript(url, func) {
var head = document.head || document.getElementByTagName('head')[0];
var js = document.createElement('script');
js.src = url;
//判断script节点加载完毕
js.onload = js.onreadystatechange = function(){
if(!this.readyState || this.readyState=='loaded' || this.readyState=='complete'){
func();// callback在此处执行
js.onload = js.onreadystatechange = null;
}
};
head.insertBefore(script, 0);
}
ie只能通过script的readystatechange属性,其它浏览器是script的load事件。
4. location.hash + iframe
原理:利用location.hash来进行传值。
例如,http://a.com#helloword 中的 ‘#helloworld’ 就是 location.hash,改变hash并不会导致页面刷新,所以可以利用hash值来进行数据传递,当然数据容量是有限的。
假设域名a.com
下的文件cs1.html
要和cnblogs.com
域名下的cs2.html
传递信息:
1) cs1.html首先自动创建一个隐藏的iframe,iframe的src指向cnblogs.com域名下的cs2.html页面;
2) cs2.html响应请求后再将通过修改cs1.html的hash值来传递数据;
3) 同时在cs1.html上加一个定时器,隔一段时间来判断location.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:
//模拟一个简单的参数处理操作
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);
}
}
5. window.name + iframe
window对象name属性的特征:
在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。
并且可以支持非常长的 name 值(2MB)。
实现:
1)在应用页面(a.com/app.html)中创建一个iframe,把其src指向数据页面(b.com/data.html)
2)在应用页面(a.com/app.html)中,监听iframe的onload事件,在此事件中设置这个iframe的src指向本地域的代理文件(代理文件和应用页面在同一域下,所以可以相互通信)
3)获取数据以后销毁这个iframe,释放内存;这也保证了安全(不被其他域frame js访问)
//a.com/app.html
<script type="text/javascript">
var state = 0,
iframe = document.createElement('iframe'),
iframe.src = 'http://b.com/data.html';
loadfn = function() {
if (state === 1) {
var data = iframe.contentWindow.name; // 读取数据
alert(data); //弹出'I was there!'
iframe.contentWindow.document.write('');
iframe.contentWindow.close();
document.body.removeChild(iframe);
} else if (state === 0) {
state = 1;
// 设置的代理文件
iframe.contentWindow.location = "http://a.com/proxy.html";
}
};
if (iframe.attachEvent) {
iframe.attachEvent('onload', loadfn);
} else {
iframe.onload = loadfn;
}
document.body.appendChild(iframe);
</script>
//data.html
<script type="text/javascript">
window.name = 'I was there!';
// 这里是要传输的数据,大小一般为2M,IE和firefox下可以大至32M左右
// 数据格式可以自定义,如json、字符串
</script>
销毁
<script>
iframe.contentWindow.document.write('');
iframe.contentWindow.close();
document.body.removeChild(iframe);
</script>
参考:
window.name实现的跨域数据传输
使用 window.name 解决跨域问题
利用window.name+iframe跨域获取数据详解
6. HTML5 的 window.postMessage 方法
window.postMessage(message,targetOrigin) 方法是 html5 新引进的特性,可以使用它来向其它的window对象发送消息,无论这个window对象是属于同源或不同源。
otherWindow.postMessage(message,targetOrigin)
otherWindow——指要接收消息的那一个window对象。
message——要发送的数据。string类型
targetOrigin——用于限制接收消息的window对象,“*”表示不作限制
otherWindow 通过监听自身的 message 事件来获取传过来的消息,消息内容储存在该事件对象的data属性中。
a.com/index.html中的代码:
<iframe id="ifr" src="b.com/index.html"></iframe>
<script>
window.onload = function() {
var ifr = document.getElementById('ifr');
var targetOrigin = 'http://b.com';
// 若写成'http://b.com/c/proxy.html'效果一样
// 若写成'http://c.com'就不会执行postMessage了
ifr.contentWindow.postMessage('I come from a.com!', targetOrigin);
};
</script>
b.com/index.html中的代码:
<script>
window.addEventListener('message', function(event){
// 通过origin属性判断消息来源地址
if (event.origin == 'http://a.com') {
alert(event.data); // 弹出"I come from a.com!"
}
}, false);
</script>