跨域的三种方式

什么是同源

若两个url的域名 + 协议 + 端口号都相同,那么称这两个url同源。非同源的客户端脚本在没有明确授权的情况下,不能读写对方资源,在请求数据时,浏览器会在控制台中报一个异常,提示拒绝访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
http://store.company.com/dir/page.html

同源、不同路径:
http://store.company.com/dir/other.html
http://store.company.com/dir/inner/another.html

协议不同:
https://store.company.com/secure.html

域名不同:
http://news.company.com/dir/other.html

端口不同:
http://store.company.com:81/dir/etc.html

什么是跨域

跨域指不同源之间的资源访问,只要请求的url在域名、协议或端口有不同,那么就属于跨域。跨域限制是浏览器自带的安全机制,只有在浏览器上发生跨域请求时,就会抛出错误。

JSONP(JSON+padding)

  • 使用场景:当前浏览器不支持CORS(一般是IE),需要别的方式实现跨域
  • JSONP的实现逻辑:通过请求一个js文件(动态添加script标签),这个js文件中有一个回调函数,回调函数中包含了我们需要的数据。回调函数的名字可以是随机生成的一个随机数,前端需要将这个回调函数的名字以callback的参数传回给后台,后台会将这个函数返回并执行
  • 优点:作用是跨域且支持IE浏览器
  • 缺点:不能像ajax那样知道精确的状态(不知道状态码、响应头),只知道请求是成功或失败(因为函数监听的是onload和onerror事件);另外script标签的src属性只支持GET不支持POST

下面是一个模拟的过程:

请求方:frank.com 的前端程序员(浏览器)

响应方:jack.com 的后端程序员(服务器)

1、请求方创建 script 标签,src 指向响应方,同时传一个查询参数 ?callbackName=yyy

2、响应方根据查询参数callbackName,构造形如

  • yyy.call(undefined, ‘你要的数据’)

  • yyy(‘你要的数据’)

这样的响应

3、浏览器接收到响应,就会执行 yyy.call(undefined, ‘你要的数据’)

4、那么请求方就知道了他要的数据

示例

HTML 中 script 标签可以加载其他域下的js,比如我们经常引入一个其他域下线上cdn的jQuery

1
<script src="http://api.jirengu.com/weather.php"></script>

这时候会向天气接口发送请求获取数据,获取数据后做为 js 来执行。 但这里有个问题,数据是 JSON 格式的数据,直接作为 JS 运行的话我如何去得到这个数据来操作呢?

于是我们添加一个callback的参数

1
<script src="http://api.jirengu.com/weather.php?callback=showData"></script>

这个请求到达后端后,后端会去解析callback这个参数获取到字符串showData,在发送数据做如下处理:

之前后端返回数据是: {“city”: “hangzhou”, “weather”: “晴天”} 现在后端返回数据: showData({“city”: “hangzhou”, “weather”: “晴天”}) 前端script标签在加载数据后会把 showData({“city” “hangzhou”, “weather”: “晴天”})做为 js 来执行,这实际上就是调用showData这个函数,同时参数是 {“city” “hangzhou”, “weather”: “晴天”}。 用户只需要提前在页面定义好showData这个全局函数,在函数内部处理参数即可。

1
2
3
4
5
6
<script>
function showData(ret){
console.log(ret);
}
</script>
<script src="http://api.jirengu.com/weather.php?callback=showData"></script>

前后端完整示例

前端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
button.addEventListener('click', (e)=>{
let script = document.createElement('script')
let functionName = 'frank'+ parseInt(Math.random()*10000000 ,10)
window[functionName] = function(){ // 每次请求之前搞出一个随机的函数
amount.innerText = amount.innerText- 1
}
script.src = '/pay?callback=' + functionName
document.body.appendChild(script)
script.onload = function(e){ // 状态码是 200~299 则表示成功
e.currentTarget.remove()
delete window[functionName] // 请求完了就干掉这个随机函数
}
script.onerror = function(e){ // 状态码大于等于 400 则表示失败
e.currentTarget.remove()
delete window[functionName] // 请求完了就干掉这个随机函数
}
})

后端代码,不用看

1
2
3
4
5
6
7
8
9
10
11
12
13
...
if (path === '/pay'){
let amount = fs.readFileSync('./db', 'utf8')
amount -= 1
fs.writeFileSync('./db', amount)
let callbackName = query.callback
response.setHeader('Content-Type', 'application/javascript')
response.write(`
${callbackName}.call(undefined, 'success')
`)
response.end()
}
...

jQuery的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$(Button).on('click', function () {
$.ajax({
url: "http://jack.com:8002/pay",
dataType: "jsonp",
success: function (response) {
if (response === 'success') {
// alert('前端写的代码')
}
},
error: function () {
alert('fail');
}
});
})

CORS跨域

跨源资源分享(Cross-Origin Resource Sharing),是一种ajax跨域请求资源的方式。实现方式很简单,当你使用XMLHttpRequest发送请求时,浏览器发现该请求不符合同源策略,会给该请求加一个请求头:Origin,后台进行一系列处理,如果确定接受请求则在返回结果中加入一个响应头:Access-Control-Allow-Origin; 浏览器判断该相应头中是否包含 Origin 的值,如果有则浏览器会处理响应,我们就可以拿到响应数据,如果不包含浏览器直接驳回,这时我们无法拿到响应数据。所以 CORS 的表象是让你觉得它与同源的 ajax 请求没啥区别,代码完全一样。

1
2
response.setHeader('Access-Control-Allow-Origin','http://jayce.com:8003')
response.setHeader('Access-Control-Allow-Origin','*') // *代表允许所有浏览器访问

postMessage

作用:用于网页间通讯
语法:otherWindow.postMessage(message, targetOrigin);

  • otherWindow:指目标窗口,也就是给哪个window发消息,是 window.frames 属性的成员或者由 window.open 方法创建的窗口

  • message: 是要发送的消息,类型为 String、Object

  • targetOrigin: 是限定消息接收范围,可以是*(无限制)或者一个URL

例如父窗口 http://aaa.com 向子窗口 http://bbb.com 发送消息

1
2
var popup = window.open('http://bbb.com', 'title');
popup.postMessage('Hello World!', 'http://bbb.com');

子窗口向父窗口发送消息也是类似

1
window.opener.postMessage('Nice to see you', 'http://aaa.com');

0%