上接《再谈风骚的跨源/域方案(昔日篇)》,本篇聊聊现代标准(HTML5之后)的跨源方案。
基础概念都在昔日篇中,初学者请务必先看完昔日篇。
配套的演示案例传送门。
本人个人能力有限,欢迎批评指正。
该方案使用了 HTML5 新的 window.postMessage
接口,该方法是专门为不同源页面通信设计的,是一个经典的“订阅-通知”模型。
该方案原理与昔日篇的“子域代理”很相似,都是主页面用 iframe 内非同源子页面作为代理去跟服务端交互获取数据。不同之处在于,“子域代理”需要通过修改 document.domain
使主页面获取子页面 document 操作权限,而 window.postMessage
已经原生提供了主页面与子页面通信的办法,故仅需要主页面通过 window.postMessage
向子页面下命令,子页面请求完成后再以此通知主页面即可实现跨源通信,换句话说子页面变成了一个类似转发服务的存在。
不需要修改 document.domain
也意味着摆脱了“子域代理”严格的域限制,可以更加自由的应用在第三方 API 上。 window.postMessage
是少有的不受同源限制的浏览器 API,准确来说是没有调用权限的限制而已,它对发送和接收的目标还是有严格限制的,这也是它安全性的体现。举个例子:
// 假设在 iframe 内页面进行订阅。
window.addEventListener('message', event => {
// 验证发送者,发送者不符合是可以不理会的。
if (event.origin !== 'http://demo.com') return
// 这就是发送过来的信息。
const data = event.data
// 这是发送者的 window 实例,可以调用上面的 postMessage 回传信息。
const source = event.source
})
// 主页面通知。
// 第二个参数是接收者的源,需要源完全匹配的页面才会接收到信息。(“源”的定义见昔日篇)
// 设置为 * 可以实现广播,不过一般不推荐。
iframe.contentWindow.postMessage('hello there!', 'http://demo.com')
iframe.contentWindow.postMessage
发送请求给代理页;event.source.postMessage
传递给主页面。前端
服务端
共享 iframe 的设计思路请参考昔日篇的“子域代理”。
前端“统一事件处理器”的设计思路:
function initMessageListener() {
// 保存回调对象的对象。
const cbStore = {}
// 设置监听,只需一个。
window.addEventListener('message', function (event) {
// 验证发送域。
if (event.origin !== targetOrigin) {
return
}
// ...
try {
// 运行失败分支。
if (...) {
cbStore[msgId].reject(new Error(...))
return
}
// 运行成功分支。
cbStore[msgId].resolve(...)
} finally {
// 执行清理。
delete cbStore[msgId]
}
})
// 这里形成了一个闭包,只能用特定方法操作 cbStore。
return {
// 设置回调对象的方法。
set: function (msgId, resolve, reject) {
// 回调对象包含成功和失败两个分支函数。
cbStore[msgId] = {
resolve,
reject
}
},
// 删除回调对象的方法。
del: function (msgId) {
delete cbStore[msgId]
}
}
}
// 初始化,每次请求都调用其 set 方法设置回调对象。
const messageListener = initMessageListener()
配合上面的“统一事件处理器”,msgId 其实没必要传递到服务端,在代理页处理即可:
window.addEventListener('message', event => {
// 验证发送域。
if (event.origin !== targetOrigin) {
return
}
// 这是主页面 postMessage 的数据。
// 其中 msgId 与“统一事件处理器”有关,其他参数与 Ajax 有关,按实际需要传递即可。
const { msgId, method, url, data } = event.data
// 发送 Ajax。
xhr(...).then(res => {
// 将 msgId 加入回传数据,其余保留原样。
res.response.data = {
...res.response.data,
msgId
}
// 回传给主页面。
event.source.postMessage(res, targetOrigin)
})
})
具体代码请参考演示案例 PostMessage 部分源码。
优点
缺点
CORS 全称 Cross-origin resource sharing ,是 W3C 组织制订的标准跨源方案(传送门),也可以说是跨源的官方终极解决方案,它让现代的 web 开发方便不少。
简单来说 CORS 是一套服务端与浏览器的协商机制,通过报文头实现,浏览器告知服务端来源(origin)和希望允许的方法,服务端返回“白名单”(也是一组报文头),浏览器依据“白名单”判断是否允许这次请求,可应用与 Ajax、canvas 等的跨源情况。
CORS 分为 简单请求(simple) 和 复杂请求(complex),他们最主要的区别就是需不需要预检(preflight)。
简单请求需要满足如下条件(只挑重点):
方法(method)为如下之一
只允许设置如下报文头(header)
Content-Type (只允许三个)
text/plain
multipart/form-data
application/x-www-form-urlencoded
不满足上面条件的都会被判定为复杂请求,就实际使用而言 form 发出的请求基本都是允许的,如果要使用 json 格式传递数据(即 Content-Type: application/json
),那必定是复杂请求。
复杂请求会先发出预检请求,也就是先问问看服务端,如果返回的“白名单”符合要求再会发起正式的请求。
预检请求是方法(method)为 OPTION 的请求,它不需要携带任何业务数据,仅依照需要发送 CORS 相关请求报文头给服务端,服务端也不需要响应任何业务数据,仅返回“白名单”,完成协商即可。
CORS 相关请求报文头
CORS 相关响应报文头(即“白名单”)
Vary: Origin
以免当 API 给不同源页面返回不同数据时,被缓存搞混;简单请求与一般的 Ajax 流程完全相同,仅需浏览器发送 Origin 请求报文头,服务端返回 Access-Control-Allow-Origin 响应报文头即可。
下面详讲复杂请求的情况。
假设现在网页源为 http://demo.com
,服务端 API 源为 http://api.demo.com
,需求请求的方法为 POST ,数据类型是 json,自定义报文头 token 。
发送预检 OPTION 请求,有关 CORS 的报文头设置如下:
Origin: http://demo.com
;Access-Control-Request-Method: POST
;Access-Control-Request-Headers: content-type, token
;服务器接收到预检请求进行响应,有关 CORS 的报文头设置如下:
Access-Control-Allow-Origin: http://demo.com
;Access-Control-Allow-Methods: POST, GET, OPTIONS
;Access-Control-Allow-Headers: Content-Type, token
;Vary: Origin
(上面有说明,它不属于 CORS 报文头,但必须)Origin: http://demo.com
,其余与正常请求一致;Access-Control-Allow-Origin: http://demo.com
和 Vary: Origin
,其余与正常响应一致;跨源相关的错误总体分两类。
前端
服务端
不建议无脑添加 CORS 相关响应报文头,要按需添加,以免造成头部冗余,参考上面的流程,可以大致可分为两组。
具体代码请参考演示案例 CORS 部分源码。
优点
缺点
我们在使用Dreamweaver的时候,底部的属性栏对我们设置网页的样式,有着很大的帮...
作者 Arghya Ghosh 翻译 New Frontend 转载推荐:项目无论是用于自己的应用,还...
作者:Daniel 译者:前端小智 来源:stackabuse 有梦想,有干货,微信搜索 【大...
xhtml+css网站重构web标准等等之类的文章太多了,我就不重复了,发个最简单的制...
前言 在一次偶然查看 PHP 文档的时候,发现了一些有趣的内容,随着阅读的增加,...
1. float+overflow:hidden 这种办法主要通过 overflow 触发 BFC,而 BFC 不会重叠...
PartialType 构造类型 Type ,并将它所有的属性设置为可选的。它的返回类型表示...
第一:CSS的4种引入方式 CSS的4种引入方式是:行内样式、内嵌样式、链接样式、导...
链接可以制作 WML 卡片来显示 WML 的锚功能,图像可以制作 WML 卡片来显示图像. ...
请放弃IDE中git的傻瓜式操作,下面通过移步换景的方式熟悉常用git命令,然后爱不...