浏览器的同源策略
浏览器出于安全方面的考虑,只允许与本域下的接口交互。不同源的客户端脚本在没有明确授权的情况下,不能读写对方的资源。
本域指的是?
- 同协议:如都是http或者https
- 同域名:如都是http://zeeliu.com/a 和http://zeeliu.com/b(要求一字不差)
- 同端口:如都是80端口
如:
http://zeeliu.com/a/b.js 和 http://zeeliu.com/index.php (同源)
不同源的例子:
http://zeeliu.com/main.js 和 https://zeeliu.com/a.php (协议不同)
http://zeeliu.com/main.js 和 http://bbs.zeeliu.com/a.php (域名不同,域名必须完全相同才可以)
http://zeeliu.com/main.js 和 http://zeeliu.com:8080/a.php (端口不同,第一个是80)
四中跨域方法
JSONP
JSONP是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,老式浏览器全部支持,服务器改造非常小。
html中script标签可以引入其他域下的js,比如引入线上的jquery库。利用这个特性,可实现跨域访问接口。需要后端支持
- 定义数据处理函数:_fn
- 创建script标签,src的地址执行后端接口,最后加个参数callback=_fun
- 服务端在收到请求后,解析参数,计算返还数据,输出 fun(data) 字符串。
- fun(data)会放到script标签做为js执行。此时会调用fun函数,将data做为参数。
代码如下
index.html代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>jsonp跨域</title>
<style>
.container {
width: 500px;
margin: 0 auto;
}
</style>
</head>
<body>
<div class="container">
<ul class="news">
<li>海军首批空中女战勤加入战斗序列</li>
<li>运20完成首次人员空运试验:乘客均为研发团队成员</li>
<li>英媒称中国将引领科技变革:新四大发明走向全球</li>
</ul>
<button class="change">换一组</button>
</div>
<script>
$('.change').addEventListener('click', function(){
//点击按钮后创建一个<script>标签;且标签的src=“http://127.0.0.1:8080/getNews?callback=appendHtml”
var script = document.createElement('script')
script.src = 'http://127.0.0.1:8080/getNews?callback=appendHtml';
document.head.appendChild(script) //把这个标签插入头部;就会发送src请求
document.head.removeChild(script) //发送请求后,这个script标签就没用了,就立即删除;
})
//由于发送的请求里面有关键字callback=appendHtml
//所以当后台处理请求时候会把数据用这个关键词和括号链接如:“appendHtml(数据)”
//下面这个函数就是用来解析数据的;
function appendHtml(news){
var html = ''
for(var i=0; i<news.length; i++){
html += '<li>' + news[i] + '</li>'
}
console.log(html)
$('.news').innerHTML = html
}
//封装的document选择器
function $(selector){
return document.querySelector(selector)
}
</script>
</body>
</html>
router.js的代码
// 注意这里的代码要放在单独的 router.js 文件中
router.get('/getNews', function (req, res) {
var news = [
"海军首批空中女战勤加入战斗序列",
"运20完成首次人员空运试验:乘客均为研发团队成员",
"英媒称中国将引领科技变革:新四大发明走向全球",
"印巴军队交火未平息 两国代表联合国又上演舌战",
"朝鲜的这样东西比“地震”更加震动美国人",
"郑家概履新武警部队参谋长 接替秦天",
"从仕途起点清除遗毒 这个落马副部问题多严重?",
"蔡英文'朴实'午宴曝光:等于退休人员全家4天菜钱"
]
var data = []
for (var i = 0; i < 3; i++) {
var index = parseInt(Math.random() * news.length)
data.push(news[index])
news.splice(index, 1) //为了避免随机到重复元素,所以push完一个就删除当前
}
var cb = req.query.callback
if (cb) {
res.send(cb + '(' + JSON.stringify(data) + ')')
} else {
res.send(data)
}
})
上面index.html中<script src="http://127.0.0.1:8080/getNews?callback=appendHtml">
标签的src就是一个请求
把这串地址在浏览器打开可以看到返回的数据如下图:
其实其他域名地址下的js就是利用这调串数据调用写好的js函数实现解析数据;实现跨域。
CORS
参考文章:阮一峰-跨域资源共享 CORS 详解
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。
它允许浏览器向跨源服务器,发出XMLHttpRequest
请求,从而克服了AJAX只能同源使用的限制。
本文详细介绍CORS的内部机制。
简介
CORS需要浏览器和服务器同时支持。
目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
基本流程
就是说当我发送请求浏览器会默认帮请求添加一个请求头如图下所示
(这都是由浏览器自己完成)
上面的头信息中,Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。
因此只要后端代码里面同意着了来源的网站请求数据就可以了!
后代码只要在原来的基础上加一个响应头表示我同意这个来源的网站向我请求数据(如下图)
此时http://api.bob.com
这个网站就可以跨域向这个后端请求数据了
CORS与JSONP的比较
CORS与JSONP的使用目的相同,但是比JSONP更强大。
JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。
降域
由于第一次看视频没看懂;最终原因是里面新出现的标签不了解这里顺便把新的知识点也记录一下;
首先什么是降域?
首先我们知道浏览器会阻止两个不同的域名之间进行数据访问操作;
但是当我有一个域名为:zeeliu.com 还有两个子域名:a.zeeliu.com和b.zeeliu.com
由于浏览器的特性a.zeeliu.com和b.zeeliu.com之间也是不能互相访问数据的(同一个爸爸也没用)
但是现实情况是我们希望这两个域名之间互相访问
所以就有了一个`document.domain`的api
例如:
a.zeeliu.com下的文件问index.html
b.zeeliu.com下的文件问index.html
在两个index.html下同时写入js代码document.domain="zeeliu.com"
这是域名都降为zeeliu.com
所以网页a.zeeliu.com(a.zeeliu.com:80/index.html)
和网页b.zeeliu.com(b.zeeliu.com:80/index.html)
之间就可以跨域数据交互了
下面我们通过代码在浏览器中测试
由于我们在同一个文件夹下测试所以两个index.html文件风别用a.html和b.html代替
代码如下(里面有解释)
a.html
<body>
<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>使用降域实现跨域</h1>
<div class="main">
<input type="text" placeholder="http://a.zeeliu.com:8080/a.html我是输入框默认显示">
</div>
<!-- //ifame标签可以在当前窗口建立一个子窗口;窗口显示b.zeeliu.com:8080/b.html这个域名的内容 -->
<iframe src="http://b.zeeliu.com:8080/b.html" frameborder="0" ></iframe>
</div>
<script>
//URL: http://a.zeeliu.com:8080/a.html
//添加一个事件输入框内容变化出发(input事件)
document.querySelector('.main input').addEventListener('input', function(){
console.log(this.value);//在控制台打印输入框输入的内容
//window.frames获取当前窗口中的所有子窗口得到一个类数组对象,其实就是针对选中<ifame>创建的子窗口
//下面的代码是选中窗口后在通过document.querySelector('input')选中b.html中的输入框(input)然后把当前窗口输入的值(this.value)赋给b.html中的input
//就是a.html中输入什么b.html中就显示什么
window.frames[0].document.querySelector('input').value = this.value;
})
//降域
//只有a.html和b.html中同时降域为zeeliu.com上面的input事件中的赋值操作才生效
//也就是a.html通过跨域操作了b.html中的内容
document.domain = "zeeliu.com"
</script>
</body>
b.html
<body>
<style>
html,body{
margin: 0;
}
input{
margin: 20px;
width: 200px;
}
</style>
<input id="input" type="text" placeholder="http://b.zeeliu.com:8080/b.html我是输入框默认显示">
<script>
// URL: http://b.zeeliu.com:8080/b.html
document.querySelector('#input').addEventListener('input', function(){
//同a.html中相同这边这边输入框输入上面同样赋值给那边
//window.parent选择当前前窗口的父窗口一般只有一个
window.parent.document.querySelector('input').value = this.value;
})
//降域
document.domain = 'zeeliu.com';
</script>
</body>
看上面的代码基本可以知道降域是怎么回事了;
下面是如何在本地建立服务器测试这个代码(包括修改hosts的内容)
首先把a.html和b.html放在同一个文件夹下
通过前面学到修改hosts的方法添加两个域名(如下图)
然后在当前文件夹
mock start
创建模拟服务器
没有降域情况下(document.domain被注释)
如下图
输入的地址为a.zeeliu.com:8080/a.html
虚线框窗口引用的地址是b.zeeliu.com:8080/b.html
所以左边输入aaaa右边没有变化
【这是域不同 且没有降域】
如下图
当输入的地址为b.zeeliu.com:8080/a.html及时没用降域也能实现左右同步
【这是因为他们的域相同】
降域情况下
如下图
通过document.domain降域后及时域名不同也可以实现效果
postMessage--------------------------------------
其实就是在不降域的情况a.html向b.html发送自己想给b.html的内容;然后b.html在接受这个内容(你情我愿);反正b向a也是一样
看代码
a.html代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>降域</title>
<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>
</head>
<body>
<div class="ct">
<h1>使用postMessage实现跨域</h1>
<div class="main">
<input type="text" placeholder="http://a.zeeliu.com:8080/a.html">
</div>
<iframe src="http://b.zeeliu.com:8080/b.html" frameborder="0" ></iframe>
</div>
<script>
//当输入框内发生变化触发事件
$('.main input').addEventListener('input', function(){
console.log(this.value);
//将当前窗口的值通过.postMessage发送给window.frames[0]所选中的窗口
//this.value是要发送的值;(可以使其他的值)
//'*':代表任何网站;(当输入b.zeeliu.com:8080/b.html则只发给这个域名)
window.frames[0].postMessage(this.value,'*');
})
//这个事件监听从b.html发送过来的数据;
//e.data就是接收到的数据(b.html中的.postMessage()发送过来的)
window.addEventListener('message',function(e) {
$('.main input').value = e.data
console.log(e.data);
});
function $(id){
return document.querySelector(id);
}
</script>
</body>
</html>
b.html代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>降域</title>
<style>
html,body{
margin: 0;
}
input{
margin: 20px;
width: 200px;
}
</style>
</head>
<body>
<input id="input" type="text" placeholder="http://b.zeeliu.com:8080/b.html">
<script>
$('#input').addEventListener('input', function(){
//将当前窗口的值通过.postMessage发送给window.parent所选中的窗口
//this.value是要发送的值;(可以使其他的值)
//'*':代表任何网站;(当输入a.zeeliu.com:8080/a.html则只发给这个域名)
window.parent.postMessage(this.value, '*');
})
//这个事件监听从a.html发送过来的数据;
//e.data就是接收到的数据(a.html中的.postMessage()发送过来的)
window.addEventListener('message',function(e) {
$('#input').value = e.data
console.log(e.data);
});
function $(id){
return document.querySelector(id);
}
</script>
</body>
</html>