跨域问题再研究

前言

之前在查漏补缺第一波的是时候发现对这个知识点有点小疑惑,因为有几个列出的项之前并没怎么接触过,因此在这篇博文中将深入学习。

1.jsonp
2.iframe

jsonp

对于jsonp格式应该都不陌生,我之前在印象笔记有记录,如下:

$.ajax({
  type: "GET",
  url: "http://127.0.0.1:8000/ajaxdemo/serverjsonp.php?number=" + $("#keyword").val(),
  dataType: "jsonp",
  jsonp: "callback",
  success: function(data) {
      if (data.success) {
          $("#searchResult").html(data.msg);
      } else {
          $("#searchResult").html("出现错误:" + data.msg);
      }
  },
  error: function(jqXHR){
     alert("发生错误:" + jqXHR.status);
  },
});

xh2

这是后台需要在输出的时候处理:

header('Access-Control-Allow-Origin:*');
header('Access-Control-Allow-Methods:POST,GET');
header('Access-Control-Allow-Credentials:true');
header("Content-Type: application/json;charset=utf-8");

iframe

iframe,使用iframe其实相当于开了一个新的网页,具体跨域的方法大致是,域A打开的母页面嵌套一个指向域B的iframe,然后提交数据,完成之后,B的服务端可以:

返回一个302重定向响应,把结果重新指回A域; 在此iframe内部再嵌套一个指向A域的iframe。

这两者都最终实现了跨域的调用,这个方法功能上要比下面介绍到的JSONP更强,因为跨域完毕之后DOM操作和互相之间的JavaScript调用都是没有问题的,但是也有一些限制,比如结果要以URL参数传递,这就意味着在结果数据量很大的时候需要分割传递,甚是麻烦;还有一个麻烦是iframe本身带来的,母页面和iframe本身的交互本身就有安全性限制。

方案:

iframe跨域问题有点多,必须要得到iframe内部页面的配合才可能通信,方法也比较多:
1,假写hash值通信,父子页面各自建立轮询去检测iframe中url的hash值,通过值来通信
2,利用HTML5的postMessage,不过注意这个也是异步的
3,利用IE6\7中对navigator的bug,在ie6/7中,父子页面使用的window.navigator是同一个东西,父页面改了,子页面也会跟着变;
4,iframe中嵌套一层与顶层页面同域的页面,比如a中套b,b中套c,其中a、c同域,b做出改变后通过url给c传值,c中操作top对象也就是a,由于同域,所以不会有问题

例子

window.TUI = window.$ = {};
/**
* @public 通过iframe异步请求数据
* @param {string}  url是请求的地址
* @param {function}  cb是处理返回数据的回调函数
*/
TUI.getIframeData = function(url, cb){
    var f = document.getElementById('crossdomain');
    if(f)
        f.src = url;
    else{
        var t = document.createElement("DIV");
        t.innerHTML = '<iframe id="crossdomain" width="0" height="0" style="visibility:hidden;" src="' + url + '" ></iframe>';
        document.body.appendChild(t.firstChild);
    }

    (function(){
    try{
       cb(document.getElementById('crossdomain').contentWindow.document.body.getElementsByTagName("TEXTAREA")[0].value);
    }catch(e){
        setTimeout(arguments.callee,0);
        return;
    }   
    })();
};

//像这样执行
$.getIframeData("http://yoursite.com/request_url/", function(data){
    /* do something */
});

window.name

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

使用window.frames[windowName]得到一个子窗口
将其设置为链接元素的target属性
加载任何页面 window.name 的值始终保持不变。由于 window.name 这个显著的特点,使其适用于在不同源之间进行跨域通信

具体方案

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

主页面代码:

function sendMsg(msg) {  
  var state = 0, data;
  var frame = document.createElement("frame");
  var baseProxy = "http://www.otherapp.com:3000/demo4-req";
  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:3000/demo4-res";
      }
  };
  document.body.appendChild(frame);
}

document.querySelector('button').onclick = function (){  
    var val = Math.random();
    sendMsg(val);
    document.querySelector('.val').innerHTML = "请求数据:"+val;
}

目标页面代码:

var hash = window.location.hash;  
if(hash && hash.length>1){  
   var request = hash.substring(1, hash.length);
   var obj = JSON.parse(decodeURI(request));
   var data = obj.data;
   //数据乘以100
   window.name = data*100;
}

postMessage

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

具体方案:

如果 iframe 想要通知不同源的父窗口它已经加载完成,可以使用 window.postMessage 来发送消息。同时,它也将监听回馈消息:
function postMessage(msg){  
  var targetWindow = parent.window;
  targetWindow.postMessage(msg,"*");
}
function handleReceive(msg){  
 if(msg.data == "ok"){
  //要做的事在这
 }else{
  //重新发送消息
  postMessage(JSON.stringify({color:'red'}));
 }
}
window.addEventListener("message", handleReceive, false);  
window.onload = function(){  
  postMessage(JSON.stringify({color:'red'}));
}


父窗口监听了消息事件,当消息到达时,它首先检查消息是否是来 www.otherapp.com,如果是就发送一个反馈消息。

function handleReceive(event){  
  if(event.origin != "http://www.otherapp.com:3000") 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:3000");
}
window.addEventListener("message", handleReceive, false);  

结尾

事实上我一般跨域遇到的都是自己测试数据的时候ajax拿不到数据,后来自从接触Mock,便很少遇到这些情况。iframe跨域可以说是最复杂也最多变的。它能依赖旧特性来达到跨域也能结合Html5新api来跨域。

今天就到这里。

Table of Contents