跨域解决方法

标签: web应用 跨域 | 发表时间:2014-08-12 05:41 | 作者:默北
出处:http://www.ttlsa.com

由于同源策略的限制,JavaScript跨域的问题,一直是一个比较棘手的问题,为了解决页面之间的跨域通信,大家煞费苦心,研究了各种跨域方案。之前也有小网同学分享过一篇“ 跨域,不再纠结” 开始照着尝试时还是有些不够明白的地方,深入了解之后,这里给大家补充一点更具体的做法。

先来看看哪些情况下才存在跨域的问题:

编号 URL 说明 是否允许通信

1

http://www.a.com/a.js http://www.a.com/b.js

同一域名下

允许

2

http://www.a.com/lab/a.js http://www.a.com/script/b.js

同一域名下不同文件夹

允许

3

http://www.a.com:8000/a.js http://www.a.com/b.js

同一域名,不同端口

不允许

4

http://www.a.com/a.js https://www.a.com/b.js

同一域名,不同协议

不允许

5

http://www.a.com/a.js http://70.32.92.74/b.js

域名和域名对应ip

不允许

6

http://www.a.com/a.js http://script.a.com/b.js

主域相同,子域不同

不允许

7

http://www.a.com/a.js http://a.com/b.js

同一域名,不同二级域名(同上)

不允许(cookie这种情况下也不允许访问)

8

http://www.a.com/a.js http://www.b.com/b.js

不同域名

不允许

其中编号6、7两种情况同属于主域名相同的情况,可以设置domain来解决问题,今天就不讨论这种情况了。 对于其他跨域通信的问题,我想又可以分成两类, 其一(第一种情况)是a.com下面的a.js试图请求b.com下某个接口时产生的跨域问题。 其二(第二种情况)是当a.com与b.com下面的页面成父子页面关系时试图互相通信时产生的跨域问题,典型的应用场景如a.com/a.html使用iframe内嵌了b.com/b.html,大家都知道a.html内的js脚本试图访问b.html时是会被拒绝的,反之亦然。  第一种情况,目前主流的方案是JSONP,高版本浏览器支持html5的话,还可以使用XHR2支持跨域通信的新特性。  第二种情况,目前主要是通过代理页面或者使用postMessageAPI来做, 这也是今天要讨论的话题。 第二种情况,有这样一些类似的案例:a.com/a.html使用iframe内嵌了b.com/b.html,现在希望iframe的高度能自动适应 b.html的高度,使iframe不要出现滚动条。我们都知道跨域了,a.html是没办法直接读取到b.html的高度的,b.html也没办法把自 己的高度告诉a.html。 直接说可以用代理页面的方法搞定这个问题吧,但是怎么代理法,先来看下面这张图:

ky-1

图1

b.html与a.html是不能直接通信的。我们可以在b.html下面再iframe内嵌一个proxy.html页面,因为这个页面是放在 a.com下面的,与a.html同域,所以它其实是可以和a.html直接通信的,假如a.html里面有定义一个方法_callback,在 proxy.html可以直接top._callback()调用它。但是b.html本身和proxy.html也是不能直接通信的,所谓代理页面的桥 梁作用怎么实现呢? b.html内嵌proxy.html是通过一段类似下面这样的代码: <iframe id=”proxy” src=”a.com/proxy.html” name=”proxy” frameborder=”0″ width=”0″ height=”0″></iframe> 这个iframe的src属性b.html是有权限控制的。如果它把src设置成a.com/proxy.html?args=XXX,也就是给url加 一个查询字符串,proxy.html内的js是可以读取到的。对的,这个url的查询字符串就是b.html和proxy.html之间通信的桥梁,美 中不足的是每次通信都要重写一次url造成一次网络请求,这有时会对服务器及页面的运行效率产生很大的影响。同时由于参数是通过url来传递的,会有长度 和数据类型的限制,搜集的资料显示:

  • IE浏览器对URL的长度现限制为2048字节。
  • 360极速浏览器对URL的长度限制为2118字节。
  • Firefox(Browser)对URL的长度限制为65536字节。
  • Safari(Browser)对URL的长度限制为80000字节。
  • Opera(Browser)对URL的长度限制为190000字节。
  • Google(chrome)对URL的长度限制为8182字节。

上面的方法,通过迂回战术实现了b.html跟a.html通信,但是倒过来,a.html怎么跟b.html通信呢?嵌入在b.html里面的 proxy.html可以用top快速的联系上a.html,但是要想让a.html找到proxy.html就不容易了,夹在中间的 b.html生生把它们分开了,a.html没法让b.html去找到proxy.html然后返回给它。只能采用更迂回的战术了。 顺着前面b.html到a.html的通信过程,逆向的想一下,虽然a.html没有办法主动找到proxy.html,但是proxy.html可以反 过来告诉a.html它在哪里: 在proxy.html加这么一段脚本:

var topWin = top;
function getMessage(data) {
    alert("messageFormTopWin:" + data);
}
function sendMessage(data) {
    topWin.proxyWin = window;
    topWin.getMessage(data);
}

在a.html加这么一段脚本:

var proxyWin = null;
function getMessage(data) {
    alert("messageFormProxyWin:"+data);
    sendMessage("top has receive data:"+data);
}

function sendMessage(data) {
    if (null != proxyWin) {
        proxyWin.getMessage(data);
    }
}

也就是必须由proxy.html先主动发送一个消息给a.html,a.html得到proxy.html页面window的引用,就可以反过来 向它发送请求了。 现在a.html可以把消息发给proxy.html了,但是proxy.html怎么把消息转送到b.html?似乎这才是难点,因为它们之间才真正有 着“跨域”这一道鸿沟。 这回我们不再用前面那个iframe内嵌代理页面的方法再在proxy.html内嵌一个b.com下面的代理页面了,这样实在会给人感觉嵌的太深了,四 层。但是为了跨越这道鸿沟,b.com下面也加一个代理页面是免不的。不过现在我们要利用一下window.name。window.name有一个特 性,就是页面在同一个浏览器窗口(标签页)中跳转时,它一直存在而且值不会改变。比如我们在a.html中设置了window.name=”a”,然后 location.href=” http://b.com/b.html”跳转 后,b.html可以读取window.name的值为”a”;而且window.name的值长度一般可以到达2M,ie和firefox甚至可以达到 32M,这样的存储容量,足够利用起来做跨域的数据传递了。好吧,我们现在要做的就是当proxy.html拿到a.html发送过来的数据后把这个数据 写入window.name中,然后跳转到b.com下面的代理页面,我们这里假设是bproxy.html。bproxy.html读取到 window.name值后,通知给它父页面b.html就简单了。我们再来看这个过程可以用图大概示意一下:

ky-2

图2

图例中绿色的双向箭头表示可以通信,橙色的双向箭头表示不能直接通信。 最后我们简单看一下双向通信的实测效果:

ky-3

图3

b.html每次加载的时候都先给a.html发一个”连接请求”,让a.html可以找到proxy.html。所以页面第一次加载的时候会产生三个请求:

ky-4

图4

每次b.html向a.html发送消息的时候会产生一个请求:

ky-5

图5

每次a.html向b.html发送消息的时候会产生两个请求,其中一个是a.com/proxy.html向b.com/bproxy.html跳转产生的,另一个是b.html重新向a.html发起“连接请求”时产生的:

ky-6

图6

最后简单看一下实测的几个测试页面代码:  代码片段一, a.com/a.html:

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>a.com</title>
</head>
<body>
    <div id="Div1">
        A.com/a.html</div>
    <input id="txt_msg" type="text" />
    <input id="Button1" type="button" value="向b.com/b.html发送一条消息" onclick="sendMessage(document.getElementById('txt_msg').value)" />
    <div id="div_msg">
    </div>
    <iframe width="800" height="400" id="mainFrame" src="<a href="http://localhost:8091/b.com/b.htm">http://localhost:8091/b.com/b.htm</a>">
    </iframe>
    <script type="text/javascript">
        var proxyWin = null;
        function showMsg(msg) {
            document.getElementById("div_msg").innerHTML = msg;
        }
        function getMessage(data) {
            showMsg("messageForm b.html to ProxyWin:" + data);
        }
        function sendMessage(data) {
            if (null != proxyWin) {
                proxyWin.getMessage(data);
            }
        }
    </script>
</body>
</html>

代码片段二, a.com/proxy.html:

<html xmlns="<a href="http://www.w3.org/1999/xhtml">http://www.w3.org/1999/xhtml</a>">
<head>
    <title>a.com</title>
</head>
<body>
<div id="Div1">A.com/proxy.html</div>
<div id="div_msg"></div>
<script type="text/javascript">
    var topWin = top;
    function showMsg(msg) {
        document.getElementById("div_msg").innerHTML = msg;
    }

    function getMessage(data) {
        showMsg("messageForm A.com/a.html:" + data + "<br/>两¢?秒?后¨®将?跳¬?转Áa到Ì?B.com/bproxy.html");
        window.name = data;
        setTimeout(function () { location.href = "<a href="http://localhost:8091/b.com/bproxy.htm">http://localhost:8091/b.com/bproxy.htm</a>" }, 2000);// 为了能让大家看到跳转的过程,所以加了个延时
    }

    function sendMessage(data) {
        topWin.proxyWin = window;
        topWin.getMessage(data);
    }

    var search = location.search.substring(1);
    showMsg("messageForm B.com/b.html:" + search);
    sendMessage(search);
    </script>
</body>
</html>

代码片段三, b.com/b.html:

<html xmlns="<a href="http://www.w3.org/1999/xhtml">http://www.w3.org/1999/xhtml</a>">
<head>
    <title>b.com</title>
</head>
<body>
    <div id="Div1">
        B.com/b.html</div>
    <input id="txt_msg" type="text" />
    <input id="Button1" type="button" value="向A.com/a.html发送一条消息" onclick="sendMessage(document.getElementById('txt_msg').value)" />
    <div id="div_msg">
    </div>
    <iframe id="proxy" name="proxy" style="width: 600px; height: 300px"></iframe>
    <script type="text/javascript">
        function showMsg(msg) {
            document.getElementById("div_msg").innerHTML = msg;
        }
        function sendMessage(data) {
            var proxy = document.getElementById("proxy");
            proxy.src="<a href="http://localhost:8090/a.com/proxy.htm?data">http://localhost:8090/a.com/proxy.htm?data</a>=" + data;
        }
        function connect() {
            sendMessage("connect");
        }
        function getMessage(data) {
            showMsg("messageForm a.html to ProxyWin:" + data);
            connect();
        }

        connect(); // 页面一加载,就执行一次连接
    </script>
</body>
</html>

码片段四, b.com/bproxy.html:

<html xmlns="<a href="http://www.w3.org/1999/xhtml">http://www.w3.org/1999/xhtml</a>">
<head>
    <title>b.com</title>
</head>
<body>
    <div id="Div1">
        B.com/bproxy.html</div>
    <div id="div_msg">
    </div>
    <script type="text/javascript">
        var parentWin = parent;
        var data = null;

        function getMessage() {
            if (window.name) {
                data = window.name;
                parentWin.getMessage(data);
            }
            document.getElementById("div_msg").innerHTML = "messageForm a.com/proxy.html:" + data;

        }
        getMessage();         
    </script>
</body>
</html>

好吧,现在我必须把话锋调转一下了。前面讲的这么多,也只是抛出来一些之前我们可能会采用的跨域通信方法,事实上代理页面、url传参数和 window.name、甚至还有一些利用url的hash值的跨域传值方法,都能百度到不少相关资料。但它们都逃不开代理页面,也就不可避免的要产生网 络请求,而事实上这并不是我们的本意,我们原本希望它们能够直接在客户端通信,避免不必要的网络请求开销——这些开销,在访问量超大的站点可能会对服务器 产生相当大的压力。那么,有没有更完美一点的替代方案呢? 必须给大家推荐postMessage。postMessage 正是为了满足一些合理的、不同站点之间的内容能在浏览器端进行交互的需求而设计的。利用postMessage API实现跨域通信非常简单,我们直接看一下实例的代码:

代码片段五, A.com/a.html

<html xmlns="<a href="http://www.w3.org/1999/xhtml">http://www.w3.org/1999/xhtml</a>">
<head runat="server">
    <title>A.com/a.html</title>
    <script type="text/javascript">
        var trustedOrigin = "<a href="http://localhost:8091/">http://localhost:8091</a>";

        function messageHandler(e) {
            if (e.origin == trustedOrigin) {//接收消息的时候,判断消息是否来自可信的源,这个源是否可信则完全看自己的定义了。
                showMsg(e.data);//e.data才是真实要传递的数据
            } else {
                // ignore messages from other origins
            }
        }

        function sendString(s) {//发送消息
            document.getElementById("widget").contentWindow.postMessage(s, trustedOrigin);
        }

        function showMsg(message) {
            document.getElementById("status").innerHTML = message;
        }

        function sendStatus() {
            var statusText = document.getElementById("statusText").value;
            sendString(statusText);
        }

        function loadDemo() {
            addEvent(document.getElementById("sendButton"), "click", sendStatus);
            sendStatus();
        }

        function addEvent(obj, trigger, fun) {
            if (obj.addEventListener) obj.addEventListener(trigger, fun, false);
            else if (obj.attachEvent) obj.attachEvent('on' + trigger, fun);
            else obj['on' + trigger] = fun;
        }
        addEvent(window, "load", loadDemo);
        addEvent(window, "message", messageHandler);

    </script>
</head>
<body>
    <h1>A.com/a.html</h1>
    <p><b>源</b>: <a href="http://localhost:8090</p">http://localhost:8090</p</a>>
    <input type="text" id="statusText" value="msg from a.com/a.html">
    <button id="sendButton">向b.com/b.html发送消息</button>
    <p>接收到来自a.com/a.html的消息: <strong id="status"></strong>.<p>
    <iframe id="widget" width="800" height="400" src="<a href="http://localhost:8091/PostMessage/Default.aspx%22%3E%3C/iframe">http://localhost:8091/PostMessage/Default.aspx"></iframe</a>>
</body>
</html>

代码片段六, B.com/b.html:

<html xmlns="<a href="http://www.w3.org/1999/xhtml">http://www.w3.org/1999/xhtml</a>">
<head runat="server">
    <title>B.com/b.html</title>
    <script type="text/javascript">
        //检查postMessage 是否可以用:window.postMessage===undefined
        //定义信任的消息源
        var trustedOrigin = "<a href="http://localhost:8090/">http://localhost:8090</a>";
        function messageHandler(e) {
            if (e.origin === "<a href="http://localhost:8090/">http://localhost:8090</a>") {
                showMsg(e.data);
            } else {
                // ignore messages from other origins
            }
        }

        function sendString(s) {
            window.top.postMessage(s, trustedOrigin); //第二个参数是消息传送的目的地
        }

        function loadDemo() {
            addEvent(document.getElementById("actionButton"), "click", function () {
                var messageText = document.getElementById("messageText").value;
                sendString(messageText);
            });
        }

        function showMsg(message) {
            document.getElementById("status").innerHTML = message;
        }

        function addEvent(obj, trigger, fun) {
            if (obj.addEventListener) obj.addEventListener(trigger, fun, false);
            else if (obj.attachEvent) obj.attachEvent('on' + trigger, fun);
            else obj['on' + trigger] = fun;
        }
        addEvent(window, "load", loadDemo);
        addEvent(window, "message", messageHandler);
    </script>
</head>
<body>
    <h1>B.com/b.html</h1>
    <p><b>源</b>: <a href="http://localhost:8091</p">http://localhost:8091</p</a>>
    <p>接收到来自a.com/a.html的消息: <strong id="status"></strong>.<p>
            <div>
                <input type="text" id="messageText" value="msg from b.com/b.html">
                <button id="actionButton"> 向a.com/a.html发送一个消息</button>
            </div>
</body>
</html>

代码的关键是message事件是一个拥有data(数据)和origin(来源)属性的DOM事件。data属性是发送的实际数据,origin 属性是发送来源。Origin属性很关键,有了这个属性,接收方可以轻易的忽略掉来自不可信源的消息,也就能有效避免跨域通信这个开口给我们的源安全带来 的隐患。接口很强大,所以代码很简单。我们可以抓包看一下,这个通信过程完全是在浏览器端的,没有产生任何的网络请求。同时这个接口目前已经得到了绝大多 数浏览器的支持,包括IE8及以上版本,参见下面的图表:

ky-7

图7

但是为了覆盖ie6等低版本浏览器,我们完整的方案里面还是要包含一下兼容代码,就是最开始介绍的代理页面的方法了,但必须是以postMessage为主,这样即便最后会有某些浏览器因为这种通信产生一些网络请求,比例也是非常低的了。

转自:http://tid.tenpay.com/?p=4695

相关 [方法] 推荐:

模板方法

- - 博客园_首页
由于前两天刚好用到模板方法这个模式,而且这个模式相对来 比较简单实用,就写写个人的一些认知吧. 大家对宋丹丹和赵本山的小品里有一个很经典的台词一定不会陌生,而且还日常中经常引用:. 《钟点工》中宋丹丹问要把大象装冰箱,总共分几步. 赵本山就懵了,大象那么大,冰箱那么小,怎么才能把大象装冰箱里呢. 答案也很经典:三步:第1步,把冰箱门打开;第2步,把大象装进去;第3步,把冰箱门带上.

详解 equals() 方法和 hashCode() 方法

- - ImportNew
Java的基类Object提供了一些方法,其中equals()方法用于判断两个对象是否相等,hashCode()方法用于计算对象的哈希码. equals()和hashCode()都不是final方法,都可以被重写(overwrite). 本文介绍了2种方法在使用和重写时,一些需要注意的问题. Object类中equals()方法实现如下:.

mysql优化方法

- - 数据库 - ITeye博客
通过show status和应用特点了解各种SQL的执行频率. 通过SHOW STATUS可以提供服务器状态信息,也可以使用mysqladmin extended-status命令获得. SHOW STATUS可以根据需要显示session级别的统计结果和global级别的统计结果. 以下几个参数对Myisam和Innodb存储引擎都计数:.

WinDump使用方法

- - 研发管理 - ITeye博客
Windump是Windows环境下一款经典的网络协议分析软件,其Unix版本名称为Tcpdump. 它可以捕捉网络上两台电脑之间所有的数据包,供网络管理员/入侵分析员做进一步流量分析和入侵检测. 在这种监视状态下,任何两台电脑之间都没有秘密可言,所有的流量、所有的数据都逃不过你的眼睛(当然加密的数据不在讨论范畴之内,而且,对数据包分析的结果依赖于你的TCP/IP知识和经验,不同水平的人得出的结果可能会大相径庭).

下载Flickr图片方法

- wind - 让PPT设计NEW一NEW
        众所周知,Flickr是全球最大的在线图片分享网站,也是最早涉足web2.0的网站之一. 网站上面有很多非常漂亮的图片,这为那些“图片控”、“下载控”提供了一个非常好的途径. 可惜啊可惜,可惜的是,Flickr网站并非允许所有图片可以下载.         前几天,有个网友发微博私信给我,问我如何下载Flickr图片.

放手的7种方法

- imacee - 译言-每日精品译文推荐
来源Seven Ways to Let Go. 前言:学会放手是一门人生哲学,无端的执着总是给生活带来不快乐. 当人们总是在抱怨放下太难的时候,是不是可以从自身上寻找一些可以改进的地方呢. 下面让我们来探讨几条小建议吧,仅供参考. 一、拓宽自己的世界观并舍弃一些习惯. 学会放开,就要拒绝执行要求自己事事完美的命令,不要总是要求自己达到某个目标,取悦他人.

止咳的天然方法

- Jerrfey - 译言-每日精品译文推荐
来源Natural Ways to Suppress a Cough. 在你伸手拿咳嗽糖浆之前,试试这些天然疗法的其中之一吧. To make a tea, mix 2 teaspoons crushed leaves in 1 cup boiling water, cover, then steep for 10 minutes and strain..

雷军的方法论

- Leo - 《商业价值》杂志
雷军做小米的过程,实际上就是将他从金山和做天使投资人时所积累的方法论,付诸实践的过程. 1992年,雷军加入金山软件,任北京开发部总经理;1998年,雷军担任金山软件CEO;1999年,金山软件筹备上市;2007年,金山软件上市; 2个月后,雷军宣布离职. 这是雷军从23岁到38岁最重要的人生一页.

IPv6地址规划方法

- Power - cnBeta.COM
今年初ICANN和APNIC的IPv4地址池全部耗尽,亚太地区成为全球首个无法满足IPv4需求的地区. 伴随着我国互联网产业的高速增长以及未来三网 融合和物联网的发展,当前我国掌握的IPv4地址资源远无法满足高速增长的用户需求,我国将成为全球最早受地址匮乏影响的国家之一.

Doclist压缩方法简介

- flychen50 - 搜索技术博客-淘宝
本文是作者在学习doclist压缩时的一点总结,希望以尽可能简单明了的方式描述各个算法的思想和适用场景,帮助同学们理解和比较. 本文并不涉及具体的算法实现,代码请大家自行google. 这里需要强调的是“所谓的改进顺序”只是作者yy出来方便理解记忆,并不反应真实的压缩方法发展历程. 倒排表的基本组成部分,看例子:.