Ajax 跨域訪問 — 方法大全
http://www.cftea.com/c/2008/06/9QA4QM0238B2C0SF.aspCase I. Web代理的方式 (on Server A)
即用戶訪問 A 網站時所產生的對 B 網站的跨域訪問請求均提交到 A 網站的指定頁面,由該頁面代替用戶頁面完成交互,從而返回合適的結果。此方案可以解決現階段所能夠想到的多數跨域訪問問題,但要求 A 網站提供 Web 代理的支持,因此A網站與 B 網站之間必須是緊密協作的,且每次交互過程,A 網站的服務器負擔增加,且無法代用戶保存 Session 狀態。
Case II. on-Demand方式 (on Server A)
MYMSN 的門戶就用的這種方式,不過 MYMSN 中不涉及跨域訪問問題。在頁面內動態生成新的 <script>,將其 src 屬性指向別的網站的網址,這個網址返回的內容必須是合法的 Javascript 腳本,常用的是 JSON 消息。此方案存在的缺陷是,script 的 src 屬性完成該調用時采取的方式時 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,下載對應的 Javascript 腳本,結束時回調其中的 setDivContent(),從而更新網頁上一個 div 的內容。
編者注:如果 Ajax 的 js 內容由 B 提供,則 A 可以利用 B 提供的 js 方便地訪問 B 的資源。比如 A 中的 js 代碼:
<script type="text/javascript" src="B/core.js"></script>
Case III. iframe 方式 (on Server A)
查看過醒來在 javaeye 上的一篇關于跨域訪問的帖子,他提到自己已經用 iframe 的方式解決了跨域訪問問題。數據提交跟獲取,采用iframe這種方式的確可以了,但由于父窗口與子窗口之間不能交互(跨域訪問的情況下,這種交互被拒絕),因此無法完成對父窗口效果的影響。
在頁面內嵌或動態生成指向別的網站的 IFRAME,然后這 2 個網頁間可以通過改變對方的 anchor hash fragment 來傳輸消息。改變一個網頁的 anchor hash fragment 并不會使瀏覽器重新裝載網頁,所以一個網頁的狀態得以保持,而網頁本身則可以通過一個計時器(timer)來察覺自己 anchor hash 的變化,從而相應改變自己的狀態。
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="發送" 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="發送" onclick="sendRequest()" /><br/>
回復:<div id="response"></div>
</body>
</html>
兩個網頁基本相同,第一個網頁內嵌一個 IFRAME,在點擊“發送”按鈕后,會將文本框里的內容通過 hash fragment 傳給 IFRAME。點擊 IFRAME 里的“發送”按鈕后,它會將文本框里的內容通過 hash fragment 傳給父窗口。因為是只改動了 hash fragment,瀏覽器不會重新 load 網頁內容,這里使用了一個計時器來檢測 URL 變化,如果變化了,就更新其中一個 div 的內容 。
Case IV. 用戶本地轉儲方式 (local)
IE 本身依附于 Windows 平臺的特性為我們提供了一種基于 iframe,利用內存來“繞行”的方案,即兩個 Window 之間可以在客戶端通過 Windows 剪貼板的方式進行數據傳輸,只需要在接受數據的一方設置 Interval 進行輪詢,獲得結果后清除 Interval 即可。FF 的平臺獨立性決定了它不支持剪貼板這種方式,而以往版本的 FF 中存在的插件漏洞又被 fixed 了,所以 FF 無法通過內存來完成暗渡陳倉。而由于文件操作 FF 也沒有提供支持(無法通過 Cookie 跨域完成數據傳遞),致使這種技巧性的方式只能在 IE 中使用。
Case V: (其實還是在服務端 A 用 iframe 解決了與服務器 B 通信的問題)
要解決的問題:發生在用戶提交網頁 URL(還包括 Tag, Notes 等)給 Bookmark 服務器時。
關于 URL 的提交至少可以有三種方式:
1. 登陸 Bookmark 服務器的提交頁面,將要收藏的 URL 通過該頁面提交給服務器。
2. 安裝瀏覽器插件,通過插件將 URL 提交給服務器。
3. 從 Bookmark 服務器動態加載 javascript 小工具到當前頁面,通過它來完成提交工作。
第一種方式開發起來最簡單,但對用戶來講比較麻煩,每次都需要先登陸 Bookmark 服務器才能完成提交;第二種方式我并不熟悉插件開發,而且用戶也不喜歡太多的插件堆滿自己的瀏覽器;第三種方式開發難度小,又避免了每次登陸服務器的麻煩,所以最終采用它。第三種方式中動態加載的 javascript 小工具除了需要生成 UI 供用戶填寫信息(URL,tag,notes 等),當用戶點擊提交的時候,還要完成與服務器通信的功能。
跨域訪問,簡單來說就是 A 網站的 javascript 代碼試圖訪問 B 網站,包括提交內容和獲取內容。由于安全原因,跨域訪問是被各大瀏覽器所默認禁止的。寫過跨域訪問 ajax 的朋友相信都遇到過被告知“沒有權限”的情況。通過 XMLHttp 來發送數據給 Bookmark 服務器的嘗試失敗了。于是,看到網上的一些資料,我又開始嘗試用 javascript 小工具在用戶網頁動態創建一個隱藏的 iframe, iframe 的 src 指向服務器的一個 servlet ,試圖通過調用 iframe 中提供的 javascript 來完成與服務器的通信。但不幸的是,用戶網頁中的 javascript 代碼訪問 iframe 也被瀏覽器歸為跨域訪問(特指 iframe 的 src 指向其它網站的情形),嘗試再次失敗。
最終,在一篇文章中看到,與 iframe 不同,如果 A 網站從 B 網站加載 javascript , A 網站可以自由的訪問該 javascript 的內容,并不會被瀏覽器認為是跨域訪問。模仿剛才 iframe 的思路,當用戶點擊提交時,可以動態創建一個 javascript 對象,該對象的 src 指向 Bookmark 服務器的一個 servlet,注意:URL、Tag、Notes、User、Password 等信息被作為 src URL 參數傳給服務器。請看下面的代碼:
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 參數提交給了 Bookmark servlet 。那么用戶又如何取得服務器的響應信息呢?答案就是最末一行代碼, servlet 的輸出必須是 javascript 代碼,它可以調用用戶網頁上的其他 javascript 函數,以及操作 dom 對象。下面的 servlet 代碼生成了一個 javascript 函數調用:
out.write("onServerResponse(INADEQUATE_INFORMATION);");
document.body.appendChild(js_obj) 執行后 onServerResponse( INADEQUATE_INFORMATION) 就會得到執行,使客戶網頁響應服務器結果。這樣一個完整的通信過程就完成了。
CaseVI:Tomcat + PHP + HTML(含JS)(on Server A)
服務器 A 上已經裝好了 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
相關閱讀
* http://www-128.ibm.com/developerworks/library/x-securemashups/
即用戶訪問 A 網站時所產生的對 B 網站的跨域訪問請求均提交到 A 網站的指定頁面,由該頁面代替用戶頁面完成交互,從而返回合適的結果。此方案可以解決現階段所能夠想到的多數跨域訪問問題,但要求 A 網站提供 Web 代理的支持,因此A網站與 B 網站之間必須是緊密協作的,且每次交互過程,A 網站的服務器負擔增加,且無法代用戶保存 Session 狀態。
Case II. on-Demand方式 (on Server A)
MYMSN 的門戶就用的這種方式,不過 MYMSN 中不涉及跨域訪問問題。在頁面內動態生成新的 <script>,將其 src 屬性指向別的網站的網址,這個網址返回的內容必須是合法的 Javascript 腳本,常用的是 JSON 消息。此方案存在的缺陷是,script 的 src 屬性完成該調用時采取的方式時 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,下載對應的 Javascript 腳本,結束時回調其中的 setDivContent(),從而更新網頁上一個 div 的內容。
編者注:如果 Ajax 的 js 內容由 B 提供,則 A 可以利用 B 提供的 js 方便地訪問 B 的資源。比如 A 中的 js 代碼:
<script type="text/javascript" src="B/core.js"></script>
Case III. iframe 方式 (on Server A)
查看過醒來在 javaeye 上的一篇關于跨域訪問的帖子,他提到自己已經用 iframe 的方式解決了跨域訪問問題。數據提交跟獲取,采用iframe這種方式的確可以了,但由于父窗口與子窗口之間不能交互(跨域訪問的情況下,這種交互被拒絕),因此無法完成對父窗口效果的影響。
在頁面內嵌或動態生成指向別的網站的 IFRAME,然后這 2 個網頁間可以通過改變對方的 anchor hash fragment 來傳輸消息。改變一個網頁的 anchor hash fragment 并不會使瀏覽器重新裝載網頁,所以一個網頁的狀態得以保持,而網頁本身則可以通過一個計時器(timer)來察覺自己 anchor hash 的變化,從而相應改變自己的狀態。
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="發送" 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="發送" onclick="sendRequest()" /><br/>
回復:<div id="response"></div>
</body>
</html>
兩個網頁基本相同,第一個網頁內嵌一個 IFRAME,在點擊“發送”按鈕后,會將文本框里的內容通過 hash fragment 傳給 IFRAME。點擊 IFRAME 里的“發送”按鈕后,它會將文本框里的內容通過 hash fragment 傳給父窗口。因為是只改動了 hash fragment,瀏覽器不會重新 load 網頁內容,這里使用了一個計時器來檢測 URL 變化,如果變化了,就更新其中一個 div 的內容 。
Case IV. 用戶本地轉儲方式 (local)
IE 本身依附于 Windows 平臺的特性為我們提供了一種基于 iframe,利用內存來“繞行”的方案,即兩個 Window 之間可以在客戶端通過 Windows 剪貼板的方式進行數據傳輸,只需要在接受數據的一方設置 Interval 進行輪詢,獲得結果后清除 Interval 即可。FF 的平臺獨立性決定了它不支持剪貼板這種方式,而以往版本的 FF 中存在的插件漏洞又被 fixed 了,所以 FF 無法通過內存來完成暗渡陳倉。而由于文件操作 FF 也沒有提供支持(無法通過 Cookie 跨域完成數據傳遞),致使這種技巧性的方式只能在 IE 中使用。
Case V: (其實還是在服務端 A 用 iframe 解決了與服務器 B 通信的問題)
要解決的問題:發生在用戶提交網頁 URL(還包括 Tag, Notes 等)給 Bookmark 服務器時。
關于 URL 的提交至少可以有三種方式:
1. 登陸 Bookmark 服務器的提交頁面,將要收藏的 URL 通過該頁面提交給服務器。
2. 安裝瀏覽器插件,通過插件將 URL 提交給服務器。
3. 從 Bookmark 服務器動態加載 javascript 小工具到當前頁面,通過它來完成提交工作。
第一種方式開發起來最簡單,但對用戶來講比較麻煩,每次都需要先登陸 Bookmark 服務器才能完成提交;第二種方式我并不熟悉插件開發,而且用戶也不喜歡太多的插件堆滿自己的瀏覽器;第三種方式開發難度小,又避免了每次登陸服務器的麻煩,所以最終采用它。第三種方式中動態加載的 javascript 小工具除了需要生成 UI 供用戶填寫信息(URL,tag,notes 等),當用戶點擊提交的時候,還要完成與服務器通信的功能。
跨域訪問,簡單來說就是 A 網站的 javascript 代碼試圖訪問 B 網站,包括提交內容和獲取內容。由于安全原因,跨域訪問是被各大瀏覽器所默認禁止的。寫過跨域訪問 ajax 的朋友相信都遇到過被告知“沒有權限”的情況。通過 XMLHttp 來發送數據給 Bookmark 服務器的嘗試失敗了。于是,看到網上的一些資料,我又開始嘗試用 javascript 小工具在用戶網頁動態創建一個隱藏的 iframe, iframe 的 src 指向服務器的一個 servlet ,試圖通過調用 iframe 中提供的 javascript 來完成與服務器的通信。但不幸的是,用戶網頁中的 javascript 代碼訪問 iframe 也被瀏覽器歸為跨域訪問(特指 iframe 的 src 指向其它網站的情形),嘗試再次失敗。
最終,在一篇文章中看到,與 iframe 不同,如果 A 網站從 B 網站加載 javascript , A 網站可以自由的訪問該 javascript 的內容,并不會被瀏覽器認為是跨域訪問。模仿剛才 iframe 的思路,當用戶點擊提交時,可以動態創建一個 javascript 對象,該對象的 src 指向 Bookmark 服務器的一個 servlet,注意:URL、Tag、Notes、User、Password 等信息被作為 src URL 參數傳給服務器。請看下面的代碼:
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 參數提交給了 Bookmark servlet 。那么用戶又如何取得服務器的響應信息呢?答案就是最末一行代碼, servlet 的輸出必須是 javascript 代碼,它可以調用用戶網頁上的其他 javascript 函數,以及操作 dom 對象。下面的 servlet 代碼生成了一個 javascript 函數調用:
out.write("onServerResponse(INADEQUATE_INFORMATION);");
document.body.appendChild(js_obj) 執行后 onServerResponse( INADEQUATE_INFORMATION) 就會得到執行,使客戶網頁響應服務器結果。這樣一個完整的通信過程就完成了。
CaseVI:Tomcat + PHP + HTML(含JS)(on Server A)
服務器 A 上已經裝好了 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
相關閱讀
* http://www-128.ibm.com/developerworks/library/x-securemashups/
posted on 2011-03-15 17:58 都市淘沙者 閱讀(481) 評論(0) 編輯 收藏 所屬分類: AJAX/XML/ANT/SOAP/WEBService