先前的概念中對JSON還是比較熟悉,對JSONP不是特別的清楚,整理完相關知識發現才豁然開朗。簡單的說JSON是一種數據交換格式,而JSONP是一種非官方跨域數據交互協議。JSON是“暗號”,而JSONP則是接頭方式。一個是描述信息的格式,一個是信息傳遞雙方約定的方法。
一、什么是JSON
JSON(JavaScript Object Notation) 是一種輕量級的數據交換格式。 易於人閱讀和編寫。同時也易於機器解析和生成。 它基於JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999的一個子集。 JSON采用完全獨立於語言的文本格式,但是也使用了類似於C語言家族的習慣(包括C, C++, C#, Java, JavaScript, Perl, Python等)。 這些特性使JSON成為理想的數據交換語言。
JSON建構於兩種結構:
-
“名稱/值”對的集合(A collection of name/value pairs)。不同的語言中,它被理解為對象(object),紀錄(record),結構(struct),字典(dictionary),哈希表(hash table),有鍵列表(keyed list),或者關聯數組 (associative array)。
-
值的有序列表(An ordered list of values)。在大部分語言中,它被理解為數組(array)。
這些都是常見的數據結構。事實上大部分現代計算機語言都以某種形式支持它們。這使得一種數據格式在同樣基於這些結構的編程語言之間交換成為可能。
JSON具有以下這些形式:
對象是一個無序的“‘名稱/值’對”集合。一個對象以“{”(左括號)開始,“}”(右括號)結束。每個“名稱”后跟一個“:”(冒號);“‘名稱/值’ 對”之間使用“,”(逗號)分隔。
數組是值(value)的有序集合。一個數組以“[”(左中括號)開始,“]”(右中括號)結束。值之間使用“,”(逗號)分隔。
值(value)可以是雙引號括起來的字符串(string)、數值(number)、true、false、 null、對象(object)或者數組(array)。這些結構可以嵌套。
字符串(string)是由雙引號包圍的任意數量Unicode字符的集合,使用反斜線轉義。一個字符(character)即一個單獨的字符串(character string)。
字符串(string)與C或者Java的字符串非常相似。
數值(number)也與C或者Java的數值非常相似。除去未曾使用的八進制與十六進制格式。除去一些編碼細節。
空白可以加入到任何符號之間。 以下描述了完整的語言。
JSON的優點:
-
基於純文本,跨平台傳遞極其簡單;
-
Javascript原生支持,后台語言幾乎全部支持;
-
輕量級數據格式,占用字符數量極少,特別適合互聯網傳遞;
-
可讀性較強,雖然比不上XML那么一目了然,但在合理的依次縮進之后還是很容易識別的;
-
容易編寫和解析,當然前提是你要知道數據結構;
JSON實例:
// 描述一個人 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;
二、什么是JSONP?
JSONP(JSON with Padding)是資料格式 JSON 的一種“使用模式”,可以讓網頁從別的網域要資料。由於同源策略,一般來說位於 server1.example.com 的網頁無法與不是 server1.example.com 的服務器溝通,而 HTML 的 <script> 元素是一個例外。利用<script> 元素的這個開放策略,網頁可以得到從其他來源動態產生的 JSON 資料,而這種使用模式就是所謂的 JSONP。用 JSONP 抓到的資料並不是 JSON,而是任意的 JavaScript,用 JavaScript 直譯器執行而不是用 JSON 解析器解析。
為了理解這種模式的原理,先想像有一個回傳 JSON 文件的 URL,而 JavaScript 程式可以用 XMLHttpRequest 跟這個 URL 要資料。假設我們的 URL 是http://server2.example.com/RetrieveUser?UserId=xxx 。假設小明的 UserId 是 1823,且當瀏覽器透過 URL 傳小明的 UserId,也就是抓取http://server2.example.com/RetrieveUser?UserId=1823 ,得到:
{"Name": "小明", "Id" : 1823, "Rank": 7}
這個 JSON 資料可能是依據傳過去 URL 的查詢參數動態產生的。
這個時候,把 <script> 元素的 src 屬性設成一個回傳 JSON 的 URL 是可以想像的,這也代表從 HTML 頁面透過 script 元素抓取 JSON 是可能的。
然而,一份 JSON 文件並不是一個 JavaScript 程式。為了讓瀏覽器可以在 <script> 元素執行,從 src 里 URL 回傳的必須是可執行的 JavaScript。在 JSONP 的使用模式里,該 URL 回傳的是由函數呼叫包起來的動態生成 JSON,這就是JSONP 的“填充(padding)”或是“前輟(prefix)”的由來。
慣例上瀏覽器提供回調函數的名稱當作送至服務器的請求中命名查詢參數的一部份,例如:
<script type="text/javascript" src="http://server2.example.com/RetrieveUser?UserId=1823&jsonp=parseResponse"> </script>
服務器會在傳給瀏覽器前將 JSON 數據填充到回調函數(parseResponse)中。瀏覽器得到的回應已不是單純的資料敘述而是一個腳本。在本例中,瀏覽器得到的是:
parseResponse({"Name": "Cheeso", "Id" : 1823, "Rank": 7})
雖然這個填充(前輟)“通常”是瀏覽器執行背景中定義的某個回調函數,它也可以是變量賦值、if 敘述或者是其他 JavaScript 敘述。JSONP 要求(也就是使用 JSONP 模式的請求)的回應不是 JSON 也不被當作 JSON 解析——回傳內容可以是任意的運算式,甚至不需要有任何的 JSON,不過慣例上填充部份還是會觸發函數調用的一小段 JavaScript 片段,而這個函數呼叫是作用在 JSON 格式的資料上的。
另一種說法—典型的 JSONP 就是把既有的 JSON API 用函數呼叫包起來以達到跨域存取的解法。為了要啟動一個 JSONP 呼叫(或者說,使用這個模式),你需要一個 script 元素。因此,瀏覽器必須為每一個 JSONP 要求加(或是重用)一個新的、有所需 src 值的 <script> 元素到 HTML DOM 里—或者說是“注入”這個元素。瀏覽器執行該元素,抓取 src 里的 URL,並執行回傳的 JSON。也因為這樣,JSON 被稱作是一種“讓使用者利用 script 元素注入的方式繞開同源策略”的方法。
使用遠端網站的 script 標簽會讓遠端網站得以注入任何的內容至網站里。如果遠端的網站有 JavaScript 注入漏洞,原來的網站也會受到影響.現在有一個正在進行計划在定義所謂的 JSON-P 嚴格安全子集,使瀏覽器可以對 MIME 類別是“application/json-p”請求做強制處理。如果回應不能被解析為嚴格的 JSON-P,瀏覽器可以丟出一個錯誤或忽略整個回應。
粗略的 JSONP 部署很容易受到跨網站的偽造要求(CSRF/XSRF)的攻擊。因為 HTML <script> 標簽在瀏覽器里不遵守同源策略,惡意網頁可以要求並取得屬於其他網站的 JSON 資料。當使用者正登入那個其他網站時,上述狀況使得該惡意網站得以在惡意網站的環境下操作該 JSON 資料,可能泄漏使用者的密碼或是其他敏感資料。
只有在該 JSON 資料含有不該泄漏給第三方的隱密資料,且服務器僅靠瀏覽器的同源策略阻擋不正常要求的時候這才會是問題。若服務器自己決定要求的專有性,並只在要求正常的情況下輸出資料則沒有問題。只靠 Cookie 並不夠決定要求是合法的,這很容易受到跨網站的偽造要求攻擊。
JSONP的客戶端具體實現:
下面來說明一下jsonp在客戶端的實現:
1、我們知道,哪怕跨域js文件中的代碼(當然指符合web腳本安全策略的),web頁面也是可以無條件執行的。
遠程服務器remoteserver.com根目錄下有個remote.js文件代碼如下:
alert('我是遠程文件');
本地服務器localserver.com下有個jsonp.html頁面代碼如下:
<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 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的服務者都要面對很多服務對象,而這些服務對象各自的本地函數都不相同啊?我們接着往下看。
聰明的開發者很容易想到,只要服務端提供的js腳本是動態生成的就行了唄,這樣調用者可以傳一個參數過去告訴服務端“我想要一段調用XXX函數的js代碼,請你返回給我”,於是服務器就可以按照客戶端的需求來生成js腳本並響應了。
看jsonp.html頁面的代碼:
<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對JSONP的實現
我們依然沿用上面那個航班信息查詢的例子,假定返回jsonp結果不變:
<!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時自動幫你生成回調函數並把數據取出來供success屬性方法來調用。
ajax與jsonp的異同
-
ajax和jsonp這兩種技術在調用方式上“看起來”很像,目的也一樣,都是請求一個url,然后把服務器返回的數據進行處理,因此jquery和ext等框架都把jsonp作為ajax的一種形式進行了封裝;
-
但ajax和jsonp其實本質上是不同的東西。ajax的核心是通過XmlHttpRequest獲取非本頁內容,而jsonp的核心則是動態添加<script>標簽來調用服務器提供的js腳本。
-
所以說,其實ajax與jsonp的區別不在於是否跨域,ajax通過服務端代理一樣可以實現跨域,jsonp本身也不排斥同域的數據的獲取。
-
還有就是,jsonp是一種方式或者說非強制性協議,如同ajax一樣,它也不一定非要用json格式來傳遞數據,如果你願意,字符串都行,只不過這樣不利於用jsonp提供公開服務。
總而言之,jsonp不是ajax的一個特例,哪怕jquery等巨頭把jsonp封裝進了ajax,也不能改變着一點!