跨域操作
浏览器出于安全方面的考虑,对于一个页面内的所有的请求做了一系列的限制,称之为:同源策略,但有时候我们的确有这方面的需求,所以可以通过一下说明一下:
同源Same origin Policy
: 即是指3相同–相同协议 + 相同origin(可以理解为协议后的/
到第一个/
之间的内容) + 相同端口跨域访问/不同源
: 就是与不同域间接口通信,从浏览器的角度基本有4种常见形式JSONP
,CROS
,(满足一定条件下的)降域方式
,通过window的message事件
首先演示一下正常请求操作: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
35
36
37
38
39
40
41
42
43
44
45
46
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>跨域操作</title>
<style>
.container{
width: 900px;
margin: 0 auto;
background: slategray;
}
</style>
</head>
<body>
<div class="container">
<h1>aaa域中的html</h1>
<hr> 点击去另一域bbb中加载数据
<br>
<button>点击</button>
</div>
<script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<script>
var btn = $('button')[0]
var container = $('.container')[0]
btn.addEventListener('click', function() {
xhr = new XMLHttpRequest()
xhr.open('get', 'http://aaa.com:3000/info', true)
xhr.send()
var html = ""
xhr.onreadystatechange = function() {
if(xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 304)) {
result = JSON.parse(xhr.responseText)
html = document.createElement('p')
html.innerText = result['info']
container.appendChild(html)
}else {
console.log('error')
}
}
})
</script>
</body>
</html>
服务器端使用node的mock-server,在router.js中回应:1
2
3
4app.get('/info', function(req, res) {
var data = {info: 'Greating from router.js'}
res.send(data)
});
得到:
现在来进行简单的跨域请求,如xhr.open('get', 'http://aaa.com:3000/info', true)
中的url改为http://bbb.com:3000/info'
,浏览器会报错:
- CORS
刚才从提示中已经隐含了提示:No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://aaa.com:3000' is therefore not allowed access.
如果我们在服务器端的为请求头添加权限,就能正常访问了如:1
2
3
4
5app.get('/info', function(req, res) {
var data = {info: 'Greating from router.js'}
res.header("Access-Control-Allow-Origin", "*");
res.send(data)
});
所以可以猜测实际上request是到达了,但没有响应权限,响应内容为空,有些浏览器甚至状态码都显示200,这一点挑食时注意,
由于这种处理主要是服务器端处理的资源分享方式,所以称为CORS [Cross-Origan Resource Sharing] 跨域资源共享
这样虽然解决了问题,但都是从服务器操作,下面重点介绍从浏览器端的操作
- JSONP
本质上就是一个script标签调用的远程脚本,利用script标签的src属性,拿到不同域的远程脚本内容实现跨域通信,其内容格式为callBack({data})
,总之基本上就是js调用函数的格式,script会执行找到本执行本地方法callBack
,将上诉代码更改一下:1
2
3
4
5
6
7<scrip>
var callBack = function(data) {
console.log("^^^^ . ^^^^^" + data)
alert('本地写好了的callBack,被远程js调用,并传递数据: ' + data.info)
}
</script>
<script src="http://bbb.com:3000/info.js"></script
刷新页面就能看到callBack被调用了,这就是JSONP的核心内容
但是如果仅仅是这样,就要求我们提起在本地写好callBack并通知给服务器,协商的成本太高,所以将callBack函数名带在url中,url做成?callback=funcName
,后台获取到funcName,在组织JSONP文件传入数据即可
注意:虽然总是根ajax一起出现,但本质上是2中东西,前者是异步网络技术,而后者就是个普通script脚本
- postMessage
适用于共用一个window下不同frame的通信,即不同html间可跨域的通信,eg,这是在bbb.com域下的iframe.html
: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
35
36
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
body {
background: palevioletred;
}
</style>
</head>
<body>
<h3>iframe 中的html -- 来自不同域bbb.com中的html</h3>
<div class="page">
<form action="">
输入:<br>
<input class="input" type="text">
</form>
</div>
<script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<script>
var input = $('.page .input')[0]
input.addEventListener('input', function(e) {
console.log(this.value)
window.parent.postMessage(this.value, '*')
})
window.addEventListener('message', function(e) {
input.value = e.data
console.log(e.data)
})
</script>
</body>
</html
然后增加新代码后的index.html
为: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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>server-mock使用说明</title>
<style>
.container{
width: 900px;
margin: 0 auto;
background: slategray;
}
.page {
background: lightseagreen;
text-align: center;
}
iframe {
width: 300px;
}
</style>
</head>
<body>
<div class="container">
<h1>aaa域中的html</h1>
<hr> 点击去另一域bbb中加载数据
<br>
<button>点击</button>
</div>
<hr>
<div class="page">
<form action="">
输入:<br>
<input class="input" type="text">
</form>
<iframe src="http://bbb.com:3000/iframe.html" frameborder="0"></iframe>
</div>
<script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<script>
var btn = $('button')[0]
var container = $('.container')[0]
btn.addEventListener('click', function() {
xhr = new XMLHttpRequest()
xhr.open('get', 'http://bbb.com:3000/info', true)
xhr.send()
var html = ""
xhr.onreadystatechange = function() {
if(xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 304)) {
result = JSON.parse(xhr.responseText)
html = document.createElement('p')
html.innerText = result['info']
container.appendChild(html)
}else {
console.log('error')
}
}
})
var callBack = function(data) {
console.log("^^^^ . ^^^^^" + data)
alert('本地写好了的callBack,被远程js调用,并传递数据: ' + data.info)
}
</script>
<script src="http://bbb.com:3000/info.js"></script>
<script>
var iframe = $('.page iframe')[0]
var input = $('.page .input')[0]
input.addEventListener('input', function(e) {
console.log(this.value)
window.frames[0].postMessage(this.value, '*')
})
window.addEventListener('message', function(e) {
input.value = e.data
console.log(e.data)
})
</script>
</body>
</html>
验证结果后最终实现2不同域不同html文件之间的通信
- 一定条件下的降域
在主域相同情况下,通过设置document.domain
为共同的自身或者更高一级的赋予,就可以通过window.fram[0].document.querySelecotr('.page .inpu')
访问到iframe.html中的元素了,严格说来并不是一种跨域
,比如完全demain不用的2个域是不允许这样操作的,所以在真正需要跨域的时候并不常用