• iframe跨域+


    script、image、iframe的src都不受同源策略的影响。所以我们可以借助这一特点,实现跨域。如前面所介绍的JSONP跨域,以及灯标(Beacons)。

    该篇随笔主要阐述iframe结合一些技术,实现跨域请求。

      1、iframe+window.name;

      2、iframe+location.hash;

      3、iframe+window.postMessage.

    另,在最后赋予“灯标”技术阐述。

    一、iframe + window.name实现跨域

    window对象有个name属性,该属性有个牛逼的地方就是:在同一个窗口中,我不管你页面怎么变,我window.name的值是一直存在的,在同一个窗口任意读写,并且支持非常长的name值(2MB)。

    有点含糊?

    我们写个demo看看。

    假设我有个页面a.html,当页面加载完成后,我将window.name赋值’Monkey’,在3秒后跳转到另一页面b.html,并在这个b.html中alert一下window.name,看看结果如何。

    a.html代码如下:

    <!DOCTYPE html>
        <head>
            <title>window.name</title>
            <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        </head>
        <body>
            <script>
                window.name = 'Monkey';
                setTimeout(function(){
                    window.location = 'b.html';
                },3000);            
            </script>
        </body>
    </html>

    b.html代码如下:

    <!DOCTYPE html>
        <head>
            <title>window.name</title>
            <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        </head>
        <body>
            <script>
                alert(window.name);        
            </script>
        </body>
    </html>

    运行a.html代码,等待3秒后,得如下结果:

     

    看来,只要在同一个窗口,window.name就是可读写的,如,前一个页面设置了这个属性,后一个页面就可以读取它。

    注:window.name传输技术,原本是用于解决cookie的一些劣势(每个域名4*20KB的限制、数据只能是字符串、设置和获取cookie语法的复杂等等)而发明,后来才强化了window.name传输,用来解决跨域数据传输问题。

    假设,当我在一个页面a中想跨域访问另一个不在同一域中b的数据时,怎么利用window.name呢?

    显然,我们不能像上面那样,将a页面window.location重定向为b,这样我岂不是当前页面已经不再了。所以我们可以借助于iframe标签来达到这一目的(因为iframe的src不受同源策略影响,所以可以跨域访问资源)。

    思路:

    (1)、在a.html中嵌入iframe,将所需要的文档b.html加载进来,且b.html利用window.name传入a.html想要获取的数据;

    (2)、iframe在得到b.html的内容后,必须将src变为a.html的同源域,因为同源策略是会阻止非同源的frame访问name属性值,最后a.html通过iframe.contentWindow.name,获取b.html里window.name的值,从而实现跨域获得数据。

    demo如下:

    <!DOCTYPE html>
        <head>
            <title>a.html</title>
            <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        </head>
        <body>
            <script>
                /*
                    addIframeTag:动态创建iframe,通过src获得相应文档
                    Param: src -->动态给创建iframe的src赋值
                */
                function addIframeTag(src){
                    //在loadFn函数中,用于判断iframe加载情况
                    var state = 0;
                    //创建iframe
                    var iframe = document.createElement('iframe');
                    //loadFn:跨域获取数据,如b.html
                    var loadFn = function(){
                        if (state === 1) {
                            //获取window.name数据
                            var data = iframe.contentWindow.name;    
                            //相关操作,如alert获取的数据
                            alert(data);
                            //清除动态创建的iframe
                            iframe.contentWindow.document.write('');
                            iframe.contentWindow.close();
                            document.body.removeChild(iframe);
                        } else if (state === 0) {
                            state = 1;
                            // 将src变为a.html的同源域
                            iframe.src = "a.html";    
                        }      
                    };
                    //给创建的iframe赋予指定的src值
                    iframe.src = src;
                    //当iframe加载完文件后,触发onload事件
                    if (iframe.attachEvent) {
                        iframe.attachEvent('onload', loadFn);
                    } else {
                        iframe.onload  = loadFn;
                    }
                    //隐藏iframe
                    iframe.style.display = 'none';
                    //将创建的iframe加入body中
                    document.body.appendChild(iframe);
                };
                window.onload = function(){
                    addIframeTag('b.html');
                };
            </script>
        </body>
    </html>
    a.html
    <!DOCTYPE html>
        <head>
            <title>b.html</title>
            <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        </head>
        <body> 
            <script>
                window.name = 'Monkey';
            </script>
        </body>
    </html>
    b.html
    二、iframe + location.hash实现跨域

    location.hash简而言之就是锚点,如127.0.0.1#monkey中的#monkey就是锚点。借用大额的一个例子,具体感受下锚点:here

    大家点击了上面的例子,会发现点击锚点后location.hash改变了,但却没有导致页面刷新。

    So,我们就可以借助这一特性,实现数据传递。 但,因为我是借助于location.hash来实现数据传递,所以缺点也很明显:数据是直接暴露在URL中,且传递的数据容量有限。

    那么,我们怎么通过location.hash来实现跨域访问数据呢?

    假设:a.html想跨域访问b.html中的数据

    思路:因为改变a.html的location.hash值不会刷新页面,所以我们可以借助iframe去访问b.html(因为iframe的src不受同源策略影响,所以可以跨域访问资源),然后在iframe中动态改变它父窗口a.html中的location.hash值。但是,个别浏览器不允许非同源的窗口修改parent.location.hash的值,所以我们还需要借助一个代理,在iframe获得b.html后,再在iframe里嵌套一个与a.html同源的html,如c.html,并通过c.html中的parent.parent.location.hash改变a.html中的location.hash值,从而达到跨域获取数据。

     

    抛砖引玉,我的具体实现如下:

    注:由于我是在本地跑的程序,所以我将c.html 直接换成a.html,并加入锚点判断,只是为了体验一把iframe+location.hash的跨域。

    <!DOCTYPE html>
        <head>
            <title>a.html</title>
            <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        </head>
        <body>
            <script>
                function startRequest(){
                    //获得a.html的锚点
                    var _hash = location.hash ? location.hash.substring(1):'';
                    //如果锚点是b.html中的代理ifrmae传来的,则赋值,如somedata
                    if(_hash === 'proxy'){
                        parent.parent.location.hash = 'somedata';
                        return;
                    }
                    else{
                        //创建一个iframe
                        var ifr = document.createElement('iframe');
                        ifr.style.display = 'none';
                        //想b.html获取信息
                        ifr.src = 'b.html#paramdo';
                        document.body.appendChild(ifr);
                        //获得b.html的数据
                        setInterval(function(){
                            try{
                                var data = location.hash ? location.hash.substring(1):'';
                                if(console.log){
                                    console.log('Now data is '+ data);
                                }
                            }catch(e){
                                console.log(e);
                            }
                        },2000);
                    }
                };
                startRequest();
            </script>
        </body>
    </html>
    a.html 
    <!DOCTYPE html>
        <head>
            <title>b.html</title>
            <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        </head>
        <body> 
            <script>
                //如果发现传过来的location.hash是#paramdo,做callBack操作
                if(location.hash === '#paramdo'){
                    callBack();
                }
                function callBack(){
                    try{
                        parent.location.hash = 'somedata';
                    }catch(e){
                        // ie、chrome的安全机制无法修改parent.location.hash,
                        // 所以要利用一个代理iframe,src与a.html同源
                        var ifrproxy = document.createElement('iframe');
                        ifrproxy.style.display = 'none';
                        //这里我指向a.html只是为了模拟同源
                        ifrproxy.src='a.html#proxy';
                        document.body.appendChild(ifrproxy);
                    }
                }
            </script>
        </body>
    </html>
    b.html
    三、iframe + window.postMessage实现跨域

    HTML5引入了一个跨域文档API(Cross-document messaging),这个API为window对象,新增了个window.postMessage方法,允许跨窗口通信,且不必同源。

    语法如下:

    otherwindow.postMessage(message, targetOrigin);

      (1)、otherwindow:对接收信息页面的window引用,可以是页面中iframe的contentWindow属性;window.open返回值;通过name或下标从window.frames取到的值。

      (2)、message:要发送的数据,string类型。

      (3)、targetOrigin:用于接收消息的窗口的源(origin),即“协议 + 域名 + 端口”。也可以设为*,表示不限制域名,向所有窗口发送。

    postMessage是向窗口发送信息,当然相应窗口得接收信息咯。

    怎么接收信息呢?

    通过message事件,监听对方的信息。一旦有postMessage传送过来的信息就可以做相应处理了。

    如下:

    Window.addEventListener(‘message’,function(e){console.log(e.data);},false);

    且message中的e(即event对象),通过三属性:

      (1)、event.source:发送消息的窗口对象;

      (2)、event.origin:发送消息窗口的源(协议+主机+端口号);

      (3)、event.data:发送的消息内容。

    抛砖迎玉Demo(a.html与b.html通信)如下:

    <!DOCTYPE html>
        <head>
            <title>a.html</title>
            <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        </head>
        <body>
            <iframe id="ifr" src='b.html'></iframe>
            <script>
                window.onload = function(){
                    var ifr = document.getElementById('ifr');
                    ifr.contentWindow.postMessage('I was there','/');
                };
            </script>
        </body>
    </html>
    a.html
    <!DOCTYPE html>
        <head>
            <title>b.html</title>
            <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
        </head>
        <body> 
            <script>
                window.addEventListener('message', function(event){
                    console.log('origin: '+event.origin);
                    console.log('data: '+event.data);
                    console.log('source'+event.source);
                });
            </script>
        </body>
    </html>
    b.html

    运行上述a.html代码,得下:

    四、拓展--灯标

    此技术与动态iframe标签插入非常类似,用JavaScript创建一个新的Image对象,将src设置为服务器上一个脚本文件的URL。此URL包含我们打算通过GET格式传回的键值对数据。

    注意并没有创建img元素或者将它们插入到DOM中。

    如下:

    var url = '/status_tracker.php';
    var params = [
        'step = 2',
        'time = 1465141496244'
    ];
    (new Image()).src = url + '?' + params.join('&');

    服务器取得此数据并保存下来,而不必向客户端返回什么,因此没有实际的图像显示。这是将信息发回服务器最有效的方法。其开销很小,而且任何服务器端错误都不会影响客户端。

    简单的图像灯标意味着你所能做的受到限制。你不能发送POST数据,所以你被URL长度限制在一个相当小的字符数量上。你可以用非常有限的方法接收返回数据。可以监听Image对象的load事件,它可以告诉你服务器端是否成功接收了数据,你还可以检查服务器返回图片的宽度和高度(如果返回了一张图片)并用这些数字通知你服务器的状态。例如,宽度为1表示‘成功’,2表示‘重试’。

    如果你不需要为此响应返回数据,那么你应当发送一个204 No Content响应代码,无消息正文。它将阻止客服端继续等待永远不会到来的消息体:

    var url = '/status_tracker.php';
    var params = [
        'step = 2',
        'time = 1465141496244'
    ];
    var beacon = new Image();
    beacon.src = url + '?' + params.join('&');
    beacon.onload = function(){
        if(this.width == 1){
            //Success
        }else if(this.width == 2){
            //Failure;create another beacon and try again.    
        }
    }
    beacon.onerror = function(){
        //Error;wait a bit, then create another beacon and try again.
    }

    灯标是向服务器回送数据最快和最有效的方法。服务器根本不需要发回任何响应正文,所以你不必担心客户端下载数据。唯一的缺点是接收到的响应类型是受限的。如果你需要向客户端返回大量数据,那么使用XHR。如果你只关心将数据发送到服务端(可能需要极少的回复),那么使用图像灯标。

    五、参考文献

    [1]、大额

    [2]、无双

    [3]、RainMan

    [4]、MDN

    [5]、阮一峰

  • 相关阅读:
    Spring实践第三天 (SpringDI:依赖注入)
    单例模式(Singleton)
    Spring IOC 及 Spring 中Bean的三种创建方式
    Spring实践第二天(创建对象的三种基本方式)
    ORA-28374: typed master key not found in wallet
    Unable open dabase as spfile parameter incorrect
    【java/C# 服务器】IOS 配置推送证书 p12文件流程
    第一贱-UILabel
    iOS开发系列--让你的应用“动”起来
    AFNetworking 使用总结
  • 原文地址:https://www.cnblogs.com/giggle/p/5561443.html
Copyright © 2020-2023  润新知