IT技术博客大学习 共学习 共进步
全部 移动开发 后端 数据库 AI 算法 安全 DevOps 前端 设计 开发者

常用跨域方法实践(二)

风影博客 2016-02-20 16:56:08 累计浏览 3,297 次
本机暂存

本文承接上一篇博文,上一篇博文介绍了跨域的相关概念、测试demo的相关配置和JSONP和CORS两种跨域方式的实现。本文主要介绍document.domain、URL.hash、cross-fragment、window.name和postMessage这五种方式的跨域实现。

document.domain

如果A源和B源具有相同的父域名,通过设置document.domain属性,就很容易使其相互通信。在 HTML 规范中document.domain是一个只读属性,现代浏览器允许将其设置为父域名(不是顶级域名)。例如,一个是www.myapp.com的页面,可以设置为myapp.com,而另一个来自sample.myapp.com的页面也可以设置为myapp.com,下图展示了document.domain的工作原理:

document-domain

通过将不同子域名的document.domain属性设置为相同的父域名,来实现不同子域名之间的跨域通信,这并不属于同源策略限制的范畴。但是,严格来说,子域名跨域的解决方案最适用于内部应用之间的跨域通信。

访问页面:document_domain_test.ejs

  <button>测试</button>
  <div></div>
  <iframe id="ifr" src="http://sample.myapp.com/document_iframe"></iframe>

  <script>
      document.domain = "myapp.com";
      function sayHello(str) {
        document.querySelector('div').innerHTML = str;
      }
      document.querySelector('button').onclick = function() {
        document.querySelector('#ifr').contentWindow.sayHello('Hello son');
      }
  </script>

跨域iframe:document_iframe.ejs

document.domain = 'myapp.com';
    function sayHello(str) {
        document.querySelector('div').innerHTML = str;
        parent.sayHello('Hello father');
    }

效果:

document-domain-show

URL.hash

一个URL由下图所示的几个部分组成:

url

一般来说,URL的任何改变都重新会加载一个新的网页,除了hash的变化,hash的任何改变都不会导致页面刷新。hash已经被广泛使用在支持局部刷新的SPA应用中,用来记录用户的访问路径。在跨域解决方案中,hash也非常有用,来自不同源的页面可以相互设置对方的URL,包括hash值,但仍被限制获取对方的hash值。文档之间可以通过hash来相互通信。如下图所示的例子:

fragment

访问页面:url_hash_test.ejs

  <button>发送消息</button>
  <iframe id="ifr" src="http://www.otherapp.com/hash_iframe"></iframe>
  <script>
    var target = "http://www.otherapp.com/hash_iframe";
    function sendMsg(msg) {
      var data = {msg: msg};
      var src = target + "#" + JSON.stringify(data);
      document.getElementById('ifr').src = src;
    }
    document.querySelector('button').onclick = function() {
      sendMsg("Time: " + (new Date()));
    }
  </script>

跨域iframe:hash_iframe.ejs

  <div></div>
  <script>
    var oldHash = "";
    var target = "http://www.myapp.com/url_hash_test";
    var checkMessage = function() {
      var newHash = window.location.hash;
      if (newHash.length > 1) {
        newHash = newHash.slice(1, newHash.length);
        if (newHash !== oldHash) {
          oldHash = newHash;
          var msgs = JSON.parse(newHash);
          var msg = msgs.msg;
          sendMessage("Hello-father");
          document.querySelector('div').innerHTML = msg;
        }
      }
    }
    setInterval(checkMessage, 1000);
    function sendMessage(msg) {
      var hash = "msg=" + msg;
      parent.location.href = target + "#" + hash;
    }
  </script>

主页面通过改变iframe的hash值向iframe传递参数,iframe不断获取自己的hash值,一旦发生变化就立即显示主页面传来的消息,并且通过设置主页面的hash就可以像主页面传递消息了,这样实际就可以完成双向的跨域通信了。

效果:

url-hash

Cross-fragment

由于许多网站的hash已经被用于其他用途,对于这样的网站用hash跨域将非常复杂(需要从hash中合并和分离出消息)。因此我们就有了基于hash的一个升级版:cross-fragment,其原理如下图所示:

cross-fragment

访问页面:cross_fragment_test.ejs

  <button>发送消息</button>
  <div></div>
  <iframe id="otherapp" name="otherapp" src="http://www.otherapp.com/fragment_iframe"></iframe>

  <script>
    function sendMsg(msg) {
      var frame = document.createElement("iframe");
      var baseProxy = "http://www.otherapp.com/fragment_req_proxy";
      var request = {frameName: "otherapp", data: msg};
      frame.src = baseProxy + "#" + encodeURI(JSON.stringify(request));
      frame.style.display = "none";
      document.body.appendChild(frame);
    }
    // 响应处理函数
    function getResponse(data) {
      document.querySelector('div').innerHTML = "收到结果:" + data;
    }
    document.querySelector('button').onclick = function() {
      // 发送一个随机数
      sendMsg(Math.random());
    }
  </script>

请求代理页面:fragment_req_proxy.ejs

  <script>
    var hash = window.location.hash;
    if (hash && hash.length > 1) {
      var request = hash.slice(1, hash.length);
      var obj = JSON.parse(decodeURI(request));
      var data = obj.data;
      //目标页面的getData方法
      parent.frames[obj.frameName].getData(data);
    }
  </script>

跨域iframe:fragment_iframe.ejs

  <div></div>
  <script>
    function getData(data) {
      document.querySelector('div').innerHTML = "收到的消息:" + data;
      var frame = document.createElement("frame");
      var baseProxy = "http://www.myapp.com/fragment_res_proxy";

      // 将接收的数据扩大一百倍
      var request = {data: data * 100};
      frame.src = baseProxy + "#" + encodeURI(JSON.stringify(request));
      frame.style.display = "none";
      document.body.appendChild(frame);
    }
  </script>

响应代理页面:fragment_res_proxy.ejs

  <script>
    var hash = window.location.hash;
    if (hash && hash.length > 1) {
      var request = hash.slice(1, hash.length);
      var obj = JSON.parse(decodeURI(request));
      var data = obj.data;
      //主页面的getResponse方法
      parent.parent.getResponse(data);
    }
  </script>

这个例子中先在主页面(来自myapp)放置一个otherapp下的iframe(目标页面),点击按钮后就在主页面中构造一个隐藏的iframe(和目标页面同域,来自otherapp,请求代理页面),通过这个请求代理页面调用目标页面的getData方法,这个方法接收传来的数据,处理完成后,构造一个隐藏的iframe(和主页面同域,来自myapp,响应代理页面),通过响应代理页面调用主页面中的getResponse方法。

效果:

cross-fragment-show

之前整理过一个利用该方法实现iframe自适应的博文,有兴趣的可以看一下

window.name

window.name跨域是一个巧妙的解决方案,我们一般使用window.name 的情况如下:

  • 使用window.frames[windowName]得到一个子窗口

  • 将其设置为链接元素的target属性

加载任何页面window.name的值始终保持不变。由于window.name这个显著的特点,使其适用于在不同源之间进行跨域通信,但这是个不常用的属性。那么怎么在同源策略下使用呢?下图显示了如何使用window.name来跨域通信。

window-name

当页面A想要从另一个源获取资源或Web服务,首先在自己的页面上创建一个隐藏的 iframe B,将 B 指向外部资源或服务,B 加载完成之后,将把响应的数据附加到 window.name上。由于现在 A 和 B 还不同源,A 依旧不能获取到 B 的name属性。当 B 获取到数据之后,再将页面导航到任何一个与 A 同源的页面,这时 A 就可以直接获取到 B 的name属性值。当 A 获取到数据之后,就可以随时删掉 B。

访问页面:window_name_test.ejs

  <button>发送消息</button>
  <div class="req"></div>
  <div class="res"></div>
  <script>
    function sendMsg(msg) {
      var state = 0, data;
      var frame = document.createElement("frame");
      var baseProxy = "http://www.otherapp.com/window_iframe";
      var request = {data: msg};
      frame.src = baseProxy + "#" + encodeURI(JSON.stringify(request));
      frame.style.display = "none";
      frame.onload = function() {
        if (state === 1) {
          data = frame.contentWindow.name;
          document.querySelector('.res').innerHTML = "获得响应:" + data;
          // 删除iframe
          frame.contentWindow.document.write('');
          frame.contentWindow.close();
          document.body.removeChild(frame);
        } else {
          state = 1;
          frame.src = "http://www.myapp.com/window_iframe";
        }
      };
      document.body.appendChild(frame);
    }
    document.querySelector('button').onclick = function() {
      var val = Math.random();
      sendMsg(val);
      document.querySelector('.req').innerHTML = "请求数据:" + val;
    }
  </script>

跨域iframe:window_iframe.ejs

  <script>
    var hash = window.location.hash;
    if (hash && hash.length > 1) {
      var request = hash.slice(1, hash.length);
      var obj = JSON.parse(decodeURI(request));
      var data = obj.data;
      window.name = data*100;
    }
  </script>

效果:window-name-show

postMessage

HTML5 规范中的新方法window.postMessage(message, targetOrigin)可以用于安全跨域通信。当该方法被调用时,将分发一个消息事件,如果窗口监听了相应的消息,窗口就可以获取到消息和消息来源。如下图所示:

post-message

如果iframe想要通知不同源的父窗口它已经加载完成,可以使用window.postMessage来发送消息。同时,它也将监听回馈消息。

访问页面:postMessage_test.ejs

  <div></div>
  <iframe name="otherApp" id="otherApp" src="http://www.otherapp.com/postMessage_iframe"></iframe>
  <script>
    function handleReceive(event) {
      if (event.origin != "http://www.otherapp.com") return;
      // 处理数据
      var data = JSON.parse(event.data);
      document.querySelector('div').innerHTML = "来自iframe的颜色:" + data.color;
      var otherAppFrame = window.frames["otherApp"];
      otherAppFrame.postMessage("ok", "http://www.otherapp.com");
    }
    window.addEventListener("message", handleReceive, false);
  </script>

跨域iframe:postMessage_iframe.ejs

  <div></div>
  <script>
    function postMessage(msg) {
      var targetWindow = parent.window;
      targetWindow.postMessage(msg, "*");
    }
    function handleReceive(msg) {
      if (msg.data === "ok") {
        document.querySelector('div').innerHTML = "消息发送成功";
      } else {
        postMessage(JSON.stringify({color: 'red'}));
      }
    }
    window.addEventListener("message", handleReceive, false);
    window.onload = function() {
      postMessage(JSON.stringify({color: 'red'}));
    }
  </script>

效果:

postMessage-show

结语

本文介绍了常用的五种跨域实现方法,测试源码请戳这

参考

同分类推荐文章

  1. translateZ() (2026-06-25 21:18:56)
  2. translateY() (2026-06-25 21:17:56)
  3. translateX() (2026-06-25 21:16:01)

查看更多 前端 文章 →

建议继续学习

  1. JSONP与POST方式请求 (累计阅读 11,294)
  2. JSON和JSONP的区别 (累计阅读 8,468)
  3. 你不知道的 HTTP (累计阅读 6,512)
  4. jQuery中getJSON跨域原理详解 (累计阅读 6,351)
  5. 利用跨域资源共享(CORS)实现ajax跨域调用 (累计阅读 5,665)
  6. 研究ext发现ajax跨域实现 (累计阅读 4,826)
  7. jQuery.ajax处理302重定向 (累计阅读 4,719)
  8. Ajax和WEB服务数据格式:JSON JSONP (累计阅读 4,538)
  9. 跨域请求的iframe解决方案(2) (累计阅读 4,278)
  10. 使用html5 postMessage和window.name实现多浏览器跨域 (累计阅读 3,680)