前端跨域知識簡介
(2年前舊文,從來沒發表過,最近重新整理了一下程式碼,弄了個steamer-cross)
靈感
差不多2年前,由於業務需要,瞭解各種各樣不同的跨域方式。但由於各種方式千奇百怪,我覺得有必要將各種方法封裝起來,方便使用,弄了個簡單的跨域使用庫,裡面包含各種跨域的使用函式,都存放在steamer-cross v1.0分支裡。但2年過後,IE8以下的瀏覽器已經逐漸淡出市場,基本上跨域的方案可以由postMessage一統天下,於是在MessengerJS啟發下,自己寫了一個steamer-cross v2.0版本,更靈活的用法,且兼顧父子視窗之間互相傳遞資料。
v1.0版本可能有bug,僅供學習參考,v2.0已寫測試樣例,可以test
資料夾中看到,文件不清楚的地方,也可以參考test/index.html
的寫法。
本主不會詳細述說各種方法的具體實現,具體的辦法可以點選後文參考資料裡面的三篇文章。本文只會提及實現過程中的一些坑,以及框架的實現辦法。具體的實現方法,可以參考steamer-cross v1.0版本中的檔案,各種辦法的實現,可以看對應資料夾裡面的檔案。
跨域方法 -- 單向
jsonp
這是最直觀的辦法,只需要一個頁面,在頁面內包含一個指向資料頁面的script tag,然後在src後面多加一個回撥函式即可以獲取資料。
cross origin resource sharing (cors)
這個辦法前後端都涉及,因此前端的同學需要後端的配合。其實質只是一個ajax,可以接收除了post和get之後的其它伺服器請求例如put。後端需要修改的是.htaccess檔案。加入以下一句
Header set Access-Control-Allow-Origin *複製程式碼
符號*代表接收任意的HTTP請求,你也可以通過修改,限制接受請求的域名或者IP地址。
另外一個隱藏坑是,ie10以下的瀏覽器是不支援的。值得注意的是,ie8和ie9是通過XDomainRequest來進行CORS通訊的。XDomainRequest同樣支援get和post方法。物件詳細內容請見參考資料。
XDomainRequest的另一個坑是,當傳送POST請求的時候,無法設定Header,如
xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");複製程式碼
這可能導致後臺沒法辨認POST資料。如果是PHP的話,後臺需要特殊的處理,例如
if(isset($HTTP_RAW_POST_DATA))
{
parse_str($HTTP_RAW_POST_DATA, $output);
echo json_encode($output);
}複製程式碼
CORS支援情況:Chrome 4 , Firefox 3.5 , IE 8~9(XDomainRequest), IE 10+ , Opera 12 , Safari
location.hash
這個辦法坑比較多,網上的辦法會有些問題。這個辦法需要三個頁面,分別是主呼叫頁(index.html), 資料頁(data.html),和代理頁(proxy.html)。實質的結構是,index.html裡有一個iframe指向data.html,而data.html裡又有一個iframe指向proxy.html。要注意的是,index.html和proxy.html主域和子域都相同,只有data.html是異域,因此當data.html生成資料時,將資料放在proxy.html連結的hash(#)後面,然後再由proxy.html裡的程式碼通過parent.parent這樣的呼叫,將資料放到proxy.html的祖父index.html的連結上面。
大多數教程都是停留在這一步。這是不夠的,還需要在index.html裡面設定一個setInterval去監聽index.html中#的變化,進而獲取資料。據說有些高階瀏覽器裡面可以直接用hashchange來監聽,但低端的話最好還是用setInterval。因此框架裡面用setInterval實現。
window.name
由於window.name在iframe的src的變化時不會改變,所以這個辦法也可以用於跨域。這個方式雖然也需要跟location.hash也需要三個頁面,但proxy.html的作用非常次要。由於data.html能夠直接對window.name寫值,因此寫值完畢後,只需要將src改成與index.html主域和子域一致的頁面,就可以讓index.html直接呼叫了。也有不需要proxy頁面的寫法,將iframe的src寫成"about:blank;"就可以了。
跨域方法 -- 雙向
document.domain
這個辦法對於主呼叫頁(index.html)和資料頁(data.html)而言是雙向的,即兩個頁面都可以得到對方的資料(主要是DOM元素)。實質就是index.html包含一個指向data.html的iframe,然後在data.html中改變document.domain,使之和index.html的document.domain是一樣的,這樣就可以使兩個頁面互相呼叫對方的資料。唯一的缺點是隻能應用於子域不同,但主域相同的兩個頁面。
postMessage
網上大部份教程都只教從index.html傳資料到data.html。其實data.html也可以發資料到index.html。實現方法一樣,只要在data.html裡面傳送的地址跟index.html的地址一樣就可以了。否則瀏覽器會報錯。這是比較優秀的一個辦法,缺點是舊式瀏覽器並不支援。
window.navigator
這是ie6和ie7的一個安全bug。目前似乎還沒有補丁打上,所以主頁面和iframe頁面之間可以自由呼叫。