1. 程式人生 > >JS、JQuery和ExtJs的跨域處理

JS、JQuery和ExtJs的跨域處理

1.什麼是跨域?
跨域,JavaScript出於安全方面的考慮,不允許跨域呼叫其他頁面的物件。簡單地理解就是因為JavaScript同源策略的限制,a.com 域名下的js無法操作b.com或是c.a.com域名下的物件。
同源策略,它是由Netscape提出的一個著名的安全策略。現在所有支援JavaScript 的瀏覽器都會使用這個策略。所謂同源是指,域名,協議,埠相同。當一個瀏覽器的兩個tab頁中分別開啟來 百度和谷歌的頁面當一個百度瀏覽器執行一個指令碼的時候會檢查這個指令碼是屬於哪個頁面的,即檢查是否同源,只有和百度同源的指令碼才會被執行。
更詳細的說明可以看下錶:

URL 說明 是否允許通訊
http://www.a.com/a.js
http://www.a.com/b.js
同一域名下 允許
http://www.a.com/lab/a.js
http://www.a.com/script/b.js
同一域名下不同資料夾 允許
http://www.a.com:8000/a.js
http://www.a.com/b.js
同一域名,不同埠 不允許
http://www.a.com/a.js
https://www.a.com/b.js
同一域名,不同協議 不允許
http://www.a.com/a.js
http://70.32.92.74/b.js
域名和域名對應ip 不允許
http://www.a.com/a.js
http://script.a.com/b.js
主域相同,子域不同 不允許
http://www.a.com/a.js
http://a.com/b.js
同一域名,不同二級域名(同上) 不允許(cookie這種情況下也不允許訪問
http://www.cnblogs.com/a.js
http://www.a.com/b.js
不同域名 不允許

特別注意兩點:
(1).如果是協議和埠造成的跨域問題“前臺”是無能為力的,
(2).在跨域問題上,域僅僅是通過“URL的首部”來識別而不會去嘗試判斷相同的ip地址對應著兩個域或兩個域是否在同一個ip上。
“URL的首部”指window.location.protocol +window.location.host,也可以理解為“Domains, protocols and ports must match”。

2.跨域請求資料解決方案
(1).document.domain+iframe的設定
對於主域相同而子域不同的例子,可以通過設定document.domain的辦法來解決。
(2).動態建立Script
雖然瀏覽器預設禁止了跨域訪問,但並不禁止在頁面中引用其他域的JS檔案,並可以自由執行引入的JS檔案中的function(包括操作cookie、Dom等等)。
(3).利用iframe和location.hash
這個辦法比較繞,但是可以解決完全跨域情況下的腳步置換問題。原理是利用location.hash來進行傳值。
(4).Window.name實現的跨域資料傳輸
iframe的src屬性由外域轉向本地域,跨域資料即由iframe的window.name從外域傳遞到本地域。這個就巧妙地繞過了瀏覽器的跨域訪問限制,但同時它又是安全操作。
(5).使用HTML5 postMessage
HTML5中最酷的新功能之一就是 跨文件訊息傳輸Cross Document Messaging。
(6).利用flash
上述,六種方式都可以處理JavaScript的跨域請求資料問題,詳細參見:Rain Man的《JavaScript跨域總結與解決辦法》。
除了上面六種方式,大家平時估計都在用指令碼框架開發,在JQuery框架和ExtJs框架中處理JS跨域問題,常用JSONP來處理。

3.什麼是JSONP?
JSONP(JSON with Padding)是一個非官方的協議,它允許在伺服器端整合Script Tags返回至客戶端,通過Javascript callback的形式實現跨域訪問(這僅僅是JSONP簡單的實現形式)。
由於同源策略的限制,XMLHttpRequest只允許請求當前源(域名、協議、埠)的資源,為了實現跨域請求,可以通過script標籤實現跨域請求,然後在服務端輸出JSON資料並執行回撥函式,從而解決了跨域的資料請求。

4..JSONP如何產生的?
(1).跨域訪問無許可權。
眾所周知的問題,Ajax直接請求普通檔案存在跨域無許可權訪問的問題,不管是靜態頁面、動態網頁、web服務、WCF,只要是跨域請求,都無許可權;
(2)."src"屬性標籤有跨域能力。
發現在Web頁面上呼叫js檔案時則不受是否跨域的影響(不僅如此,擁有"src"屬性的標籤都擁有跨域能力,比如<script>、<img>、<iframe>);
(3).將資料裝進JS格式資料。
如果想通過純Web端(ActiveX控制元件、服務端代理、HTML5之Websocket等方式不算)跨域訪問資料就只有一種可能,就是在遠端伺服器上設法把資料裝進js格式的資料裡,供客戶端呼叫和進一步處理;
(4).JSON格式承載資料適合。
有一種JSON的純字元資料格式可以簡潔的描述複雜資料,更妙的是JSON還被JS原生支援,所以在客戶端幾乎可以隨心所欲的處理這種格式的資料;
(5).動態生成JSON格式資料。
Web客戶端可以通過與呼叫指令碼一模一樣的方式,來呼叫跨域伺服器上動態生成的js格式檔案,顯而易見,伺服器之所以要動態生成JSON檔案,目的在於把客戶端需要的資料裝入進去。
(6).JSON資料成功回撥到客戶端。
客戶端在對JSON檔案呼叫成功之後,也就獲得了自己所需的資料,剩下的就是按照自己需求進行處理和展現了,這種獲取遠端資料的方式看起來非常像AJAX,但實質上是不一樣。
(7).形成一種非正式傳輸協議JSONP。
為了便於客戶端使用資料,逐漸形成了一種非正式傳輸協議,人們把它稱作JSONP,該協議的一個要點就是允許使用者傳遞一個callback引數給服務端,然後服務端返回資料時會將這個callback引數作為函式名來包裹住JSON資料,這樣客戶端就可以隨意定製自己的函式來自動處理返回的資料。

5.JSONP的工作原理
JSONP的原理:建立一個回撥函式,動態建立Script標籤,然後在遠端服務上呼叫這個函式並且將JSON 資料形式作為引數傳遞,完成回撥。將JSON資料填充進回撥函式,進行相關的邏輯處理,或許這就是JSONP的JSON+Padding的含義。
(1).跨域簡單原理
新建一個asp.net的web程式,新增sample.html網頁和一個test.js檔案,程式碼如下:
sample.html的程式碼:

複製程式碼
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
  <head>
    <title>test</title>
    <script type="text/javascript" src="test.js"></script>
  </head>
  <body>
  </body>
</html>
複製程式碼

test.js的程式碼:

 alert("success");

開啟sample.html後會跳出"success”這樣的這樣的資訊框,這似乎並不能說明什麼, 跨域問題到底怎麼解決呢?
現在模擬非同源的環境,把上面的Web程式叫做A程式,再新建一個Web程式叫做B程式,將A程式的test.js檔案移除然後拷貝到B程式中。將兩個程式都執行起來,Visual Studio會啟動內建伺服器,假設A程式是localhost:20001,B程式是localhost:20002,這就模擬了一個非同源的環境了(雖然域名相同但埠號不同,所以是非同源的)。

現在改下A程式sample.html裡的程式碼,因為test.js檔案在B程式上了,url也就變成了localhost:20002。

sample.html部分程式碼:

<script type="text/javascript" src="http://localhost:20002/test.js"></script>

請保持AB兩個Web程式的執行狀態,當你再次重新整理A程式localhost:20001/sample.html的時候,和原來一樣跳出了"success"的對話方塊,這樣就成功訪問到了非同源的B程式localhost:20002/test.js這個所謂的遠端服務了。到這裡,大家應該已經大概明白如何跨域訪問的原理了。
<script>標籤的src屬性並不被同源策略所約束,所以可以獲取任何伺服器上指令碼並執行。

(2).跨域實現CallBack
繼續修改程式碼,實現JSONP的JavaScript callback形式。
修改程式A中sample的程式碼:

複製程式碼
<script type="text/javascript">
  //回撥函式
  function callback(data) {
    alert(data.message);
  }
</script>
<script type="text/javascript" src="http://localhost:20002/test.js"></script>
複製程式碼

程式B中test.js的程式碼:

//呼叫callback函式,並以json資料形式作為闡述傳遞,完成回撥
callback({message:"success"});

這其實就是JSONP的簡單實現模式,或者說是JSONP的原型:建立一個回撥函式,然後在遠端服務上呼叫這個函式並且將JSON 資料形式作為引數傳遞,完成回撥。
(3).跨域實現動態JS指令碼

怎麼讓遠端js知道它應該呼叫的本地函式叫什麼名字?畢竟是jsonp的服務者都要面對很多服務物件,而這些服務物件各自的本地函式都不相同。只要服務端提供的js指令碼是動態生成的就可以,這樣呼叫者可以傳一個引數過去告訴服務端“我想要一段呼叫XXX函式的js程式碼,請你返回給我”,於是伺服器就可以按照客戶端的需求來動態生成js指令碼並響應了。
程式A中sample的程式碼:

複製程式碼
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script type="text/javascript">
    // 得到航班資訊查詢結果後的回撥函式
    var flightHandler = function(data){
        alert('你查詢的航班結果是:票價 ' + data.price + ' 元,餘票 ' + data.tickets + ' 張。');
    };
    // 提供jsonp服務的url地址(不管是什麼型別的地址,最終生成的返回值都是一段javascript程式碼)
    var url = "http://flightQuery.com/jsonp/flightResult.aspx?code=CA1998&callback=flightHandler";
    // 建立script標籤,設定其屬性
    var script = document.createElement('script');
   script.setAttribute("type","text/javascript");
   script.setAttribute('src', url);
    // 把script標籤加入head,此時呼叫開始
    document.getElementsByTagName('head')[0].appendChild(script); 
    </script>
</head>
<body>
</body>
</html>
複製程式碼

這樣不直接把遠端js檔案寫死,而是編碼實現動態查詢,而這也正是jsonp客戶端實現的核心部分,其重點也就在於如何完成jsonp呼叫的全過程。
看到呼叫的url中傳遞了一個code引數,告訴伺服器我要查的是CA1998次航班的資訊,而callback引數則告訴伺服器,本地回撥函式叫做flightHandler,所以請把查詢結果傳入這個函式中供本地呼叫。
程式B中test.js的程式碼:

flightHandler({
"code": "CA1998",
"price": 1780,
"tickets": 5
});

我們看到,傳遞給flightHandler函式的是一個json,它描述了航班的基本資訊。執行一下頁面,成功彈出提示視窗,jsonp的執行全過程順利完成!

6.JQuery和ExtJs實現JSONP
(1).JQuery的JSONP跨域實現
<1>.$.getJSON
jQuery框架支援JSONP,可以使用$.getJSON(url,[data],[callback])方法(詳細可以參考http://api.jquery.com/jQuery.getJSON/)。繼續修改程式A的程式碼,改用jQuery的getJSON方法來實現(下面的例子沒用用到向服務傳參,所以只寫了getJSON(url,[callback])):

<script type="text/javascript" src="http://code.jquery.com/jquery-latest.js"></script>
<script type="text/javascript">
$.getJSON("http://localhost:20002/MyService.ashx?callback=?",function(data){
alert(data.name + " is a a" + data.sex);
});
</script>

要注意的是在url的後面必須新增一個callback引數,這樣getJSON方法才會知道是用JSONP方式去訪問服務,callback後面的那個問號是內部自動生成的一個回撥函式名。這個函式名可以debug看一下,比如jQuery17207481773362960666_1332575486681。

<2>.$.ajax
假如說我們想指定自己的回撥函式名,或者說服務上規定了固定回撥函式名該怎麼辦呢?可以使用$.ajax方法來實現(引數較多,詳細可以參見http://api.jquery.com/jQuery.ajax)。

複製程式碼
<script type="text/javascript" src="http://code.jquery.com/jquery-latest.js"></script>
<script type="text/javascript">
$.ajax({
    url:"http://localhost:20002/MyService.ashx?callback=?", 
    dataType:"jsonp",
    jsonpCallback:"person",
    success:function(data){
        alert(data.name + " is a a" + data.sex);
    }
});
</script>
複製程式碼

jsonpCallback就是指定我們自己的回撥方法名person,遠端服務接受callback引數的值就不再是自動生成的回撥名,而是person。dataType是指定按照JSOPN方式訪問遠端服務。

(2).ExtJs的JSONP跨域實現
<1>.Ext.data.JsonP.request
ExtJS4.1的Ext.data.JsonP.request實現跨域訪問:

複製程式碼
//跨域請求,MsgUrl為其他站點地址
Ext.data.JsonP.request({
    url: MsgUrl + '/Home/InitializeComet',
    timeout: 300000,
    params: { loginId: LoginId },
    callbackKey: "jsonPCallback",
    success: function(result) {
        if (result.rettype == 'true') {
            me.Comet.privateToken = result.msg;
            me.RegisterComet();
        } else {
            alert(result.msg);
        }
    },
    failure: function(result) {
        alert(result);
    }
});                
複製程式碼

其中跨域請求的要點是類名:Ext.data.JsonP和callbackKey的引數。
“jsonPCallback”該名稱將作為Jsonp請求的方法名傳遞到伺服器端,獲取該請求的URL:
http://10.0.13.64:89/Home/InitializeComet?loginId=0001&jsonPCallback=Ext.data.JsonP.callback1&_dc=1370687739484

<2>.Ext.data.ScriptTagProxy

複製程式碼
var ss = new Ext.data.ScriptTagProxy({
  //url: 'http://10.128.3.104/edi/rest/GetBillCaseInfo',
    url: 'testjson.do',
    callbackParam: "_callback",
    headers: { 'Authorization': 'Basic YWRtaW46YWRtaW4xMjM=' }
});
ss.load({ '_out': 'json' },
  new Ext.data.JsonReader(
    { root: "ROWSET.ROW" },
    [{ name: 'CaseCode', mapping: 'CaseCode' },{ name: 'CaseName', mapping: 'CaseName'}]),
    function (recordsBlock, arg, isok) {
        alert(Ext.encode(recordsBlock));
        alert(Ext.encode(recordsBlock.records[0].data));
}
);
Ext.Ajax.request({
    url: 'http://10.128.3.104/edi/rest/GetBillCaseInfo',
    //url: 'testjson.do',
    scriptTag: true,
    success: function (req) {
        alert(req.responseText);
    },
    failure: function (req) {
        alert(req.responseText);
    },
    headers: { 'Authorization': 'Basic YWRtaW46YWRtaW4xMjM=' },
    params: { _out: 'json' }
});
複製程式碼

7.AJAX與JSONP的異同
(1).Ajax和Jsonp是兩種技術。
Ajax和Jsonp這兩種技術在呼叫方式上“看起來”很像,目的也一樣,都是請求一個URL,然後把伺服器返回的資料進行處理,因此JQuery和EXT等框架都把Jsonp作為Ajax的一種形式進行了封裝;
(2).Ajax和Jsonp實現原理不同。
Ajax和Jsonp在本質實現上有差別。Ajax的核心是通過XmlHttpRequest獲取非本頁內容,而Jsonp的核心則是動態新增<script>標籤來呼叫伺服器提供的js指令碼。所以說,其實Ajax與jsonp的區別不在於是否跨域,ajax通過服務端代理一樣可以實現跨域,Jsonp本身也不排斥同域的資料的獲取;
(3).Ajax和Jsonp都是非強制性協議。
Jsonp是一種方式或者說非強制性協議,如同Ajax一樣,它也不一定非要用Json格式來傳遞資料,如果你願意,字串都行,只不過這樣不利於用Jsonp提供公開服務。
總而言之,Jsonp不是Ajax的一個特例,哪怕Jquery、Ext等巨頭把Jsonp封裝進了Ajax,也不能改變這一點!