解決跨域問題的利器——JSONP

Asynchronous JavaScript and XML (Ajax ) 是驅動新一代 Web 站點(流行術語爲 Web 2.0 站點)的關鍵技術。Ajax 容許在不干擾 Web 應用程序的顯示和行爲的狀況下在後臺進行數據檢索。使用 XMLHttpRequest 函數獲取數據,它是一種 API,容許客戶端 JavaScript 經過 HTTP 鏈接到遠程服務器。Ajax 也是許多 mashup 的驅動力,它可未來自多個地方的內容集成爲單一 Web 應用程序。javascript

不過,因爲受到瀏覽器的限制,該方法不容許跨域通訊。若是嘗試從不一樣的域請求數據,會出現安全錯誤。若是能控制數 據駐留的遠程服務器而且每一個請求都前往同一域,就能夠避免這些安全錯誤。可是,若是僅停留在本身的服務器上,Web 應用程序還有什麼用處呢?若是須要從多個第三方服務器收集數據時,又該怎麼辦?php

 

理解同源策略限制html

同源策略阻止從一個域上加載的腳本獲取或操做另外一個域上的文檔屬性。也就是說,受到請求的 URL 的域必須與當前 Web 頁面的域相同。這意味着瀏覽器隔離來自不一樣源的內容,以防止它們之間的操做。這個瀏覽器策略很舊,從 Netscape Navigator 2.0 版本開始就存在。java

克服該限制的一個相對簡單的方法是讓 Web 頁面向它源自的 Web 服務器請求數據,而且讓 Web 服務器像代理同樣將請求轉發給真正的第三方服務器。儘管該技術得到了廣泛使用,但它是不可伸縮的。另外一種方式是使用框架要素在當前 Web 頁面中建立新區域,而且使用 GET 請求獲取任何第三方資源。不過,獲取資源後,框架中的內容會受到同源策略的限制。jquery

克服該限制更理想方法是在 Web 頁面中插入動態腳本元素,該頁面源指向其餘域中的服務 URL 而且在自身腳本中獲取數據。腳本加載時它開始執行。該方法是可行的,由於同源策略不阻止動態腳本插入,而且將腳本看做是從提供 Web 頁面的域上加載的。但若是該腳本嘗試從另外一個域上加載文檔,就不會成功。幸運的是,經過添加 JavaScript Object Notation (JSON) 能夠改進該技術。web

一、什麼是JSONP?ajax

要了解JSONP,不得不提一下JSON,那麼什麼是JSON ?json

JSON is a subset of the object literal notation of JavaScript. Since JSON is a subset of JavaScript, it can be used in the language with no muss or fuss.跨域

JSONP(JSON with Padding)是一個非官方的協議,它容許在服務器端集成Script tags返回至客戶端,經過javascript callback的形式實現跨域訪問(這僅僅是JSONP簡單的實現形式)。瀏覽器

二、JSONP有什麼用?

因爲同源策略的限制,XmlHttpRequest只容許請求當前源(域名、協議、端口)的資源,爲了實現跨域請求,能夠經過script標籤實現跨域請求,而後在服務端輸出JSON數據並執行回調函數,從而解決了跨域的數據請求。

三、如何使用JSONP?

下邊這一DEMO其實是JSONP的簡單表現形式,在客戶端聲明回調函數以後,客戶端經過script標籤向服務器跨域請求數據,而後服務端返回相應的數據並動態執行回調函數。

HTML代碼 (任一 ):

Html代碼 

<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />  
<script type="text/javascript">  
    function jsonpCallback(result) {  
        //alert(result);  
        for(var i in result) {  
            alert(i+":"+result[i]);//循環輸出a:1,b:2,etc.  
        }  
    }  
    var JSONP=document.createElement("script");  
    JSONP.type="text/javascript";  
    JSONP.src="http://crossdomain.com/services.php?callback=jsonpCallback";  
    document.getElementsByTagName("head")[0].appendChild(JSONP);  
</script>

或者

Html代碼 

<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />  
<script type="text/javascript">  
    function jsonpCallback(result) {  
        alert(result.a);  
        alert(result.b);  
        alert(result.c);  
        for(var i in result) {  
            alert(i+":"+result[i]);//循環輸出a:1,b:2,etc.  
        }  
    }  
</script>  
<script type="text/javascript" src="http://crossdomain.com/services.php?callback=jsonpCallback"></script>

 

JavaScript的連接,必須在function的下面。

服務端PHP代碼 (services.php):

Php代碼 

<?php  
  
//服務端返回JSON數據  
$arr=array('a'=>1,'b'=>2,'c'=>3,'d'=>4,'e'=>5);  
$result=json_encode($arr);  
//echo $_GET['callback'].'("Hello,World!")';  
//echo $_GET['callback']."($result)";  
//動態執行回調函數  
$callback=$_GET['callback'];  
echo $callback."($result)";

若是將上述JS客戶端代碼用jQuery的方法來實現,也很是簡單。

$.getJSON
$.ajax
$.get

客戶端JS代碼在jQuery中的實現方式1:

Js代碼 

<script type="text/javascript" src="jquery.js"></script>  
<script type="text/javascript">  
    $.getJSON("http://crossdomain.com/services.php?callback=?",  
    function(result) {  
        for(var i in result) {  
            alert(i+":"+result[i]);//循環輸出a:1,b:2,etc.  
        }  
    });  
</script>

 客戶端JS代碼在jQuery中的實現方式2:

Js代碼 

<script type="text/javascript" src="jquery.js"></script>  
<script type="text/javascript">  
    $.ajax({  
        url:"http://crossdomain.com/services.php",  
        dataType:'jsonp',  
        data:'',  
        jsonp:'callback',  
        success:function(result) {  
            for(var i in result) {  
                alert(i+":"+result[i]);//循環輸出a:1,b:2,etc.  
            }  
        },  
        timeout:3000  
    });  
</script>

客戶端JS代碼在jQuery中的實現方式3:

Js代碼 

<script type="text/javascript" src="jquery.js"></script>  
<script type="text/javascript">  
    $.get('http://crossdomain.com/services.php?callback=?', {name: encodeURIComponent('tester')}, function (json) { for(var i in json) alert(i+":"+json[i]); }, 'jsonp');  
</script>

其中 jsonCallback 是客戶端註冊的,獲取 跨域服務器 上的json數據 後,回調的函數。
http://crossdomain.com/services.php?callback=jsonpCallback
這個 url 是跨域服務 器取 json 數據的接口,參數爲回調函數的名字,返回的格式爲

Js代碼 

jsonpCallback({msg:'this is json data'})

Jsonp原理: 
首先在客戶端註冊一個callback, 而後把callback的名字傳給服務器。

此時,服務器先生成 json 數據。
而後以 javascript 語法的方式,生成一個function , function 名字就是傳遞上來的參數 jsonp.

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

客戶端瀏覽器,解析script標籤,並執行返回的 javascript 文檔,此時數據做爲參數,傳入到了客戶端預先定義好的 callback 函數裏.(動態執行回調函數)

 

使用JSON的優勢在於:

  • 比XML輕了不少,沒有那麼多冗餘的東西。
  • JSON也是具備很好的可讀性的,可是一般返回的都是壓縮事後的。不像XML這樣的瀏覽器能夠直接顯示,瀏覽器對於JSON的格式化的顯示就須要藉助一些插件了。
  • 在JavaScript中處理JSON很簡單。
  • 其餘語言例如PHP對於JSON的支持也不錯。

JSON也有一些劣勢:

  • JSON在服務端語言的支持不像XML那麼普遍,不過JSON.org上提供不少語言的庫。
  • 若是你使用eval()來解析的話,會容易出現安全問題。

儘管如此,JSON的優勢仍是很明顯的。他是Ajax數據交互的很理想的數據格式。

 

主要提示:

JSONP 是構建 mashup 的強大技術,但不幸的是,它並非全部跨域通訊需求的萬靈藥。它有一些缺陷,在提交開發資源以前必須認真考慮它們。

第一,也是最重要的一點,沒有關於 JSONP 調用的錯誤處理。若是動態腳本插入有效,就執行調用;若是無效,就靜默失敗。失敗是沒有任何提示的。例如,不能從服務器捕捉到 404 錯誤,也不能取消或從新開始請求。不過,等待一段時間尚未響應的話,就不用理它了。(將來的 jQuery 版本可能有終止 JSONP 請求的特性)。

JSONP 的另外一個主要缺陷是被不信任的服務使用時會很危險。由於 JSONP 服務返回打包在函數調用中的 JSON 響應,而函數調用是由瀏覽器執行的,這使宿主 Web 應用程序更容易受到各種攻擊。若是打算使用 JSONP 服務,瞭解它能形成的威脅很是重要。

 

關聯:

征服 Ajax 應用程序的安全威脅

防止僞造跨站請求

以上內容,偏概念一些。下面咱們直接show出代碼。

 

實戰篇

參考地址 http://kb.cnblogs.com/page/139725/

其實網上關於JSONP的講解有不少,但卻千篇一概,並且雲裏霧裏,對於不少剛接觸的人來說理解起來有些困難,小可不才,試着用本身的方式來闡釋一下這個問題,看看是否有幫助。

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

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

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

  四、恰巧咱們已經知道有一種叫作JSON的純字符數據格式能夠簡潔的描述複雜數據,更妙的是JSON還被js原生支持,因此在客戶端幾乎能夠爲所欲爲的處理這種格式的數據;

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

  六、客戶端在對JSON文件調用成功以後,也就得到了本身所需的數據,剩下的就是按照本身需求進行處理和展示了,這種獲取遠程數據的方式看起來很是像AJAX,但其實並不同。

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

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

  JSONP的客戶端具體實現:

  無論jQuery也好,ExtJs也罷,又或者是其餘支持jsonp的框架,他們幕後所作的工做都是同樣的,下面我來按部就班的說明一下jsonp在客戶端的實現:

  一、咱們知道,哪怕跨域js文件中的代碼(固然指符合web腳本安全策略的),web頁面也是能夠無條件執行的。

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

alert('我是遠程文件');

  本地服務器localserver.com下有個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></title>
    <script type="text/javascript" src="http://remoteserver.com/remote.js"></script>
</head>
<body>
 
</body>
</html>

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

  二、如今咱們在jsonp.html頁面定義一個函數,而後在遠程remote.js中傳入數據進行調用。

  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></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頁面的代碼:

<!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的執行全過程順利完成!

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

  什麼?你用的是jQuery,想知道jQuery如何實現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時(仍是忍不住吐槽,雖然jquery也把jsonp納入了ajax,但其實它們真的不是一回事兒),自動幫你生成回調函數並把數據取出來供success屬性方法來調用,是否是很爽呀?

  好啦,寫到這裏,我已經無力再寫下去,又困又累,得趕忙睡覺。朋友們要是看這不錯,以爲有啓發,給點個「推薦」唄!因爲實在比較簡單,因此就再也不提供demo源碼下載了。

  沒想到上了博客園的頭條推薦。看到你們對這篇文章的承認和評論,仍是很開心的,這裏針對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,也不能改變這一點!

相關文章
相關標籤/搜索