同源策略里面的 源 指的是:协议、域名和端口。
只要这三者不同就会受到同源策略的影响,在浏览器控制台中,使用
console.log(location.origin)
,输出的结果便是 源。
IE
是一个例外:端口号并未加入到同源策略的组成部分之中。所以http://example.com:8080/
和http://example.com
属于同源并且不受任何限制。
常用方法
jsonp
- 服务端
b.com/index.php
:
<?php
$arr=['domain'=>'come from: '.$_SERVER['HTTP_HOST']];
$callback=$_GET['callback'];
$json=$callback."(".json_encode($arr).");";
echo $json;
- 客户端
a.com/index.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<title>jsonp</title>
<meta charset="UTF-8">
</head>
<body>
<script>
var url="http://b.com/index.php?callback=foo";
//注意 foo函数的定义要先于 引入 url里的内容
function foo(res){
console.log(res);
}
function loadData(url){
var elem=document.createElement('script');
elem.src=url;
document.getElementsByTagName('head')[0].appendChild(elem);
}
loadData(url);
</script>
</body>
</html>
加载 此页面a.com/index.html
,对 b.com/index.php
发起请求,后者将会对前者返回:
foo({domain: "come from: 182.254.152.115:8080"});
于是调用 a.com/index.html
中的 foo()
函数,a.com/index.html
页面所在的控制台输出:
{ domain: "come from: 182.254.152.115:8080" }
jsonp
的缺点:
1. 无法在post
请求类型中使用
2. 只能做到动态创建script
标签并读取数据而无法从客户端发出数据
CORS (XMLHttpRequest level 2)
XMLHttpRequest
无法跨源通信,但在XMLHttpRequest level2
中 只要服务器端许可就可以实现
- 服务端
b.com/index.php
<?php
header('Access-Control-Allow-Origin:http://a.com');
header('Access-Control-Allow-Credentials:true');
header('Access-Control-Allow-Methods:POST');
echo file_get_contents("php://input");
- 客户端
a.com/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>jsonp</title>
<meta charset="UTF-8">
</head>
<body>
<script>
function foo(res) {
console.log(res);
}
function xhr(url, params, callback) {
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.withCredentials = true;
var headerType = typeof params==='string' ? 'application/x-www-form-urlencoded' : 'multipart/form-data';
xhr.setRequestHeader('Content-Type', headerType);
if (callback) {
xhr.onreadystatechange = function () {
if (xhr.readyState !== 4) {
return;
}
callback(xhr.responseText || '');
};
}
xhr.send(params);
}
xhr('http://b.com/index.php', 'id=9999', foo);
</script>
</body>
</html>
加载 此页面a.com/index.html
,对 b.com/index.php
发起请求,后者将会对前者返回:
id=9999
于是调用 a.com/index.html
中的 foo()
函数,a.com/index.html
页面所在的控制台输出:
id=9999
postMessage()
语法:
originWindow.postMessage(message, targetOrigin);
originWindow
:要发起请求的窗口引用
message
: 要发送出去的消息,可以是字符串或者对象
targetOrigin
: 指定哪些窗口能够收到消息,其值可以是字符串*
(表示无限制)或者一个确切的URI
只有目标窗口的协议、主机地址或端口这三者全部匹配才会发送消息。
- 父窗口
b.com/index.html
<!DOCTYPE html>
<html>
<head>
<title>Parent window</title>
</head>
<body>
<div>
<iframe id="child" src="http://a.com/index.html"></iframe>
</div>
<input id="p_input" type="txet" value="0">
<button onclick='p_add()'>parent click to add</button>
<script type="text/javascript">
window.addEventListener('message',function(e){
document.getElementById('p_input').value = e.data;
});
function p_add(){
var p_input = document.getElementById('p_input');
var value = +p_input.value+1;
p_input.value= value;
window.frames[0].postMessage(value,'http://a.com');
}
</script>
</body>
</html>
- 子窗口
a.com/index.html
<!doctype html>
<html>
<head>
<title>Child window</title>
</head>
<body>
<input id="c_input" type="text" value="0">
<button onclick='c_add()'>child click to add</button>
<script type="text/javascript">
var c_input=document.getElementById('c_input');
window.addEventListener('message', function(e) {
if(e.source!=window.parent) return;
var value = +c_input.value+1;
c_input.value = value;
});
function c_add(){
var value = +c_input.value+1;
c_input.value = value;
window.parent.postMessage(value,'http://b.com');
}
</script>
</body>
</html>
beacon (ping)
ping
协议其实是一种很古老的跨域方法。它的本质是利用了浏览器可以获取不同域名下图片的原理,把请求参数放在了图片地址的URL
参数中发给后端。
- 客户端
a.com/index.html
<!DOCTYPE html>
<html>
<head>
<title>Post Message</title>
</head>
<body>
<script type="text/javascript">
function foo() {
console.log('ok');
}
function ping(url, callback) {
var img = new Image();
var done = function () {
// 图片实例不需要插入到页面DOM树中,避免了返回图片展示出来被用户看到
img.onload = img.onerror = img.onabort = null;
img = null;
if (callback) {
callback();
}
};
img.onload = img.onerror = img.onabort = done;
img.src = url;
}
ping('http://b.com/index.php?id=9078', foo);
// 通过200ms的JS死循环来延迟浏览器关闭,为发送ping请求争取到更多的时间,确保请求发送成功
var end;
var delay = 200;
var now = +new Date();
for (end = now + delay; now < end;) {
now = +new Date();
}
</script>
</body>
</html>
- 服务端
b.com/index.php
<?php
header("HTTP/1.1 204 No Content");
submit协议
适合大数据量或者文件的请求发送,需要浏览器支持
FormData
- 客户端
a.com/index.html
<!DOCTYPE html>
<html>
<head>
<title>Post Message</title>
</head>
<body>
<form id="form">
<input type="text" name="text1" value="1">
<input type="text" name="text2" value="2">
<input type="submit">
</form>
<script type="text/javascript">
function foo() {
console.log('ok');
}
function xhr(url, params, callback) {
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.withCredentials = true;
var headerType = typeof params==='string' ? 'application/x-www-form-urlencoded' : 'multipart/form-data';
xhr.setRequestHeader('Content-Type', headerType);
if (callback) {
xhr.onreadystatechange = function () {
if (xhr.readyState !== 4) {
return;
}
callback(xhr.responseText || '');
};
}
xhr.send(params);
}
var formElement = document.getElementById('form');
formElement.onsubmit = function (e) {
// 提交时改为XHR提交
e.preventDefault();
var formData = new FormData(formElement);
xhr('http://b.com/index.php', formData, foo);
};
</script>
</body>
</html>
- 客户端
b.com/index.php
<?php
header('Access-Control-Allow-Origin:http://a.com');
header('Access-Control-Allow-Credentials:true');
header('Access-Control-Allow-Methods:POST');
如何选择
_ | jsonp | CORS | postMessage | beacon | submit |
---|---|---|---|---|---|
提交的数据量 | 大 | 大 | 少(2083字符) | 少(2048字符) | 大 |
支持文件上传 | 不支持 | 不支持 | 不支持 | 不支持 | 支持 |
页面关闭时发送 | 不支持 | 不支持 | 不支持 | 可以支持(手动设置死循环,锁定浏览器) | 不支持 |
浏览器支持程度 | 支持度良好 | 支持度良好 | 部分支持(必须支持 postMessage 的浏览器) | 支持度良好 | 部分支持(必须支持 FormData 的浏览器) |
从表中可以看出来:
JSONP
协议主要适合于拉取数据、获取配置等不需要大数据量提交的操作;
beacon
协议则更适用于不要关心返回结果的少数据量提交,尤其适合在页面关闭是发送数据;
submit
协议则适用于大数据量的提交,尤其适合文件上传;