前端跨域请求解决方案汇总

网上关于这类解决方案的描述有很多,都是差不多的内容,这里做一个摘要。

wengjq/Blog#2

各方法示例

同源策略

abc.com 一级域名(主域名)

*.abc.com 二级域名(子域名)

1 主域相同时的跨域:document.domain + iframe

注:document.domain只能设置为当前域的超级域(只能往上层设置,域名会变得更短)。

1)将两个域的document.domain都设置为相同的主域,注意端口号。

2)还需利用iframe:创建iframe,将其src设置为目标资源的地址,就可以通过获取ifr.contentDocument || ifr.contentWindow.document来加载获取目标资源,然后再对数据进行所需操作。

2 window.name + iframe

该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的。window.name属性的神奇之处在于name 值在不同的页面(甚至不同域名)加载后依旧存在(如果没修改则值不会变化),并且可以支持非常长的 name 值(2MB)。

能使用这种方式跨域,有几个条件必不可少:

  • iframe标签的跨域能力
  • window.name属性值在文档刷新后依旧存在的能力

代码示例:

涉及3个页面:资源请求页a.com/cs1.html),资源请求代理页(a.com/proxy.html),资源提供页(b.com/cs1.htm)。(前两个同源,最后一个不同源。)

  • 将目标域(资源提供页)内的资源内容(要传送的内容)赋值给资源提供页的window.name
1
2
3
<script>
window.name = '要传送的内容';
</script>
  • 资源请求代理页中proxy函数的功能:

    • 资源请求代理页添加iframe,src指向目标域的资源提供页;

    • 第一次加载时,ifr.contentWindow.location指向资源请求页;

    • 之后页面刷新,就得到ifr.contentWindow.name, 用回调函数func处理数据。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<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://b.com/', function(data){
console.log(data);
});
</script>
</body>

window.name+iframe阅读

补充:iframe.srcifr.contentWindow.location.href 的区别:

每个iframe都有个子窗口(可以用iframe.contentWindow获取)

重置iframe.src 或者 iframe.contentWindow.location.href 都会引起子窗口页面的刷新,以及内容的变化(指向重置后的url)

如果重置的是iframe.src,那么刷新后iframe.src === iframe.contentWindow.location.href

如果重置的是iframe.contentWindow.location.hrefiframe.src值不会变化。

3 location.hash + iframe

原理是利用location.hash来进行传值。

假设域名a.com下的文件cs1.html要和b.com域名下的cs2.html传递信息。

1) cs1.html首先创建自动创建一个隐藏的iframe,iframe的src指向b.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文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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); //轮询hash变化。也可以用onhashchange事件,但要考虑兼容性

b.com域名下的cs2.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//模拟一个简单的参数处理操作
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,
// 所以要利用一个中间的a.com域下的代理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的值

1
parent.parent.location.hash = self.location.hash.substring(1);

location.hash+iframe原理图

优点:

  • 可以解决域名完全不同的跨域。
  • 可以实现双向通讯。

缺点:

  • location.hash会直接暴露在URL里,并且在一些浏览器里会产生历史记录,数据安全性不高也影响用户体验。
  • 由于URL大小的限制,支持传递的数据量也不大。
  • 有些浏览器不支持onhashchange事件,需要轮询来获知URL的变化。

location.hash+iframe补充阅读

4 window.postMessage

eg1: 父子窗口通信

父窗口:aaa.com 发消息

1
2
3
var popup = window.open('http://bbb.com', 'title');
//父窗口http://aaa.com向子窗口http://bbb.com发消息,调用postMessage方法。
popup.postMessage('Hello World!', 'http://bbb.com');

子窗口:bbb.com 接收消息

postMessage方法的第一个参数是具体的信息内容,第二个参数是接收消息的窗口的源(origin),即”协议 + 域名 + 端口”。也可以设为*,表示不限制域名,向所有窗口发送。

父窗口和子窗口都可以通过message事件,监听对方的消息。message事件的事件对象event,提供以下三个属性:

  • event.source:发送消息的窗口
  • event.origin: 消息发向的网址
  • event.data:消息内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var onmessage = function (event) {
var data = event.data;//消息
var origin = event.origin;//消息来源地址
var source = event.source;//源Window对象
if(origin == "http://www.aaa.com"){
console.log(data);//hello world!
}
source.postMessage('Nice to see you!', '*');
};
if (typeof window.addEventListener != 'undefined') {
window.addEventListener('message', onmessage, false);
} else if (typeof window.attachEvent != 'undefined') {
//ie
window.attachEvent('onmessage', onmessage);
}

eg2: 可以利用iframe:

1) a.com/index.html中的代码:

1
2
3
4
5
6
7
8
<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';
ifr.contentWindow.postMessage('I was there!', targetOrigin);
};
</script>

2) b.com/index.html中的代码:

1
2
3
4
5
6
7
8
9
10
<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>

5 JSONP(动态创建script标签)

基本原理:网页通过添加一个<script>元素,向服务器请求JSON数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。

JSONP包含两部分:回调函数和数据。

回调函数是当响应到来时要放在当前页面被调用的函数(handleResponse())。

数据就是传入回调函数中的json数据,也就是回调函数的参数了({"data": "zhe"})。

例子如下:

1
2
3
4
5
6
7
8
9
10
function handleResponse(response){
console.log('The responsed data is: ' + response.data); //The responsed data is: zhe
}
var script = document.createElement('script');
//向服务器www.server.com发出请求。注意,该请求的查询字符串有一个callback参数,用来指定回调函数的名字。
script.src = 'http://www.server.com/json/?callback=handleResponse';
document.body.appendChild(script);
//服务器收到这个请求以后,会将数据放在回调函数的参数位置返回。
/*handleResonse({"data": "zhe"});*/

优点:简单适用,老式浏览器全部支持,服务器改造小。不需要XMLHttpRequest或ActiveX的支持。

缺点:只支持GET请求。

这里面试曾经被问到:JSONP有什么不好的地方?

1)安全问题(请求代码中可能存在安全隐患)

2)要确定JSONP请求是否失败并不容易

JSONP补充阅读

6 WebSockets协议

WebSockets协议不受同源策略限制,只要支持该协议的服务器就可以。

1
2
3
4
5
var socket = new WebSockt('ws://www.baidu.com'); //http->ws; https->wss
socket.send('hello WebSockt');
socket.onmessage = function(event){
var data = event.data;
};

7 CORS

Cross-Rrigin Resource Sharing 跨域资源共享

ruanyf/CORS

对开发者来说,CORS请求与同源的AJAX请求无异,由浏览器自动完成CORS请求。

所有浏览器都支持该功能(通过 XMLHttpRequest 对象发起),除了IE浏览器不能低于IE10(Internet Explorer 8 和 9 可以通过 XDomainRequest 对象来实现CORS)。

关键是服务器需要实现CORS接口。

向跨域服务器发送XmlHttpRequest请求,克服AJAX的同源策略。

注意区分简单请求和非简单请求的定义。两种请求的CORS实现流程不同:

  • 对于简单请求:
    • 浏览器在请求头中添加origin字段。服务端判断收到请求的origin是否在白名单中。浏览器根据返回的响应头字段中的Access-Control-字段判断是否跨域请求成功。
    • cookies问题:withCredentials字段设置客户端(若设为true,则服务器给的响应头中Access-Origin-Allow-Origin必须指定允许请求的域名,不能使用“*”),Access-Control-Allow-Credentials设置服务端。cookie仍受同源策略限制。
  • 对于非简单请求:
    • 浏览器先发送预检请求(OPTIONS方法,带上希望CORS请求的Access-Control-Request-Method, Access-Control-Request-Header字段);
    • 得到肯定答复(有Access-Control-Allow-Origin等CORS相关字段)后,
    • 浏览器和服务器用简单请求通信(每次请求带上origin字段,每次响应也有Access-Control-Allow-Origin字段)。
    • 如果浏览器否定预检请求,会返回正常HTTP响应,但是没有任何CORS相关的头信息字段,同时触发错误,被XmlHttpRequest的对象的onerror捕获,打印在控制台。

CORS优点:是跨域请求的根本解决办法,支持所有HTTP方法。

分享
0%