美好的前端学习在五一这天又一次开始!这次来看看跨域如何实现。
这一天依旧是继续加油的马大侠~
什么是跨域
浏览器出于安全方面的考虑,不允许js跨域调用其他页面,于是出现了同源策略。在这里我们把域想成域名,比如有一个域名为china.net,另一个为china.com,这两者属于不同的域名,所以它们之间的页面也是不能相互调用的,这属于同源策略的一种。我们来看看同源策略具体分为几类:
- 不同域名
- 相同域名不同端口号
- 同一个域名不同协议
- 域名和域名对应的IP
- 主域和子域
- 子域和子域
以上情况只要出现了,那么就会产生跨域问题。如何解决跨域的问题:
跨域解决方案
JSONP
对于JSONP,有个易懂的解释:JSONP是数据格式json的一种“使用方式”,可以让网页从别的网页要数据。
由于同源策略,页面之间的相互调用具有限制,但是HTML中的 <script>
标签并不遵书同源策略,我们可以利用这个特性,让网页可以得到从其他来源动态产生的JSONP数据,这种使用模式就是所谓的JSONP。用JSONP抓到的数据并不是JSON,而是任意的JavaScript,用JavaScript解释器运行而不是用JSON解析器解析。我们来看个例子:
前端浏览器页面
<script>
function jsonpCallBack (res, req) {
console.log(res, req);
}
</script>
<script type="text/JavaScript" src="http://localhost/test/jsonp.php?callback=jsonpCallBack&data=getJsonpData"></script>
};
另一个域名服务器请求接口
<?php
/*后端获取请求字段数据,并生成返回内容*/
$data = $_GET["data"];
$callback = $_GET["callback"];
echo $callback."('success', '".$data."')";
?>
测试结果如下:
这种方案要注意的是,它只支持GET这一种HTTP请求类型,还有尤为重要的是其他域要有可靠性,保证安全。当然有时我们还可以通过一个方法来动态生成需要的JSONP.
跨域资源共享CORS
CORS,是JSONP模式的现代升级版,不同的是,他除了GET这种HTTP请求方式,还支持其它的HTTP请求。浏览器CORS请求分成两种:
- 简单请求
- 非简单请求(协商模型/预检请求)
如何区分请求具体属于哪一种呢?看下面的总结:
- 请求方式
GET
HEAD
POST- HTTP的头信息字段
Accept
Accept-Language
Cntent-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain,其中’text/plain’默认支持,其他两种则需要预检请求和服务器协商。
满足以上两大点的即为简单请求,否则为非简单请求。具体请求处理的不同 大家可以去查阅下MDN HTTP访问控制(CORS) ,那里有详细的解析及用法。
document.domain+iframe(适用于主域名相同的情况)
从同源策略可知,浏览器认为主域和子域,子域和子域,它们属于不同的域,那么如果我们需要让主域和子域之间可以进行通信,我们需要修改document.domain,把它们改成相同的domain
我们来看一个例子:
在域名为http://server.example.com中的a.html:
document.domain = 'example.com';
var $iframe = document.createElement('iframe');
$iframe.src = 'server.child.example.com/b.html';
$iframe.style.display = 'none';
document.body.appendChild($iframe);
$iframe.onload = function(){
var doc = $iframe.contentDocument || $iframe.contentWindow.document;
//在这里操作doc,也就是操作b.html
$iframe.onload = null;
};
在域名为http://server.child.example.com中的b.html:
document.domain = 'example.com'
这种形式方便贵方便但是也有其他方面带来的隐患:
- 当一个站点受到攻击后,另一个站点会引起安全漏洞
- 若页面同时引入多个Iframe,想要操作所有iframe,domain需要全部设置成一样的。
window.name + iframe
- window对象的name属性是一个很特别的属性,它可以在不同页面甚至不同域名加载后依旧存在。使用步骤如下:
- 首先在A页面中利用iframe加载其他域的B页面
- 如有需要传给A页面的数据,在B页面中赋给window.name
- 在A页面中修改iframe的地址,把地址变成同一个域下的地址
- 现在,就可以获取到iframe中页面B的window.name属性
我们来看一个示例:
- 首先我们在域名为http://127.0.0.1下建好页面B,在B页面的
<script>
标签中将需要传递的数据赋给window.name
window.name = '页面B中传递给页面A的数据';
function proxy (url, callback) {
var flag = true,
$iframe = document.createElement('iframe'),
loadCallBack = function () {
if (flag) {
// 这里我们还得在域名为 http://127.0.0.1:9000 建立一个tmp.html文件当做缓存界面
$iframe.contentWindow.location = 'http://127.0.0.1:9000/tmp.html';
flag = false;
}
// 修改localtion后,每次触发onload事件会重置src,相当于重新载入页面,然后继续触发onload。
// 这里是针对该问题做的处理
else {
callback($iframe.contentWindow.name);
$iframe.contentWindow.close();
document.body.removeChild($iframe);
$iframe.src = '';
$iframe = null;
}
};
$iframe.src = url;
$iframe.style.display = 'none';
// 事件绑定兼容简单处理
// IE 支持iframe的onload事件,不过是隐形的,需要通过attachEvent来注册
if ($iframe.attachEvent) {
$iframe.attachEvent('onload', loadCallBack);
}
else {
$iframe.onload = loadCallBack;
}
document.body.appendChild($iframe);
}
proxy('http://127.0.0.1/bop/test.html', function(data){
console.log(data);
});
- 测试结果如图:
HTML5中的postMessage(适用于两个iframe或两个页面之间)
postMessage隶属于html5,但是它支持IE8+和其他的浏览器,即可以实现同域传递,也可以实现跨域传递。它包括发送消息postMessage和接受消息message功能
- postMessage的调用语法如下:
otherWindow.postMessage(message, targetOrigin, [transfer]);- otherWindow:其他窗口的一个引用,比如iframe的contentWindow属性、执行window.open返回的窗口对象、或者是命名过或数值索引的window.frames
- message:将要发送到其他window的数据,类型为string或者Object
- targetOrign: 通过窗口的origin属性来指定哪些窗口能接受到消息事件,其值可以是字符串”*”(表示无限制)或者一个URL。
- transfer(可选):一串和message同时传递的Transferable对象。
- 接收message的属性有:
- data:从其他window中传递过来的数据
- origin:调用postMessage时消息发送方窗口的origin
- source:对发送消息的窗口对象的引用
- 示例如下:域名http://127.0.0.1:9000页面A通过iframe嵌入了http://127.0.0.1页面B,接下来页面A将通过postMessage对页面B进行数据传递,页面B将通过message属性接收页面A的数据
- 页面A发送消息代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>页面A</title>
</head>
<body>
<h1>hello jsonp</h1>
<iframe src="http://127.0.0.1/b.html" id="iframe"></iframe>
</body>
</html>
<script>
window.onload = function() {
var $iframe = document.getElementById('iframe');
var targetOrigin = "http://127.0.0.1";
$iframe.contentWindow.postMessage('postMessage发送消息', targetOrigin);
};
</script>
- 页面B接收消息代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>页面B</title>
</head>
<body>
<h1>hello jsonp</h1>
</body>
</html>
<script>
var onmessage = function (event) {
var data = event.data; //消息
var origin = event.origin; //消息来源地址
var source = event.source; //源Window对象
if(origin === "http://127.0.0.1:9000"){
console.log(data, origin, source);
}
};
// 事件兼容简单处理
if (window.addEventListener) {
window.addEventListener('message', onmessage, false);
}
else if (window.attachEvent) {
window.attachEvent('onmessage', onmessage);
}
else {
window.onmessage = onmessage;
}
</script>
- 运行结果如下:
location.hash + iframe(适用于两个iframe之间)
- 对于location.hash,先看一张图:
- location.hash可以用来获取或者设置页面的标签值,如127.0.0.1:9000/#…,它的location.hash值则为“#hello”。它一般用于浏览器锚点定位,HTTP请求中却不会携带hash,所以这部分的修改不会产生HTTP请求,但是会产生浏览器历史记录,这对我们进行跨域通信给予了帮助。我们可以通过修改URL的hash部分来进行双向通信。
示例如下:域名http://127.0.0.1:9000页面A通过iframe嵌入了http://127.0.0.1页面B,接下来页面A和页面B将通过location.hash进行双向通信。- 页面A代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>页面A</title>
</head>
<body>
<iframe src="http://127.0.0.1/bop/test.html#locationHash" id="ifr"></iframe>
</body>
</html>
- 页面B代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>页面B</title>
</head>
<body>
<h1>hello localtionHash</h2>
</body>
</html>
<script>
try {
parent.location.hash = 'data';
} catch (e) {
// ie、chrome的安全机制无法修改parent.location.hash,所以要借助于父窗口域名下的一个代理iframe
var $ifrproxy = document.createElement('iframe');
$ifrproxy.style.display = 'none';
// 注意proxy.html必须是域名为 http://127.0.0.1:9000 下的页面
$ifrproxy.src = "http://127.0.0.1:9000/proxy.html#locationHashChange";
document.body.appendChild($ifrproxy);
}
</script>
- 代理页代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Proxy页面</title>
</head>
<body>
</body>
<script>
// 因为parent.parent和自身属于同一个域,所以可以改变其location.hash的值
parent.parent.location.hash = self.location.hash.substring(1);
</script>
</html>
- 运行结果如图