YongSir

专业程序员伪装者

跨域操作

跨域操作

浏览器出于安全方面的考虑,对于一个页面内的所有的请求做了一系列的限制,称之为:同源策略,但有时候我们的确有这方面的需求,所以可以通过一下说明一下:

  • 同源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
<!DOCTYPE html>
<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
4
app.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
    5
    app.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
    <!DOCTYPE html>
    <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
<!DOCTYPE html>
<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个域是不允许这样操作的,所以在真正需要跨域的时候并不常用