Ajax跨域請求 JSON JSONP





















同源策略和跨域-總結

目錄:
1.同源策略
2.跨域
3.幾種跨域技術
 

1.同源策略
什麼叫同源?
URL由協議、域名、端口和路徑組成,如果兩個URL的協議、域名和端口相同,則表示他們同源。相反,只要協議,域名,端口有任何一個的不同,就被當作是跨域。
e.g. 對於http://store.company.com/dir/page.html進行同源檢測:
URL 結果 原因
http://store.company.com/dir2/other.html 成功 僅路徑不同
http://store.company.com/dir/inner/another.html 成功 僅路徑不同 
https://store.company.com/secure.html 失敗 協議不同
http://store.company.com:81/dir/etc.html 失敗 端口不同
http://news.company.com/dir/other.html 失敗 主機名不同
 
同源策略  Same-Origin-Policy(SOP)
瀏覽器採用同源策略,禁止頁面加載或執行與自身來源不同的域的任何腳本。換句話說瀏覽器禁止的是來自不同源的"document"或腳本,對當前"document"讀取或設置某些屬性。
情景:
比如一個惡意網站的頁面通過iframe嵌入了銀行的登錄頁面(二者不同源),如果沒有同源限制,惡意網頁上的javascript腳本就可以在用戶登錄銀行的時候獲取用戶名和密碼。
 
瀏覽器中有哪些不受同源限制呢?
<script>、<img>、<iframe>、<link>這些包含 src 屬性的標籤可以加載跨域資源。但瀏覽器限制了JavaScript的權限使其不能讀、寫加載的內容。
 

 
2.跨域
跨域是指從一個域的網頁去請求另一個域的資源。比如從http://www.baidu.com/ 頁面去請求 http://www.google.com 的資源。
 

 
3.跨域技術-JSONP
JSONP是什麼?
上面提到過包含src屬性的<script>標籤可以加載跨域資源。 JSONP就是利用<script>標籤的跨域能力實現跨域數據的訪問。
 
JSONP實現的原理
在這之前,先來介紹下我是如何簡單的開啓服務的。
使用python的SimpleHTTPServer模塊(2.x)(3.x中時http.server)The SimpleHTTPServer module has been merged into http.server in Python 3.0.
------------------------------------------------------------------------------------------------
如果你已經用過python,請跳過這裏。
 
>>安裝python(注意設置環境變量 Path -- D:\Program Files\Python2.7.1;)
(驗證python安裝配置完成:打開cmd, 輸入python 有識別)
(cmd 切換工作目錄)
>>在工作目錄中執行
$ python -m SimpleHTTPServer 8088 (2.x)
$ python -m http.server 8088 (3.x)
>>服務啓動後,不要關閉該窗口;否則相當於殺死了該服務進程
 
------------------------------------------------我是分割線---------------------------------------
index.html,在8088端口提供服務
複製代碼
 1 <!doctype html>
 2 <html lang="en" ng-app="simpleApp">
 3 <head>
 4     <meta charset="UTF-8">
 5     <script src="scripts/vendor/angular.min.js"></script>
 6     <script src="scripts/vendor/angular-resource.min.js"></script>
 7     <script src="scripts/controllers/controllers.js"></script>
 8     <script src="scripts/services/services.js"></script>
 9     <link href="styles/bootstrap.css" rel="stylesheet">
10 
11     <!-- [跨域]step1. 通過擁有src屬性的標籤進行跨域調用 <script><img><iframe> -->
12     <script type="text/javascript" src="http://localhost:8090/remote.js"></script>
13 
14     <!-- [跨域]step2. 本地函數被跨域的遠程js調用,並接收遠程js帶來的數據 -->
15     <script type="text/javascript">
16         var localHandler = function(data){
17             alert('我是本地函數,可以被跨域的remote.js文件調用,遠程js帶來的數據是:' + data.result);
18         };
19     </script>
20 
21     <!-- [跨域]step3. JSONP client端的核心:實現動態查詢
22         怎麼樣讓遠程JS知道它應該調用的本地函數叫什麼名字?
23         client傳參告訴server"我想要一段調用xx函數的JS代碼,請返回給我";即client在發送請求URL時,指定回調函數-->
24     <script type="text/javascript">
25         // 得到航班信息查詢結果後的回調函數
26         var flightHandler = function(data){
27             <!-- [跨域]step4. 處理server返回的數據:幾種格式 -->
28             /* JSONArray
29              * flightHandler([
30              * {"code": "CA1998", "price": 1780, "tickets": 5 },
31              * {"code": "CA2001", "price": 2090, "tickets": 10 }
32              * ]);*/
33             for(var i=0; i<data.length; i++){
34                 alert('你查詢的航班結果是:票價 ' + data[i].price + ' 元,' + '餘票 ' + data[i].tickets + ' 張。');
35             }
36 
37             /* JSON
38              flightHandler({ "code": "CA1998", "price": 1780, "tickets": 5 });
39              */
40 //            alert('你查詢的航班結果是:票價 ' + data.price + ' 元,' + '餘票 ' + data.tickets + ' 張。');
41         };
42         // 提供jsonp服務的url地址(不管是什麼類型的地址,最終生成的返回值都是一段javascript代碼)
43         var url = "http://localhost:8090/remote.js?code=CA1998&callback=flightHandler";
44         // 創建script標籤,設置其屬性
45         var script = document.createElement('script');
46         script.setAttribute('src', url);
47         // 把script標籤加入head,此時調用開始
48         document.getElementsByTagName('head')[0].appendChild(script);
49     </script>
50     
51     <title>simpleApp</title>
52 </head>
53 <body>
54     <div ng-view></div>
55 </body>
56 </html>
複製代碼

remote.js,在8090端口提供服務,來模擬不同域的遠程文件(端口不同)

複製代碼
 1 alert("遠程文件");
 2 localHandler({"result":"我是遠程js帶來的數據"});
 3 
 4 //JSON
 5 flightHandler({
 6     "code": "CA1998",
 7     "price": 1780,
 8     "tickets": 5
 9 });
10 
11 //JSONArray
12 flightHandler([{
13     "code": "CA1998",
14     "price": 1780,
15     "tickets": 5
16 }, {
17     "code": "CA2001",
18     "price": 2090,
19     "tickets": 10
20 }]);
複製代碼
 
JSONP的缺點
JSONP只支持 GET 請求。
 
支持JSONP的不同技術
>>AngularJS
複製代碼
1 myUrl ="http://localhost:8090/api/test?callback=JSON_CALLBACK";
2 $http.jsonp(myUrl).success(
3      function(data){
4           alert(data);
5      }     
6 );
複製代碼
1.angularJS中使用$http.jsonp函數
2.指定callback和回調函數名,函數名爲JSON_CALLBACK時,會調用success回調函數,JSON_CALLBACK必須全爲大寫。
3.也可以指定其它回調函數,但必須是定義在window下的全局函數。
4.url中必須加上callback
5.當callback爲JSON_CALLBACK時,只會調用success,即使window中有JSON_CALLBACK函數,也不會調用該函數。
 
>>Ajax
複製代碼
 1 myUrl ="http://localhost:8090/api/test";
 2 $.ajax({
 3   type:"GET",
 4   url:myUrl,
 5   dataType:"jsonp",
 6   jsonp:"callback",
 7   jsonpCallback:"jsonpCallback",
 8   success:function(data){
 9     alert(data.msg);
10   }
11 });
12 function jsonpCallback(data){
13      alert(data);
14 }
複製代碼
JSONP 是通過動態添加<script>標籤來調用服務器的腳本(<script>含有src屬性,src屬性沒有跨域限制);而 Ajax 是通過 XHR(XmlHttpRequest) 對象。兩者並沒有直接關係,以上只是Ajax封裝JSONP的一種方式。
 

 4.跨域技術-CORS











AJAX POST&跨域 解決方案 - CORS

 
 一晃又到新年了,於是開始着手好好整理下自己的文檔,順便把一些自認爲有意義的放在博客上,記錄成點的點滴。
     
 

 
  跨域是我在日常面試中經常會問到的問題,這詞在前端界出現的頻率不低,主要原因還是由於安全限制(同源策略, 即JavaScript或Cookie只能訪問同域下的內容),因爲我們在日常的項目開發時會不可避免的需要進行跨域操作,所以跨域能力也算是前端工程師的基本功之一。
  和大多數跨域的解決方案一樣,JSONP也是我的選擇,可是某天PM的需求變了,某功能需要改成支持POST,因爲傳輸的數據量比較大,GET形式搞不定。所以折騰了下聞名已久的 CORS(跨域資源共享,Cross-Origin Resource Sharing ,這邊文章也就是折騰期間的小記與總結。
     
     

 

概述

  • CORS能做什麼:
     正常使用AJAX會需要正常考慮跨域問題,所以偉大的程序員們又折騰出了一系列跨域問題的解決方案,如JSONP、flash、ifame、xhr2等等。
     本文介紹的CORS就是一套AJAX跨域問題的解決方案。
 
  •  CORS的原理:
     CORS定義一種跨域訪問的機制,可以讓AJAX實現跨域訪問。CORS 允許一個域上的網絡應用向另一個域提交跨域 AJAX 請求。實現此功能非常簡單,只需由服務器發送一個響應標頭即可。
 
  • CORS瀏覽器支持情況如下圖:
       
     
  喜聞樂見、普大喜奔的支持情況,尤其是在 移動終端上,除了opera Mini;
  PC上的現代瀏覽器都能友好的支持,除了IE9-,不過前端工程師對這種情況早應該習慣了...
     
 

CORS啓航

 
  假設我們頁面或者應用已在  http://www.test1.com 上了,而我們打算從  http://www.test2.com 請求提取數據。一般情況下,如果我們直接使用 AJAX 來請求將會失敗,瀏覽器也會返回「源不匹配」的錯誤," 跨域"也就以此由來。
  利用 CORS, http://www.test2.com 只需添加一個標頭,就可以允許來自  http://www.test1.com 的請求,下圖是我在PHP中的 hander() 設置, 「*」號表示允許任何域向我們的服務端提交請求
     
   也可以設置指定的域名,如域名 http://www.test2.com ,那麼就允許來自這個域名的請求
     
     
  當前我設置的header爲「*」,任意一個請求過來之後服務端我們都可以進行處理&響應,那麼在調試工具中可以看到其頭信息設置,其中見紅框中有一項信息是「 Access-Control-Allow-Origin:* 」,表示我們已經啓用CORS,如下圖。
   PS: 由於demo都在我廠的兩臺測試機間完成,外網也不能訪問,所以在這就不提供demo了,見諒
     
   簡單的一個header設置,一個支持跨域&POST請求的server就完成了:)
 
  當然,如果沒有開啓CORS必定失敗的啦,如下圖:
  

問題&小結

  • 剛剛說到的兼容性。CORS是W3C中一項較新的方案,所以部分瀏覽器還沒有對其進行支持或者完美支持,詳情可移至 http://www.w3.org/TR/cors/
  • 安全問題。CORS提供了一種跨域請求方案,但沒有爲安全訪問提供足夠的保障機制,如果你需要信息的絕對安全,不要依賴CORS當中的權限制度,應當使用更多其它的措施來保障,比如OAuth2。
 
  自認爲的cors使用場景:
  • cors在移動終端支持的不錯,可以考慮在移動端全面嘗試;PC上有不兼容和沒有完美支持,所以小心踩坑。當然瀏覽器兼容就是個僞命題,說不準某個瀏覽器的某個版本就完美兼容了,說不準就有點小坑,尼瑪傷不起!~
  • jsonp是get形式,承載的信息量有限,所以信息量較大時CORS是不二選擇;
  • 配合新的JSAPI(fileapi、xhr2等)一起使用,實現強大的新體驗功能。
 
  如果覺得這文章也算用心,請勞駕點右下角的推薦。
  祝2014順利。
 
  最後,有 北京&上海 的朋友想春節後想換工作的,【百度移動雲事業部】期待聰明、勤奮的你 與我聯繫 (JD在頁面右上角)
 
參考資料:
           http://www.w3.org/TR/cors/
           http://caniuse.com/#search=cors
 

JSONP原理優缺點(只能GET不支持POST)


JSONP的優點是:它不像XMLHttpRequest對象實現的Ajax請求那樣受到同源策略的限制;它的兼容性更好,在更加古老的瀏覽器中都 可以運行,不需要XMLHttpRequest或ActiveX的支持;並且在請求完畢後可以通過調用callback的方式回傳結果。


JSONP的缺點則是:它只支持GET請求而不支持POST等其它類型的HTTP請求;它只支持跨域HTTP請求這種情況,不能解決不同域的兩個頁面之間如何進行JavaScript調用的問題。


===============================================================================================================

JSONP原理
JSONP的最基本的原理是:動態添加一個<script>標籤,而script標籤的src屬性是沒有跨域的限制的。這樣說來,這種跨域方式其實與ajax XmlHttpRequest協議無關了。

這樣其實"jQuery AJAX跨域問題"就成了個僞命題,jquery $.ajax方法名有誤導人之嫌。

如果設爲dataType: 'jsonp',這個$.ajax方法就和ajax XmlHttpRequest沒什麼關係了,取而代之的則是JSONP協議。JSONP是一個非官方的協議,它允許在服務器端集成Script tags返回至客戶端,通過javascript callback的形式實現跨域訪問。

JSONP即JSON with Padding。由於同源策略的限制,XmlHttpRequest只允許請求當前源(域名、協議、端口)的資源。如果要進行跨域請求, 我們可以通過使用html的script標記來進行跨域請求,並在響應中返回要執行的script代碼,其中可以直接使用JSON傳遞javascript對象。 這種跨域的通訊方式稱爲JSONP。

jsonCallback 函數jsonp1236827957501(....):是瀏覽器客戶端註冊的,獲取跨域服務器上的json數據後,回調的函數

Jsonp的執行過程如下:

首先在客戶端註冊一個callback (如:'jsoncallback'), 然後把callback的名字(如:jsonp1236827957501)傳給服務器。注意:服務端得到callback的數值後,要用jsonp1236827957501(......)把將要輸出的json內容包括起來,此時,服務器生成 json 數據才能被客戶端正確接收。

然後以 javascript 語法的方式,生成一個function, function 名字就是傳遞上來的參數 'jsoncallback'的值 jsonp1236827957501 .

最後將 json 數據直接以入參的方式,放置到 function 中,這樣就生成了一段 js 語法的文檔,返回給客戶端。

客戶端瀏覽器,解析script標籤,並執行返回的 javascript 文檔,此時javascript文檔數據,作爲參數, 傳入到了客戶端預先定義好的 callback 函數(如上例中jquery $.ajax()方法封裝的的success: function (json))裏。

可以說jsonp的方式原理上和<script src="http://跨域/...xx.js"></script>是一致的(qq空間就是大量採用這種方式來實現跨域數據交換的)。JSONP是一種腳本注入(Script Injection)行爲,所以有一定的安全隱患。










由於Sencha Touch 2這種開發模式的特性,基本決定了它原生的數據交互行爲幾乎只能通過AJAX來實現。 


當然了,通過調用強大的PhoneGap插件然後打包,你可以實現100%的Socket通訊和本地數據庫功能,又或者通過HTML5的WebSocket也可以實現與服務器的通訊和服務端推功能,但這兩種方式都有其侷限性,前者需要PhoneGap支持,後者要求用戶設備必須支持WebSocket,因此都不能算是ST2的原生解決方案,原生的只有AJAX。 

  

說到AJAX就會不可避免的面臨兩個問題,第一個是AJAX以何種格式來交換數據?第二個是跨域的需求如何解決? 這兩個問題目前都有不同的解決方案,比如數據可以用自定義字符串或者用XML來描述,跨域可以通過服務器端代理來解決。 

但到目前爲止最被推崇或者說首選的方案還是用JSON來傳數據,靠JSONP來跨域。而這就是本文將要講述的內容。 

  

JSON和JSONP雖然只有一個字母的差別,但其實他們根本不是一回事兒:JSON是一種數據交換格式,而JSONP是一種依靠開發人員的聰明才智創造出的一種非官方跨域數據交互協議。我們拿最近比較火的諜戰片來打個比方,JSON是地下黨們用來書寫和交換情報的「暗號」,而JSONP則是把用暗號書寫的情報傳遞給自己同志時使用的接頭方式。看到沒?一個是描述信息的格式,一個是信息 傳遞雙方約定的方法。 

什麼是JSON? 



前面簡單說了一下,JSON是一種基於文本的數據交換方式,或者叫做數據描述格式,你是否該選用他首先肯定要關注它所擁有的優點。 



JSON的優點: 

1、基於純文本,跨平臺傳遞極其簡單; 

2、Javascript原生支持,後臺語言幾乎全部支持; 

3、輕量級數據格式,佔用字符數量極少,特別適合互聯網傳遞; 

4、可讀性較強,雖然比不上XML那麼一目瞭然,但在合理的依次縮進之後還是很容易識別的; 

5、容易編寫和解析,當然前提是你要知道數據結構; 

JSON的缺點當然也有,無關緊要的東西不再單獨說明。 



JSON的格式或者叫規則: 

JSON能夠以非常簡單的方式來描述數據結構,XML能做的它都能做,因此在跨平臺方面兩者完全不分伯仲。 

1、JSON只有兩種數據類型描述符,大括號{}和方括號[],其餘英文冒號:是映射符,英文逗號,是分隔符,英文雙引號""是定義符。 

2、大括號{}用來描述一組「不同類型的無序鍵值對集合」(每個鍵值對可以理解爲OOP的屬性描述),方括號[]用來描述一組「相同類型的有序數據集合」(可對應OOP的數組)。 

3、上述兩種集合中若有多個子項,則通過英文逗號,進行分隔。 

4、鍵值對以英文冒號:進行分隔,並且建議鍵名都加上英文雙引號"",以便於不同語言的解析。 

5、JSON內部常用數據類型無非就是字符串、數字、布爾、日期、null 這麼幾個,字符串必須用雙引號引起來,其餘的都不用,日期類型比較特殊,這裏就不展開講述了,只是建議如果客戶端沒有按日期排序功能需求的話,那麼把日期時間直接作爲字符串傳遞就好,可以省去很多麻煩。 



JSON實例: 

Js代碼 
// 描述一個人 
  
var person = { 
    "Name": "Bob", 
    "Age": 32, 
    "Company": "IBM", 
    "Engineer": true 

  
// 獲取這個人的信息 
  
var personAge = person.Age; 
  
// 描述幾個人 
  
var members = [ 
    { 
        "Name": "Bob", 
        "Age": 32, 
        "Company": "IBM", 
        "Engineer": true 
    }, 
    { 
        "Name": "John", 
        "Age": 20, 
        "Company": "Oracle", 
        "Engineer": false 
    }, 
    { 
        "Name": "Henry", 
        "Age": 45, 
        "Company": "Microsoft", 
        "Engineer": false 
    } 

  
// 讀取其中John的公司名稱 
  
var johnsCompany = members[1].Company; 
  
// 描述一次會議 
  
var conference = { 
    "Conference": "Future Marketing", 
    "Date": "2012-6-1", 
    "Address": "Beijing", 
    "Members": 
    [ 
        { 
            "Name": "Bob", 
            "Age": 32, 
            "Company": "IBM", 
            "Engineer": true 
        }, 
        { 
            "Name": "John", 
            "Age": 20, 
            "Company": "Oracle", 
            "Engineer": false 
        }, 
        { 
            "Name": "Henry", 
            "Age": 45, 
            "Company": "Microsoft", 
            "Engineer": false 
        } 
    ] 

  
// 讀取參會者Henry是否工程師 
  
var henryIsAnEngineer = conference.Members[2].Engineer; 



關於JSON,就說這麼多,更多細節請在開發過程中查閱資料深入學習。 



什麼是JSONP? 



先說說JSONP是怎麼產生的: 

1、一個衆所周知的問題,Ajax直接請求普通文件存在跨域無權限訪問的問題,甭管你是靜態頁面、動態網頁、web服務、WCF,只要是跨域請求,一律不準; 

2、不過我們又發現,Web頁面上調用js文件時則不受是否跨域的影響(不僅如此,我們還發現凡是擁有"src"這個屬性的標籤都擁有跨域的能力,比如<script>、<img>、<iframe>); 

3、於是可以判斷,當前階段如果想通過純web端(ActiveX控件、服務端代理、屬於未來的HTML5之Websocket等方式不算)跨域訪問數據就只有一種可能,那就是在遠程服務器上設法把數據裝進js格式的文件裏,供客戶端調用和進一步處理; 

4、恰巧我們已經知道有一種叫做JSON的純字符數據格式可以簡潔的描述複雜數據,更妙的是JSON還被js原生支持,所以在客戶端幾乎可以隨心所欲的處理這種格式的數據; 

5、這樣子解決方案就呼之欲出了,web客戶端通過與調用腳本一模一樣的方式,來調用跨域服務器上動態生成的js格式文件(一般以JSON爲後綴),顯而易見,服務器之所以要動態生成JSON文件,目的就在於把客戶端需要的數據裝入進去。 

6、客戶端在對JSON文件調用成功之後,也就獲得了自己所需的數據,剩下的就是按照自己需求進行處理和展現了,這種獲取遠程數據的方式看起來非常像AJAX,但其實並不一樣。 

7、爲了便於客戶端使用數據,逐漸形成了一種非正式傳輸協議,人們把它稱作JSONP,該協議的一個要點就是允許用戶傳遞一個callback參數給服務端,然後服務端返回數據時會將這個callback參數作爲函數名來包裹住JSON數據,這樣客戶端就可以隨意定製自己的函數來自動處理返回數據了。 

如果對於callback參數如何使用還有些模糊的話,我們後面會有具體的實例來講解。 



JSONP的客戶端具體實現: 

不管jQuery也好,extjs也罷,又或者是其他支持jsonp的框架,他們幕後所做的工作都是一樣的,下面我來循序漸進的說明一下jsonp在客戶端的實現: 



1、我們知道,哪怕跨域js文件中的代碼(當然指符合web腳本安全策略的),web頁面也是可以無條件執行的。 

遠程服務器remoteserver.com根目錄下有個remote.js文件代碼如下: 

alert('我是遠程文件'); 
本地服務器localserver.com下有個jsonp.html頁面代碼如下: 

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></title> 
    <script type="text/javascript" src=" http://remoteserver.com/remote.js "></script> 
</head> 
<body> 
  
</body> 
</html> 

毫無疑問,頁面將會彈出一個提示窗體,顯示跨域調用成功。 



2、現在我們在jsonp.html頁面定義一個函數,然後在遠程remote.js中傳入數據進行調用。 

jsonp.html頁面代碼如下: 

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></title> 
    <script type="text/javascript"> 
    var localHandler = function(data){ 
        alert('我是本地函數,可以被跨域的remote.js文件調用,遠程js帶來的數據是:' + data.result); 
    }; 
    </script> 
    <script type="text/javascript" src=" http://remoteserver.com/remote.js "></script> 
</head> 
<body> 

</body> 
</html> 
remote.js文件代碼如下: 

localHandler({"result":"我是遠程js帶來的數據"}); 
運行之後查看結果,頁面成功彈出提示窗口,顯示本地函數被跨域的遠程js調用成功,並且還接收到了遠程js帶來的數據。很欣喜,跨域遠程獲取數據的目的基本實現了,但是又一個問題出現了,我怎麼讓遠程js知道它應該調用的本地函數叫什麼名字呢?畢竟是jsonp的服務者都要面對很多服務對象,而這些服務對象各自的本地函數都不相同啊?我們接着往下看。 

  

3、聰明的開發者很容易想到,只要服務端提供的js腳本是動態生成的就行了唄,這樣調用者可以傳一個參數過去告訴服務端「我想要一段調用XXX函數的js代碼,請你返回給我」,於是服務器就可以按照客戶端的需求來生成js腳本並響應了。 

看jsonp.html頁面的代碼: 

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></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('src', url); 
    // 把script標籤加入head,此時調用開始 
    document.getElementsByTagName('head')[0].appendChild(script); 
    </script> 
</head> 
<body> 
  
</body> 
</html> 
 
這次的代碼變化比較大,不再直接把遠程js文件寫死,而是編碼實現動態查詢,而這也正是jsonp客戶端實現的核心部分,本例中的重點也就在於如何完成jsonp調用的全過程。 

我們看到調用的url中傳遞了一個code參數,告訴服務器我要查的是CA1998次航班的信息,而callback參數則告訴服務器,我的本地回調函數叫做flightHandler,所以請把查詢結果傳入這個函數中進行調用。 

OK,服務器很聰明,這個叫做flightResult.aspx的頁面生成了一段這樣的代碼提供給jsonp.html(服務端的實現這裏就不演示了,與你選用的語言無關,說到底就是拼接字符串): 

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




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

  

4、到這裏爲止的話,相信你已經能夠理解jsonp的客戶端實現原理了吧?剩下的就是如何把代碼封裝一下,以便於與用戶界面交互,從而實現多次和重複調用。 

什麼?你用的是jQuery,想知道jQuery如何實現jsonp調用?好吧,那我就好人做到底,再給你一段jQuery使用jsonp的代碼(我們依然沿用上面那個航班信息查詢的例子,假定返回jsonp結果不變): 

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>Untitled Page</title> 
      <script type="text/javascript" src=jquery.min.js"></script> 
      <script type="text/javascript"> 
     jQuery(document).ready(function(){ 
        $.ajax({ 
             type: "get", 
             async: false, 
             url: " http://flightQuery.com/jsonp/flightResult.aspx?code=CA1998 ", 
             dataType: "jsonp", 
             jsonp: "callback",//傳遞給請求處理程序或頁面的,用以獲得jsonp回調函數名的參數名(一般默認爲:callback) 
             jsonpCallback:"flightHandler",//自定義的jsonp回調函數名稱,默認爲jQuery自動生成的隨機函數名,也可以寫"?",jQuery會自動爲你處理數據 
             success: function(json){ 
                 alert('您查詢到航班信息:票價: ' + json.price + ' 元,餘票: ' + json.tickets + ' 張。'); 
             }, 
             error: function(){ 
                 alert('fail'); 
             } 
         }); 
     }); 
     </script> 
     </head> 
  <body> 
  </body> 
</html> 
是不是有點奇怪?爲什麼我這次沒有寫flightHandler這個函數呢?而且竟然也運行成功了!哈哈,這就是jQuery的功勞了,jquery在處理jsonp類型的ajax時(還是忍不住吐槽,雖然jquery也把jsonp歸入了ajax,但其實它們真的不是一回事兒),自動幫你生成回調函數並把數據取出來供success屬性方法來調用,是不是很爽呀? 


  
補充: 

  

這裏針對ajax與jsonp的異同再做一些補充說明: 

1、ajax和jsonp這兩種技術在調用方式上「看起來」 很像,目的也一樣,都是請求一個url,然後把服務器返回的數據進行處理,因此jquery和ext等框架都把jsonp作爲ajax的一種形式進行了封裝; 

2、但ajax和jsonp其實本質上是不同的東西。ajax的核心是通過XmlHttpRequest獲取非本頁內容,而jsonp的核心則是動態添加<script>標籤來調用服務器提供的js腳本 。 

3、所以說,其實ajax與jsonp的區別不在於是否跨域,ajax通過服務端代理一樣可以實現跨域,jsonp本身也不排斥同域的數據的獲取。 

4、還有就是,jsonp是一種方式或者說非強制性協議,如同ajax一樣,它也不一定非要用json格式來傳遞數據,如果你願意,字符串都行,只不過這樣不利於用jsonp提供公開服務。 

總而言之,jsonp不是ajax的一個特例,哪怕jquery等巨頭把jsonp封裝進了ajax,也不能改變着一點!






如何解決ajax跨域問題(轉)

由 於此前很少寫前端的代碼(哈哈,不合格的程序員啊),最近項目中用到json作爲系統間交互的手段,自然就伴隨着衆多ajax請求,隨之而來的就是要解決 ajax的跨域問題。本篇將講述一個小白從遇到跨域不知道是跨域問題,到知道是跨域問題不知道如何解決,再到解決跨域問題,最後找到兩種方法解決ajax 跨域問題的全過程。

不知是跨域問題

起 因是這樣的,爲了複用,減少重複開發,單獨開發了一個用戶權限管理系統,共其他系統獲取認證與授權信息,暫且稱之爲A系統;調用A系統以B爲例。在B系統 中用ajax調用A系統系統的接口(數據格式爲json),當時特別困惑,在A系統中訪問相應的url可正常回返json數據,但是在B系統中使用 ajax請求同樣的url則一點兒反應都沒有,好像什麼都沒有發生一樣。這樣反反覆覆改來改去好久都沒能解決,於是求救同事,提醒可能是ajax跨域問 題,於是就將這個問題當做跨域問題來解決了。

知跨域而不知如何解決

知道問題的確切原因,剩下的就是找到解決問題的方法了。google了好久,再次在同事的指點下知道jQuery的ajax有jsonp這樣的屬性可以用來解決跨域的問題。

找到一種解決方式

現在也知道了怎樣來解決跨域問題,餘下的就是實現的細節了。實現的過程中錯誤還是避免不了的。由於不瞭解json和jsonp兩種格式的區別,也犯了錯誤,google了好久才解決。

首先來看看在頁面中如何使用jQuery的ajax解決跨域問題的簡單版:

複製代碼
$(document).ready(function(){
   var url='http://localhost:8080/WorkGroupManagment/open/getGroupById"
       +"?id=1&callback=?';
   $.ajax({
     url:url,
     dataType:'jsonp',
     processData: false, 
     type:'get',
     success:function(data){
       alert(data.name);
     },
     error:function(XMLHttpRequest, textStatus, errorThrown) {
       alert(XMLHttpRequest.status);
       alert(XMLHttpRequest.readyState);
       alert(textStatus);
     }});
   });
複製代碼

這樣寫是完全沒有問題的,起先error的處理函數中僅僅是alert(「error」),爲了進一步弄清楚是什麼原因造成了錯誤,故將處理函數變 爲上面的實現方式。最後一行alert使用爲;parsererror。百思不得其解,繼續google,最終還是在萬能的stackoverflow找 到了答案,鏈接在這裏。原因是jsonp的格式與json格式有着細微的差別,所以在server端的代碼上稍稍有所不同。

比較一下json與jsonp格式的區別:

json格式:
{
    "message":"獲取成功",
    "state":"1",
    "result":{"name":"工作組1","id":1,"description":"11"}
}
jsonp格式:
callback({
    "message":"獲取成功",
    "state":"1",
    "result":{"name":"工作組1","id":1,"description":"11"}
})

看出來區別了吧,在url中callback傳到後臺的參數是神馬callback就是神馬,jsonp比json外面有多了一層,callback()。這樣就知道怎麼處理它了。於是修改後臺代碼。

後臺java代碼最終如下:

複製代碼
@RequestMapping(value = "/getGroupById")
  public String getGroupById(@RequestParam("id") Long id,
      HttpServletRequest request, HttpServletResponse response)
      throws IOException {
    String callback = request.getParameter("callback");
    ReturnObject result = null;
    Group group = null;
    try {
      group = groupService.getGroupById(id);
      result = new ReturnObject(group, "獲取成功", Constants.RESULT_SUCCESS);
    } catch (BusinessException e) {
      e.printStackTrace();
      result = new ReturnObject(group, "獲取失敗", Constants.RESULT_FAILED);
    }
    String json = JsonConverter.bean2Json(result);
    response.setContentType("text/html");
    response.setCharacterEncoding("utf-8");
    PrintWriter out = response.getWriter();
    out.print(callback + "(" + json + ")");
    return null;
  }
複製代碼

注意這裏需要先將查詢結果轉換我json格式,然後用參數callback在json外面再套一層,就變成了jsonp。指定數據類型爲jsonp的ajax就可以做進一步處理了。

雖然這樣解決了跨域問題,還是回顧下造成parsererror的原因。原因在於盲目的把json格式的數據當做jsonp格式的數據讓ajax處理,造成了這個錯誤,此時server端代碼是這樣的:

複製代碼
@RequestMapping(value = "/getGroupById")
  @ResponseBody
  public ReturnObject getGroupById(@RequestParam("id") Long id,
      HttpServletRequest request, HttpServletResponse response){
    String callback = request.getParameter("callback");
    ReturnObject result = null;
    Group group = null;
    try {
      group = groupService.getGroupById(id);
      result = new ReturnObject(group, "獲取成功", Constants.RESULT_SUCCESS);
    } catch (BusinessException e) {
      e.printStackTrace();
      result = new ReturnObject(group, "獲取失敗", Constants.RESULT_FAILED);
    }
    return result;
  }
複製代碼

至此解決ajax跨域問題的第一種方式就告一段落。

追加一種解決方式

追求永無止境,在google的過程中,無意中發現了一個專門用來解決跨域問題的jQuery插件-jquery-jsonp。地址:https://github.com/congmo/jquery-jsonp

有第一種方式的基礎,使用jsonp插件也就比較簡單了,server端代碼無需任何改動。

來看一下如何使用jquery-jsonp插件解決跨域問題吧。

複製代碼
var url="http://localhost:8080/WorkGroupManagment/open/getGroupById"
    +"?id=1&callback=?";
$.jsonp({
  "url": url,
  "success": function(data) {
    $("#current-group").text("當前工作組:"+data.result.name);
  },
  "error": function(d,msg) {
    alert("Could not find user "+msg);
  }
});
複製代碼

 

至此兩種解決跨域問題的方式就全部介紹完畢。

原文地址:http://www.congmo.net/blog/2012/06/27/ajax-cross-domain/

js中幾種實用的跨域方法原理詳解

這裏說的js跨域是指通過js在不同的域之間進行數據傳輸或通信,比如用ajax向一個不同的域請求數據,或者通過js獲取頁面中不同域的框架中(iframe)的數據。只要協議、域名、端口有任何一個不同,都被當作是不同的域。

下表給出了相對http://store.company.com/dir/page.html同源檢測的結果:

QQ截圖20130613230631

要解決跨域的問題,我們可以使用以下幾種方法:

一、通過jsonp跨域

在js中,我們直接用XMLHttpRequest請求不同域上的數據時,是不可以的。但是,在頁面上引入不同域上的js腳本文件卻是可以的,jsonp正是利用這個特性來實現的。

比如,有個a.html頁面,它裏面的代碼需要利用ajax獲取一個不同域上的json數據,假設這個json數據地址是http://example.com/data.php,那麼a.html中的代碼就可以這樣:

QQ截圖20130613230631

我們看到獲取數據的地址後面還有一個callback參數,按慣例是用這個參數名,但是你用其他的也一樣。當然如果獲取數據的jsonp地址頁面不是你自己能控制的,就得按照提供數據的那一方的規定格式來操作了。

因爲是當做一個js文件來引入的,所以http://example.com/data.php返回的必須是一個能執行的js文件,所以這個頁面的php代碼可能是這樣的:

QQ截圖20130613230631

最終那個頁面輸出的結果是:

QQ截圖20130613230631

所以通過http://example.com/data.php?callback=dosomething得到的js文件,就是我們之前定義的dosomething函數,並且它的參數就是我們需要的json數據,這樣我們就跨域獲得了我們需要的數據。

這樣jsonp的原理就很清楚了,通過script標籤引入一個js文件,這個js文件載入成功後會執行我們在url參數中指定的函數,並且會把我們需要的json數據作爲參數傳入。所以jsonp是需要服務器端的頁面進行相應的配合的。

知道jsonp跨域的原理後我們就可以用js動態生成script標籤來進行跨域操作了,而不用特意的手動的書寫那些script標籤。如果你的頁面使用jquery,那麼通過它封裝的方法就能很方便的來進行jsonp操作了。

QQ截圖20130613230631

原理是一樣的,只不過我們不需要手動的插入script標籤以及定義回掉函數。jquery會自動生成一個全局函數來替換callback=?中的問號,之後獲取到數據後又會自動銷燬,實際上就是起一個臨時代理函數的作用。$.getJSON方法會自動判斷是否跨域,不跨域的話,就調用普通的ajax方法;跨域的話,則會以異步加載js文件的形式來調用jsonp的回調函數。

 

2、通過修改document.domain來跨子域

瀏覽器都有一個同源策略,其限制之一就是第一種方法中我們說的不能通過ajax的方法去請求不同源中的文檔。 它的第二個限制是瀏覽器中不同域的框架之間是不能進行js的交互操作的。有一點需要說明,不同的框架之間(父子或同輩),是能夠獲取到彼此的window對象的,但蛋疼的是你卻不能使用獲取到的window對象的屬性和方法(html5中的postMessage方法是一個例外,還有些瀏覽器比如ie6也可以使用top、parent等少數幾個屬性),總之,你可以當做是隻能獲取到一個幾乎無用的window對象。比如,有一個頁面,它的地址是http://www.example.com/a.html  , 在這個頁面裏面有一個iframe,它的src是http://example.com/b.html, 很顯然,這個頁面與它裏面的iframe框架是不同域的,所以我們是無法通過在頁面中書寫js代碼來獲取iframe中的東西的:

  QQ截圖20130613230631

這個時候,document.domain就可以派上用場了,我們只要把http://www.example.com/a.html 和 http://example.com/b.html這兩個頁面的document.domain都設成相同的域名就可以了。但要注意的是,document.domain的設置是有限制的,我們只能把document.domain設置成自身或更高一級的父域,且主域必須相同。例如:a.b.example.com 中某個文檔的document.domain 可以設成a.b.example.com、b.example.com 、example.com中的任意一個,但是不可以設成 c.a.b.example.com,因爲這是當前域的子域,也不可以設成baidu.com,因爲主域已經不相同了。

在頁面 http://www.example.com/a.html 中設置document.domain:

QQ截圖20130613230631

在頁面 http://example.com/b.html 中也設置document.domain,而且這也是必須的,雖然這個文檔的domain就是example.com,但是還是必須顯示的設置document.domain的值:

QQ截圖20130613230631

這樣我們就可以通過js訪問到iframe中的各種屬性和對象了。

不過如果你想在http://www.example.com/a.html 頁面中通過ajax直接請求http://example.com/b.html 頁面,即使你設置了相同的document.domain也還是不行的,所以修改document.domain的方法只適用於不同子域的框架間的交互。如果你想通過ajax的方法去與不同子域的頁面交互,除了使用jsonp的方法外,還可以用一個隱藏的iframe來做一個代理。原理就是讓這個iframe載入一個與你想要通過ajax獲取數據的目標頁面處在相同的域的頁面,所以這個iframe中的頁面是可以正常使用ajax去獲取你要的數據的,然後就是通過我們剛剛講得修改document.domain的方法,讓我們能通過js完全控制這個iframe,這樣我們就可以讓iframe去發送ajax請求,然後收到的數據我們也可以獲得了。

 

3、使用window.name來進行跨域

window對象有個name屬性,該屬性有個特徵:即在一個窗口(window)的生命週期內,窗口載入的所有的頁面都是共享一個window.name的,每個頁面對window.name都有讀寫的權限,window.name是持久存在一個窗口載入過的所有頁面中的,並不會因新頁面的載入而進行重置。

比如:有一個頁面a.html,它裏面有這樣的代碼:

QQ截圖20130613230631

再看看b.html頁面的代碼:

QQ截圖2013061323063並不會因新頁面的載入而進行重置。</p>
<p style= 比如:有一個頁面a.html,它裏面有這樣的代碼:

QQ截圖20130613230631

再看看b.html頁面的代碼:

QQ截圖20130613230631

a.html頁面載入後3秒,跳轉到了b.html頁面,結果爲:

QQ截圖20130613230631

我們看到在b.html頁面上成功獲取到了它的上一個頁面a.html給window.name設置的值。如果在之後所有載入的頁面都沒對window.name進行修改的話,那麼所有這些頁面獲取到的window.name的值都是a.html頁面設置的那個值。當然,如果有需要,其中的任何一個頁面都可以對window.name的值進行修改。注意,window.name的值只能是字符串的形式,這個字符串的大小最大能允許2M左右甚至更大的一個容量,具體取決於不同的瀏覽器,但一般是夠用了。

上面的例子中,我們用到的頁面a.html和b.html是處於同一個域的,但是即使a.html與b.html處於不同的域中,上述結論同樣是適用的,這也正是利用window.name進行跨域的原理。

下面就來看一看具體是怎麼樣通過window.name來跨域獲取數據的。還是舉例說明。

比如有一個www.example.com/a.html頁面,需要通過a.html頁面裏的js來獲取另一個位於不同域上的頁面www.cnblogs.com/data.html裏的數據。

data.html頁面裏的代碼很簡單,就是給當前的window.name設置一個a.html頁面想要得到的數據值。data.html裏的代碼:

QQ截圖20130613230631

那麼在a.html頁面中,我們怎麼把data.html頁面載入進來呢?顯然我們不能直接在a.html頁面中通過改變window.location來載入data.html頁面,因爲我們想要即使a.html頁面不跳轉也能得到data.html裏的數據。答案就是在a.html頁面中使用一個隱藏的iframe來充當一箇中間人角色,由iframe去獲取data.html的數據,然後a.html再去得到iframe獲取到的數據。

充當中間人的iframe想要獲取到data.html的通過window.name設置的數據,只需要把這個iframe的src設爲www.cnblogs.com/data.html就行了。然後a.html想要得到iframe所獲取到的數據,也就是想要得到iframe的window.name的值,還必須把這個iframe的src設成跟a.html頁面同一個域才行,不然根據前面講的同源策略,a.html是不能訪問到iframe裏的window.name屬性的。這就是整個跨域過程。

看下a.html頁面的代碼:

上面的代碼只是最簡單的原理演示代碼,你可以對使用js封裝上面的過程,比如動態的創建iframe,動態的註冊各種事件等等,當然爲了安全,獲取完數據後,還可以銷燬作爲代理的iframe。網上也有很多類似的現成代碼,有興趣的可以去找一下。

通過window.name來進行跨域,就是這樣子的。

 

4、使用HTML5中新引進的window.postMessage方法來跨域傳送數據

window.postMessage(message,targetOrigin)  方法是html5新引進的特性,可以使用它來向其它的window對象發送消息,無論這個window對象是屬於同源或不同源,目前IE8+、FireFox、Chrome、Opera等瀏覽器都已經支持window.postMessage方法。

調用postMessage方法的window對象是指要接收消息的那一個window對象,該方法的第一個參數message爲要發送的消息,類型只能爲字符串;第二個參數targetOrigin用來限定接收消息的那個window對象所在的域,如果不想限定域,可以使用通配符 *  。

需要接收消息的window對象,可是通過監聽自身的message事件來獲取傳過來的消息,消息內容儲存在該事件對象的data屬性中。

上面所說的向其他window對象發送消息,其實就是指一個頁面有幾個框架的那種情況,因爲每一個框架都有一個window對象。在討論第二種方法的時候,我們說過,不同域的框架間是可以獲取到對方的window對象的,而且也可以使用window.postMessage這個方法。下面看一個簡單的示例,有兩個頁面

QQ截圖20130613230631

相關文章
相關標籤/搜索