文章標題有點饒舌難懂,直接說我需求就清楚了。我想在員工入口網站(例如:portal.utopia.com)加入人事、行政、會計、電子表單等現成網站功能,這些應用程式各有自己的網站(例如:webap.utopia.com),最簡單的整合方法是在入口網站放個Iframe將其他網站的網頁內嵌進來,兩分鐘搞定,用膝蓋就能完成。
BUT,人生最機X的就是這個BUT!
PM/老闆/使用者一定不會這麼簡單放過你,既然網頁已經整在一起,那麼切換樣式跟入口網站融為一體,審完表單入口網站的待審數字要減一,非常合情合理,應該難不倒你吧?不!瀏覽器跳出來說:「Over my dead body!」
母網頁跟Iframe網頁要溝通基本上不是難事,可用靠JavaScript操作另一方的DOM搞定,但若是兩個網頁分屬不同站台,問題就沒這麼單純。舉個實例,入口網站網頁httq://portal.utopia.com/SOP/container.aspx長這樣:
<%@ Page Language="C#" %>
<!DOCTYPEhtml>
<html>
<head>
<title></title>
<style>
iframe { width: 320px; height: 240px; margin: 12px; }
</style>
</head>
<body>
<h5></h5>
<div>
<button>Get value from IFrame</button>
</div>
<iframeid="frmEmbedded"src="http://webap.utopia.com/SOP/Frame.aspx"></iframe>
<scriptsrc="https://code.jquery.com/jquery-2.2.3.min.js"></script>
<script>
$("h5").text(location.href);
$("button").click(function () {
alert($("#frmEmbedded").contents().find("#txtValue").val());
});
</script>
</body>
</html>
被嵌入的應用程式網頁httq://webap.utopia.com/SOP/embedded.aspx長這樣:
<%@ Page Language="C#" %>
<!DOCTYPEhtml>
<html>
<head>
<title>Frame</title>
<style>
body {
background-color: #ddd;
}
</style>
</head>
<body>
<h5></h5>
<inputid="txtValue"value="32767"/>
<scriptsrc="https://code.jquery.com/jquery-2.2.3.min.js"></script>
<script>
$("h5").text(location.href);
</script>
</body>
</html>
我們希望按下Container.aspx <button>時可以讀取Embedded.aspx的<input id="txtValue">並顯示其值,當Container.aspx與Embedded.aspx分屬不同主機(portal.utopia.com與webap.utopia.com),由Container.aspx存取Embedded.aspx的行為將被瀏覽器禁止:
出現以下錯誤:
Uncaught SecurityError: Failed to read the 'contentDocument' property from 'HTMLIFrameElement': Blocked a frame with origin "httq://portal.utopia.com" from accessing a frame with origin "httq://webap.utopia.com". Protocols, domains, and ports must match.
這個限制來自瀏覽器的同源政策(Single Origin Policy),是瀏覽器防止惡意程式作怪的基本安全防線,前端攻城獅必須摸清它的特性。關於SOP的細詳解說,我推薦阮一峰先生寫的文章:浏览器同源政策及其规避方法,是我目前看到最淺顯完整的中文文件。
我的案例發生在公司內部,符合後端域名相同條件,幸運地可以靠Container.aspx/Embedded.aspx同時加上document.domain="utopia.com"克服。
如此,藉由加註document.domain,入口網站與應用程式網站的網頁在彼此存取對方的DOM時,瀏覽器視為同網站的兩個網站應用程式,就不受同源政策限制囉~
【補充心得】
- document.domain="…"指定的內容必須與目前網址中的網域名稱相符。如果你在httq://portal.utopia.com的網頁中指定document.domain="darkthread.net",會出錯:Uncaught DOMException: Failed to set the 'domain' property on 'Document': 'darkthread.net' is not a suffix of 'utopia.com'.
- 必須雙方配合才能成功,除了入口網站要加,也要協調被嵌入網頁的開發單位配合在網頁上加入document.domain設定。
- 關於document.domain的網名值,建議不要寫死成字串,讓.NET或JavaScript自動由目前的網址抓取是上策。
C#可以使用以下語法:
string.Join(".", Request.Url.Host.Split('.').Skip(1).ToArray())
JavaScript則複雜一點是:/http(s)*:\/\/(.+?)\//i.exec(location.href)[2].split('.').slice(1).join(".")
location.hostname.split('.').slice(1).join(".") (感謝Ammon大開示,location.hostname可直接取主機名稱) - 要用這招,網址只能用網域名稱,不能用IP,故在公司進行內部測試時需配套措施:向DNS註冊測試主機,並限定使用者一律用網域名稱URL進行測試。