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]、阮一峰


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM