Ajax 跨域訪問 — 方法大全
http://www.cftea.com/c/2008/06/9QA4QM0238B2C0SF.aspCase I. Web代理的方式 (on Server A)
即用戶訪問 A 網(wǎng)站時所產(chǎn)生的對 B 網(wǎng)站的跨域訪問請求均提交到 A 網(wǎng)站的指定頁面,由該頁面代替用戶頁面完成交互,從而返回合適的結(jié)果。此方案可以解決現(xiàn)階段所能夠想到的多數(shù)跨域訪問問題,但要求 A 網(wǎng)站提供 Web 代理的支持,因此A網(wǎng)站與 B 網(wǎng)站之間必須是緊密協(xié)作的,且每次交互過程,A 網(wǎng)站的服務(wù)器負擔增加,且無法代用戶保存 Session 狀態(tài)。
Case II. on-Demand方式 (on Server A)
MYMSN 的門戶就用的這種方式,不過 MYMSN 中不涉及跨域訪問問題。在頁面內(nèi)動態(tài)生成新的 <script>,將其 src 屬性指向別的網(wǎng)站的網(wǎng)址,這個網(wǎng)址返回的內(nèi)容必須是合法的 Javascript 腳本,常用的是 JSON 消息。此方案存在的缺陷是,script 的 src 屬性完成該調(diào)用時采取的方式時 get 方式,如果請求時傳遞的字符串過大時,可能會無法正常運行。不過此方案非常適合聚合類門戶使用。
<html>
<head>
<script language="javascript" type="text/javascript">
function loadContent()
{
var s=document.createElement('script');
s.src='http://www.anotherdomain.com/TestCrossJS.aspx?f=setDivContent';
document.body.appendChild(s);
}
function setDivContent(v)
{
var dv = document.getElementById("dv");
dv.innerHTML = v;
}
</script>
<div id=dv></div>
<input onclick=loadContent() type=button value="Click Me">
其中的 www.anotherdomain.com/TestCrossJS.aspx 是這樣的:
<script language="C#" runat="server">
void Page_Load(object sender, EventArgs e)
{
string f = Request.QueryString["f"];
Response.Clear();
Response.ContentType = "application/x-javascript";
Response.Write(String.Format(@"
{0}('{1}');",
f,
DateTime.Now));
Response.End();
}
</script>
點擊“Click Me”按鈕,生成一個新的 script tag,下載對應(yīng)的 Javascript 腳本,結(jié)束時回調(diào)其中的 setDivContent(),從而更新網(wǎng)頁上一個 div 的內(nèi)容。
編者注:如果 Ajax 的 js 內(nèi)容由 B 提供,則 A 可以利用 B 提供的 js 方便地訪問 B 的資源。比如 A 中的 js 代碼:
<script type="text/javascript" src="B/core.js"></script>
Case III. iframe 方式 (on Server A)
查看過醒來在 javaeye 上的一篇關(guān)于跨域訪問的帖子,他提到自己已經(jīng)用 iframe 的方式解決了跨域訪問問題。數(shù)據(jù)提交跟獲取,采用iframe這種方式的確可以了,但由于父窗口與子窗口之間不能交互(跨域訪問的情況下,這種交互被拒絕),因此無法完成對父窗口效果的影響。
在頁面內(nèi)嵌或動態(tài)生成指向別的網(wǎng)站的 IFRAME,然后這 2 個網(wǎng)頁間可以通過改變對方的 anchor hash fragment 來傳輸消息。改變一個網(wǎng)頁的 anchor hash fragment 并不會使瀏覽器重新裝載網(wǎng)頁,所以一個網(wǎng)頁的狀態(tài)得以保持,而網(wǎng)頁本身則可以通過一個計時器(timer)來察覺自己 anchor hash 的變化,從而相應(yīng)改變自己的狀態(tài)。
1. http://domain1/TestCross.html:
<html>
<head>
<script language="javascript" type="text/javascript">
var url = "http://domain2/TestCross.html"
var oldHash = null;
var timer = null;
function getHash()
{
var hash = window.location.hash;
if ((hash.length >= 1) && (hash.charAt(0) == '#'))
{
hash = hash.substring(1);
}
return hash;
}
function sendRequest()
{
var d = document;
var t = d.getElementById('request');
var f = d.getElementById('alienFrame');
f.src = url + "#" + t.value + "<br/>" + new Date();
}
function setDivHtml(v)
{
var d = document;
var dv = d.getElementById('response');
dv.innerHTML = v;
}
function idle()
{
var newHash = getHash();
if (newHash != oldHash)
{
setDivHtml(newHash);
oldHash = newHash;
}
timer = window.setTimeout(idle, 100);
}
function window.onload()
{
timer = window.setTimeout(idle, 100);
}
</script>
</head>
<body>
請求:<input type="text" id="request">
<input type="button" value="發(fā)送" onclick="sendRequest()" /><br/>
回復:<div id="response"></div>
<iframe id="alienFrame" src="http://domain2/TestCross.html"></iframe>
</body>
</html>
2. http://domain2/TestCross.html:
<html>
<head>
<script language="javascript" type="text/javascript">
var url = "http://domain1/TestCross.html"
var oldHash = null;
var timer = null;
function getHash()
{
var hash = window.location.hash;
if ((hash.length >= 1) && (hash.charAt(0) == '#'))
{
hash = hash.substring(1);
}
return hash;
}
function sendRequest()
{
var d = document;
var t = d.getElementById('request');
var f = parent;
//alert(f.document); //試著去掉這個注釋,你會得到“Access is denied”
f.location.href = url + "#" + t.value + "<br/>" + new Date();
}
function setDivHtml(v)
{
var d = document;
var dv = d.getElementById('response');
dv.innerHTML = v;
}
function idle()
{
var newHash = getHash();
if (newHash != oldHash)
{
setDivHtml(newHash);
oldHash = newHash;
}
timer = window.setTimeout(idle, 100);
}
function window.onload()
{
timer = window.setTimeout(idle, 100);
}
</script>
</head>
<body>
請求:<input type="text" id="request">
<input type="button" value="發(fā)送" onclick="sendRequest()" /><br/>
回復:<div id="response"></div>
</body>
</html>
兩個網(wǎng)頁基本相同,第一個網(wǎng)頁內(nèi)嵌一個 IFRAME,在點擊“發(fā)送”按鈕后,會將文本框里的內(nèi)容通過 hash fragment 傳給 IFRAME。點擊 IFRAME 里的“發(fā)送”按鈕后,它會將文本框里的內(nèi)容通過 hash fragment 傳給父窗口。因為是只改動了 hash fragment,瀏覽器不會重新 load 網(wǎng)頁內(nèi)容,這里使用了一個計時器來檢測 URL 變化,如果變化了,就更新其中一個 div 的內(nèi)容 。
Case IV. 用戶本地轉(zhuǎn)儲方式 (local)
IE 本身依附于 Windows 平臺的特性為我們提供了一種基于 iframe,利用內(nèi)存來“繞行”的方案,即兩個 Window 之間可以在客戶端通過 Windows 剪貼板的方式進行數(shù)據(jù)傳輸,只需要在接受數(shù)據(jù)的一方設(shè)置 Interval 進行輪詢,獲得結(jié)果后清除 Interval 即可。FF 的平臺獨立性決定了它不支持剪貼板這種方式,而以往版本的 FF 中存在的插件漏洞又被 fixed 了,所以 FF 無法通過內(nèi)存來完成暗渡陳倉。而由于文件操作 FF 也沒有提供支持(無法通過 Cookie 跨域完成數(shù)據(jù)傳遞),致使這種技巧性的方式只能在 IE 中使用。
Case V: (其實還是在服務(wù)端 A 用 iframe 解決了與服務(wù)器 B 通信的問題)
要解決的問題:發(fā)生在用戶提交網(wǎng)頁 URL(還包括 Tag, Notes 等)給 Bookmark 服務(wù)器時。
關(guān)于 URL 的提交至少可以有三種方式:
1. 登陸 Bookmark 服務(wù)器的提交頁面,將要收藏的 URL 通過該頁面提交給服務(wù)器。
2. 安裝瀏覽器插件,通過插件將 URL 提交給服務(wù)器。
3. 從 Bookmark 服務(wù)器動態(tài)加載 javascript 小工具到當前頁面,通過它來完成提交工作。
第一種方式開發(fā)起來最簡單,但對用戶來講比較麻煩,每次都需要先登陸 Bookmark 服務(wù)器才能完成提交;第二種方式我并不熟悉插件開發(fā),而且用戶也不喜歡太多的插件堆滿自己的瀏覽器;第三種方式開發(fā)難度小,又避免了每次登陸服務(wù)器的麻煩,所以最終采用它。第三種方式中動態(tài)加載的 javascript 小工具除了需要生成 UI 供用戶填寫信息(URL,tag,notes 等),當用戶點擊提交的時候,還要完成與服務(wù)器通信的功能。
跨域訪問,簡單來說就是 A 網(wǎng)站的 javascript 代碼試圖訪問 B 網(wǎng)站,包括提交內(nèi)容和獲取內(nèi)容。由于安全原因,跨域訪問是被各大瀏覽器所默認禁止的。寫過跨域訪問 ajax 的朋友相信都遇到過被告知“沒有權(quán)限”的情況。通過 XMLHttp 來發(fā)送數(shù)據(jù)給 Bookmark 服務(wù)器的嘗試失敗了。于是,看到網(wǎng)上的一些資料,我又開始嘗試用 javascript 小工具在用戶網(wǎng)頁動態(tài)創(chuàng)建一個隱藏的 iframe, iframe 的 src 指向服務(wù)器的一個 servlet ,試圖通過調(diào)用 iframe 中提供的 javascript 來完成與服務(wù)器的通信。但不幸的是,用戶網(wǎng)頁中的 javascript 代碼訪問 iframe 也被瀏覽器歸為跨域訪問(特指 iframe 的 src 指向其它網(wǎng)站的情形),嘗試再次失敗。
最終,在一篇文章中看到,與 iframe 不同,如果 A 網(wǎng)站從 B 網(wǎng)站加載 javascript , A 網(wǎng)站可以自由的訪問該 javascript 的內(nèi)容,并不會被瀏覽器認為是跨域訪問。模仿剛才 iframe 的思路,當用戶點擊提交時,可以動態(tài)創(chuàng)建一個 javascript 對象,該對象的 src 指向 Bookmark 服務(wù)器的一個 servlet,注意:URL、Tag、Notes、User、Password 等信息被作為 src URL 參數(shù)傳給服務(wù)器。請看下面的代碼:
var url = "http://localhost:8080/Deeryard/BookmarkServlet?" +
"url=" + url_source + "&" + "title=" + title + "&" +
"tag=" + tag + "&" + "notes=" + notes + "&" +
"user=" + user + "&" + "password=" + password;
url = encodeURI(url);
//Submit to server with a trick
var js_obj = document.createElement( "script" );
js_obj.type = "text/javascript" ;
js_obj.setAttribute( "src" , url);
//Get response from server by appending it to document
document.body.appendChild(js_obj);
上面例子中, js_obj.setArrribute() 將信息作為 src 的 URL 參數(shù)提交給了 Bookmark servlet 。那么用戶又如何取得服務(wù)器的響應(yīng)信息呢?答案就是最末一行代碼, servlet 的輸出必須是 javascript 代碼,它可以調(diào)用用戶網(wǎng)頁上的其他 javascript 函數(shù),以及操作 dom 對象。下面的 servlet 代碼生成了一個 javascript 函數(shù)調(diào)用:
out.write("onServerResponse(INADEQUATE_INFORMATION);");
document.body.appendChild(js_obj) 執(zhí)行后 onServerResponse( INADEQUATE_INFORMATION) 就會得到執(zhí)行,使客戶網(wǎng)頁響應(yīng)服務(wù)器結(jié)果。這樣一個完整的通信過程就完成了。
CaseVI:Tomcat + PHP + HTML(含JS)(on Server A)
服務(wù)器 A 上已經(jīng)裝好了 Tomcat, 我們寫一個 test.html(含JS),再寫一個 PHP 文件(由其來完成跨域通信要求)。
更多,請參考:
* https://www6.software.ibm.com/developerworks/cn/education/xml/x-ajaxtrans/index.html
* http://www.xyhhxx.com/news/net/20061013121041.htm
相關(guān)閱讀
* http://www-128.ibm.com/developerworks/library/x-securemashups/
即用戶訪問 A 網(wǎng)站時所產(chǎn)生的對 B 網(wǎng)站的跨域訪問請求均提交到 A 網(wǎng)站的指定頁面,由該頁面代替用戶頁面完成交互,從而返回合適的結(jié)果。此方案可以解決現(xiàn)階段所能夠想到的多數(shù)跨域訪問問題,但要求 A 網(wǎng)站提供 Web 代理的支持,因此A網(wǎng)站與 B 網(wǎng)站之間必須是緊密協(xié)作的,且每次交互過程,A 網(wǎng)站的服務(wù)器負擔增加,且無法代用戶保存 Session 狀態(tài)。
Case II. on-Demand方式 (on Server A)
MYMSN 的門戶就用的這種方式,不過 MYMSN 中不涉及跨域訪問問題。在頁面內(nèi)動態(tài)生成新的 <script>,將其 src 屬性指向別的網(wǎng)站的網(wǎng)址,這個網(wǎng)址返回的內(nèi)容必須是合法的 Javascript 腳本,常用的是 JSON 消息。此方案存在的缺陷是,script 的 src 屬性完成該調(diào)用時采取的方式時 get 方式,如果請求時傳遞的字符串過大時,可能會無法正常運行。不過此方案非常適合聚合類門戶使用。
<html>
<head>
<script language="javascript" type="text/javascript">
function loadContent()
{
var s=document.createElement('script');
s.src='http://www.anotherdomain.com/TestCrossJS.aspx?f=setDivContent';
document.body.appendChild(s);
}
function setDivContent(v)
{
var dv = document.getElementById("dv");
dv.innerHTML = v;
}
</script>
<div id=dv></div>
<input onclick=loadContent() type=button value="Click Me">
其中的 www.anotherdomain.com/TestCrossJS.aspx 是這樣的:
<script language="C#" runat="server">
void Page_Load(object sender, EventArgs e)
{
string f = Request.QueryString["f"];
Response.Clear();
Response.ContentType = "application/x-javascript";
Response.Write(String.Format(@"
{0}('{1}');",
f,
DateTime.Now));
Response.End();
}
</script>
點擊“Click Me”按鈕,生成一個新的 script tag,下載對應(yīng)的 Javascript 腳本,結(jié)束時回調(diào)其中的 setDivContent(),從而更新網(wǎng)頁上一個 div 的內(nèi)容。
編者注:如果 Ajax 的 js 內(nèi)容由 B 提供,則 A 可以利用 B 提供的 js 方便地訪問 B 的資源。比如 A 中的 js 代碼:
<script type="text/javascript" src="B/core.js"></script>
Case III. iframe 方式 (on Server A)
查看過醒來在 javaeye 上的一篇關(guān)于跨域訪問的帖子,他提到自己已經(jīng)用 iframe 的方式解決了跨域訪問問題。數(shù)據(jù)提交跟獲取,采用iframe這種方式的確可以了,但由于父窗口與子窗口之間不能交互(跨域訪問的情況下,這種交互被拒絕),因此無法完成對父窗口效果的影響。
在頁面內(nèi)嵌或動態(tài)生成指向別的網(wǎng)站的 IFRAME,然后這 2 個網(wǎng)頁間可以通過改變對方的 anchor hash fragment 來傳輸消息。改變一個網(wǎng)頁的 anchor hash fragment 并不會使瀏覽器重新裝載網(wǎng)頁,所以一個網(wǎng)頁的狀態(tài)得以保持,而網(wǎng)頁本身則可以通過一個計時器(timer)來察覺自己 anchor hash 的變化,從而相應(yīng)改變自己的狀態(tài)。
1. http://domain1/TestCross.html:
<html>
<head>
<script language="javascript" type="text/javascript">
var url = "http://domain2/TestCross.html"
var oldHash = null;
var timer = null;
function getHash()
{
var hash = window.location.hash;
if ((hash.length >= 1) && (hash.charAt(0) == '#'))
{
hash = hash.substring(1);
}
return hash;
}
function sendRequest()
{
var d = document;
var t = d.getElementById('request');
var f = d.getElementById('alienFrame');
f.src = url + "#" + t.value + "<br/>" + new Date();
}
function setDivHtml(v)
{
var d = document;
var dv = d.getElementById('response');
dv.innerHTML = v;
}
function idle()
{
var newHash = getHash();
if (newHash != oldHash)
{
setDivHtml(newHash);
oldHash = newHash;
}
timer = window.setTimeout(idle, 100);
}
function window.onload()
{
timer = window.setTimeout(idle, 100);
}
</script>
</head>
<body>
請求:<input type="text" id="request">
<input type="button" value="發(fā)送" onclick="sendRequest()" /><br/>
回復:<div id="response"></div>
<iframe id="alienFrame" src="http://domain2/TestCross.html"></iframe>
</body>
</html>
2. http://domain2/TestCross.html:
<html>
<head>
<script language="javascript" type="text/javascript">
var url = "http://domain1/TestCross.html"
var oldHash = null;
var timer = null;
function getHash()
{
var hash = window.location.hash;
if ((hash.length >= 1) && (hash.charAt(0) == '#'))
{
hash = hash.substring(1);
}
return hash;
}
function sendRequest()
{
var d = document;
var t = d.getElementById('request');
var f = parent;
//alert(f.document); //試著去掉這個注釋,你會得到“Access is denied”
f.location.href = url + "#" + t.value + "<br/>" + new Date();
}
function setDivHtml(v)
{
var d = document;
var dv = d.getElementById('response');
dv.innerHTML = v;
}
function idle()
{
var newHash = getHash();
if (newHash != oldHash)
{
setDivHtml(newHash);
oldHash = newHash;
}
timer = window.setTimeout(idle, 100);
}
function window.onload()
{
timer = window.setTimeout(idle, 100);
}
</script>
</head>
<body>
請求:<input type="text" id="request">
<input type="button" value="發(fā)送" onclick="sendRequest()" /><br/>
回復:<div id="response"></div>
</body>
</html>
兩個網(wǎng)頁基本相同,第一個網(wǎng)頁內(nèi)嵌一個 IFRAME,在點擊“發(fā)送”按鈕后,會將文本框里的內(nèi)容通過 hash fragment 傳給 IFRAME。點擊 IFRAME 里的“發(fā)送”按鈕后,它會將文本框里的內(nèi)容通過 hash fragment 傳給父窗口。因為是只改動了 hash fragment,瀏覽器不會重新 load 網(wǎng)頁內(nèi)容,這里使用了一個計時器來檢測 URL 變化,如果變化了,就更新其中一個 div 的內(nèi)容 。
Case IV. 用戶本地轉(zhuǎn)儲方式 (local)
IE 本身依附于 Windows 平臺的特性為我們提供了一種基于 iframe,利用內(nèi)存來“繞行”的方案,即兩個 Window 之間可以在客戶端通過 Windows 剪貼板的方式進行數(shù)據(jù)傳輸,只需要在接受數(shù)據(jù)的一方設(shè)置 Interval 進行輪詢,獲得結(jié)果后清除 Interval 即可。FF 的平臺獨立性決定了它不支持剪貼板這種方式,而以往版本的 FF 中存在的插件漏洞又被 fixed 了,所以 FF 無法通過內(nèi)存來完成暗渡陳倉。而由于文件操作 FF 也沒有提供支持(無法通過 Cookie 跨域完成數(shù)據(jù)傳遞),致使這種技巧性的方式只能在 IE 中使用。
Case V: (其實還是在服務(wù)端 A 用 iframe 解決了與服務(wù)器 B 通信的問題)
要解決的問題:發(fā)生在用戶提交網(wǎng)頁 URL(還包括 Tag, Notes 等)給 Bookmark 服務(wù)器時。
關(guān)于 URL 的提交至少可以有三種方式:
1. 登陸 Bookmark 服務(wù)器的提交頁面,將要收藏的 URL 通過該頁面提交給服務(wù)器。
2. 安裝瀏覽器插件,通過插件將 URL 提交給服務(wù)器。
3. 從 Bookmark 服務(wù)器動態(tài)加載 javascript 小工具到當前頁面,通過它來完成提交工作。
第一種方式開發(fā)起來最簡單,但對用戶來講比較麻煩,每次都需要先登陸 Bookmark 服務(wù)器才能完成提交;第二種方式我并不熟悉插件開發(fā),而且用戶也不喜歡太多的插件堆滿自己的瀏覽器;第三種方式開發(fā)難度小,又避免了每次登陸服務(wù)器的麻煩,所以最終采用它。第三種方式中動態(tài)加載的 javascript 小工具除了需要生成 UI 供用戶填寫信息(URL,tag,notes 等),當用戶點擊提交的時候,還要完成與服務(wù)器通信的功能。
跨域訪問,簡單來說就是 A 網(wǎng)站的 javascript 代碼試圖訪問 B 網(wǎng)站,包括提交內(nèi)容和獲取內(nèi)容。由于安全原因,跨域訪問是被各大瀏覽器所默認禁止的。寫過跨域訪問 ajax 的朋友相信都遇到過被告知“沒有權(quán)限”的情況。通過 XMLHttp 來發(fā)送數(shù)據(jù)給 Bookmark 服務(wù)器的嘗試失敗了。于是,看到網(wǎng)上的一些資料,我又開始嘗試用 javascript 小工具在用戶網(wǎng)頁動態(tài)創(chuàng)建一個隱藏的 iframe, iframe 的 src 指向服務(wù)器的一個 servlet ,試圖通過調(diào)用 iframe 中提供的 javascript 來完成與服務(wù)器的通信。但不幸的是,用戶網(wǎng)頁中的 javascript 代碼訪問 iframe 也被瀏覽器歸為跨域訪問(特指 iframe 的 src 指向其它網(wǎng)站的情形),嘗試再次失敗。
最終,在一篇文章中看到,與 iframe 不同,如果 A 網(wǎng)站從 B 網(wǎng)站加載 javascript , A 網(wǎng)站可以自由的訪問該 javascript 的內(nèi)容,并不會被瀏覽器認為是跨域訪問。模仿剛才 iframe 的思路,當用戶點擊提交時,可以動態(tài)創(chuàng)建一個 javascript 對象,該對象的 src 指向 Bookmark 服務(wù)器的一個 servlet,注意:URL、Tag、Notes、User、Password 等信息被作為 src URL 參數(shù)傳給服務(wù)器。請看下面的代碼:
var url = "http://localhost:8080/Deeryard/BookmarkServlet?" +
"url=" + url_source + "&" + "title=" + title + "&" +
"tag=" + tag + "&" + "notes=" + notes + "&" +
"user=" + user + "&" + "password=" + password;
url = encodeURI(url);
//Submit to server with a trick
var js_obj = document.createElement( "script" );
js_obj.type = "text/javascript" ;
js_obj.setAttribute( "src" , url);
//Get response from server by appending it to document
document.body.appendChild(js_obj);
上面例子中, js_obj.setArrribute() 將信息作為 src 的 URL 參數(shù)提交給了 Bookmark servlet 。那么用戶又如何取得服務(wù)器的響應(yīng)信息呢?答案就是最末一行代碼, servlet 的輸出必須是 javascript 代碼,它可以調(diào)用用戶網(wǎng)頁上的其他 javascript 函數(shù),以及操作 dom 對象。下面的 servlet 代碼生成了一個 javascript 函數(shù)調(diào)用:
out.write("onServerResponse(INADEQUATE_INFORMATION);");
document.body.appendChild(js_obj) 執(zhí)行后 onServerResponse( INADEQUATE_INFORMATION) 就會得到執(zhí)行,使客戶網(wǎng)頁響應(yīng)服務(wù)器結(jié)果。這樣一個完整的通信過程就完成了。
CaseVI:Tomcat + PHP + HTML(含JS)(on Server A)
服務(wù)器 A 上已經(jīng)裝好了 Tomcat, 我們寫一個 test.html(含JS),再寫一個 PHP 文件(由其來完成跨域通信要求)。
更多,請參考:
* https://www6.software.ibm.com/developerworks/cn/education/xml/x-ajaxtrans/index.html
* http://www.xyhhxx.com/news/net/20061013121041.htm
相關(guān)閱讀
* http://www-128.ibm.com/developerworks/library/x-securemashups/
posted on 2011-03-15 17:58 都市淘沙者 閱讀(484) 評論(0) 編輯 收藏 所屬分類: AJAX/XML/ANT/SOAP/WEBService