背景
应用服务平台集成监控运维平台的监控看板,监控运维平台按照应用服务场景构建看板,使用Iframe嵌入应用服务平台。
基础环境
- 应用服务平台:paas.server.com
- 监控运维平台:paas.blueking.com
轻松开篇
// 引入iframe标签
<iframe id="monitor-iframe" class="grafana-wrap-frame" allow="fullscreen" :src="url" frameborder="0"></iframe>
// 设置url
url: 'http://paas.blueking.com/monitorv3/grafana/d/QgFGIJqIz/fitosyun-zhu-ji?var-object_id=123'
白屏问题
大大的白屏,受限于安全限制,https协议下不允许嵌入http协议的页面。
Mixed Content: xxx This request has been blocked; the content must be served over HTTPS.
如何解决呢?按照证书方式升级http到https显然动作太大,不现实。尝试在升级页面的head中加入meta自动将http的不安全请求升级为https。
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
再次尝试还是无效果。
白屏场景2
另一种白屏场景是子页面系统不允许以iframe方式内嵌。在nginx中配置X-Frame-Options和Content-Security-Policy "frame-ancestors"来开放可被嵌入的域名。
最终决定使用同源策略来规避这些安全限制。
跨域问题
跨域(Cross-Origin)指的是浏览器阻止前端网页从一个域名(Origin)向另一个域名的服务器发送请求。具体来说,一个页面的协议、域名、端口三者任意一个与请求的目标地址不同,就被视为跨域请求。
同源方案
同源策略的定义:如果两个URL的协议、域名和端口都相同,则这两个URL具有相同的源。
应用方案即用应用服务平台的 [paas.server.com] 做层代理来访问监控运维平台的监控看板。如下去掉域名前缀,默认将使用应用服务平台的域名访问。这样打破了所有跨域带来的安全限制。
// 设置url
url: '/monitorv3/grafana/d/QgFGIJqIz/fitosyun-zhu-ji?var-object_id=123'
//配置nginx
location /monitorv3/ {
proxy_pass http://paas.blueking.com/monitorv3/;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 600s;
}
认证打通
嵌入是成功了,但是认证体系是两套,嵌入后内部页面会重定向到监控运维平台的登录页面。需要模拟登录来完成认证打通。iframe中设置Cookies也依赖于同源策略
- 在应用服务中模拟调用监控运维的登录api,拿到
token - 将token按照监控运维的规范设置进
Cookies
import Cookies from 'js-cookie';
getBkToken().then(res => {
Cookies.set('bk_token', res);
this.isLogin = true;
}).catch((err) => {
// todo
});
Token过期问题
当然还有token过期问题,两个系统的过期不同步,可能存在父平台还未过期,子系统过期场景,不过问题不大,拉长过期时间可抵消出现频率。
双滚动条问题
父页面和iframe中子页面同时存在纵向滚动条,显示交互存在瑕疵。出现这种问题的原因是子页面构建完成高度能撑多高并不确定,父页面起始设置的高度不能确定导致出现Y轴滚动条。解决思路也就两种:
保留父页面滚动条,此方案使用所用场景,把交互方式控制在自己手里保留子页面滚动条,此方案适合界面相对简单纯净的场景
方案一
分同源和不同源场景,不同源情况下可以使用postMessage api 建立双向通讯,在子页面渲染完成后把最终高度告知父页面并动态设置给父容器,消除子页面中滚动条。同源场景相对简单,采用postMessage通讯方式,或者直接获取子页面iframe中DOM 对象的高度设置。当然也可以不通讯,适当延迟动态获取高度动态设置也可行。
const iframe = document.getElementById('monitor-iframe');
if (iframe.contentWindow || iframe.contentDocument) {
const doc = iframe.contentDocument || iframe.contentWindow.document;
const elem = doc.querySelector('.react-grid-layout');
if (elem) {
setIframeHeight(elem.offsetHeight + 32);
setLoading(false);
}
} else {
setLoading(false);
console.log('Cannot access iframe contentWindow or contentDocument');
}
方案二
设置样式,保证父容器不产生Y轴滚动条即可。此方案适合界面相对简单纯净的场景,一些局部内嵌 + 其它业务布局组合的场景,高度溢出父容器避免不了Y轴滚动条场景。
后记
Iframe应用最关键的因素就是同源,突破浏览器安全限制。其次就是把显示、交互、通讯控制在自己手里,才能优雅内嵌。