如何基于 iframe 解决跨域?

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第10天,点击查看活动详情

一般跨域听得比较多的方案是 Nginx 代理,CORS,而 JSONPiframe 的跨域解决往往只在背八股文的时候出现,而且老是只给 JSONP 的实际操作手段,老是找不着 iframe 的实际操作,所以这篇文章就是介绍如何基于 iframe 解决跨域

您可以在线查看完整的示例源代码

跨域

什么是跨域,跨域定义太多了,我直接介绍哪些场景会有跨域,当请求路径和当前页面路径存在以下情况

协议不同,域名不同,端口不同

location.hash

阅前注意

  1. http://localhost:3001: server 服务端
  2. http://localhost:3000: client 客户端

这个方法主要是使用的 location.hash 去通信,iframesrc 属性不受跨域限制和中间页面解决(vue-router 也有用到 location.hash

解决方案如下,需要跨域的页面,简称 client.html

<!-- client.html -->
<body>
  <button onclick="getUsers()">请求</button>
</body>
<script>
  const getUsers = () => {
    let clientIframe = document.getElementById("clientIframe");
    if (!clientIframe) {
      clientIframe = document.createElement("iframe");
      clientIframe.id = "clientIframe";
      clientIframe.style.display = "none";
      clientIframe.src = "http://localhost:3001/data.html/#users";
      document.body.appendChild(clientIframe);
    } else {
      clientIframe.src = "http://localhost:3001/data.html/#users";
    }

    window.addEventListener("hashchange", function (e) {
      console.log("返回响应", location.hash.substring(1));
    });
  };
</script>

客户端封装一个包裹 iframe 的请求,方便重复请求

注:考虑到浏览器创建 DOM 的消耗过高,因此采用单一实例,有部分文章采用的是重复创建,由于作者眼界有限,如有错误望在评论区指出

其中 http://localhost:3001/data.html 是服务端提供的文件,由于和服务端同属一个域,因此在其中的请求不受跨域限制

<!-- http://localhost:3001/data.html -->
<body></body>
<script>
  // 初次加载
  switch (location.hash) {
    case "#users":
      callback("users");
      break;
  }

  // 重复调用
  window.addEventListener("hashchange", function (e) {
    switch (location.hash) {
      case "#users":
        callback("users");
        break;
    }
  });

  /**
   * iframe 跨域回调
   * @param {string} path 请求路径
   */
  function callback(path) {
    try {
      const xhr = new XMLHttpRequest();
      xhr.open("GET", `http://localhost:3001/${path}`);
      xhr.onreadystatechange = () => {
        if (xhr.readyState === 4) {
          if (xhr.status === 200) {
            // do something
          }
        }
      };
      xhr.send();
    } catch (e) {
      console.log(e);
    }
  }
</script>

内嵌 iframe 在请求到数据以后需要怎样和客户端通信呢?

http://localhost:3001/data.htmlhttp://localhost:3000 通信难题

这个问题的解决方案其实就是通过 location.hash,每一个 iframe 拥有一个属性 parent

如果当前窗口是一个 <iframe><object>, 或者 <frame>,则它的父窗口是嵌入它的那个窗口 – window.parent – MDN

但注意,由于 ie, chrome 的安全机制我们无法修改此处的 http://localhost:3001/data.htmlparent.location.hash,因此需要借助一个中间页面,以 http://localhost:3000/proxy.html 代称,根据跨域定义,它和 http://localhost:3000 同源,下面给张图捋一捋三个 html 的关系

IMG

proxy.html 内容如下

<body></body>
<script>
  // 初次加载
  parent.parent.location.hash = self.location.hash.substring(3);

  // 重复调用
  window.addEventListener("hashchange", function (e) {
    parent.parent.location.hash = self.location.hash.substring(3);
  });
</script>

http://localhost:3000/proxy.htmllocation.hashhttp://localhost:3000/location.hash 是一样的

IMG

所以在 data.html 中的 do something 修改成以下内容

<!-- http://localhost:3001/data.html -->
<body></body>
<script>
// ...
          if (xhr.status === 200) {
            // ie, chrome 下的安全机制无法修改 parent.location.hash
            // 所以要利用一个中间的代理 iframe
            let proxyIframe = document.getElementById("proxyIframe");
            if (!proxyIframe) {
              proxyIframe = document.createElement("iframe");
              proxyIframe.id = "proxyIframe";
              proxyIframe.style.display = "none";
              proxyIframe.src =
                "http://localhost:3000/proxy.html#" +
                Math.floor(Math.random() * 100) +
                xhr.response;
              document.body.appendChild(proxyIframe);
            } else {
              proxyIframe.src =
                "http://localhost:3000/proxy.html#" +
                Math.floor(Math.random() * 100) +
                xhr.response;
            }
            location.hash = "";
          }
// ...
</script>

响应返回加上随机数的原因是为了 proxy.html 能够触发 hashchange 事件,注意,前面说过http://localhost:3000/proxy.htmllocation.hashhttp://localhost:3000/location.hash 是一样的,但在此处修改 proxyIframesrc 是不会影响 proxy.htmllocation.hash 同时也能触发 hashchange 事件,除此之外,客户端也需要按约定过滤随机数,即 self.location.hash.substring(3)

效果如下

29.gif

总结

你可以不懂但是不可以不会,该用到 JSONPiframe 的时候你就可以拥有这个方向的思路,就拿 JSONP 来说,我以前一直以为是无用的八股,但后面实习时遇到一个公共接口不能使用 CORS 也不能使用 Nginx,想知道原因?原因如下

  1. CORS: 公司老业务,运行很久很稳定,开发人员跑路了,只有上古文档,而且非同组沟通困难
  2. Nginx: 不能直接上,需要知道接口的具体部署地址和库,而且有引用鉴权,同组对接后端尝试自行补写 Referrer,但最终失败

后面使用的 JSONP 解决,而 iframe 我想也有它合适的适用场景,就像这篇文章中提到的利用 iframe 引用公共视频播放器

参考资料

  1. 浅析api跨域的三种方案及iframe跨域的四种方案对比 – 古兰精 – 博客园
  2. iframe跨域的几种常用方法 – SugarTurboS
© 版权声明
THE END
喜欢就支持一下吧
点赞6 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容