1. 程式人生 > >跨域解決方案JSONP

跨域解決方案JSONP

什麼是跨域

老生常談的問題了。下面列出一個表格:

牢記:只要協議schema ,域名,子域名,埠有一個不同的都是跨域!

而且跨域問題上,域僅僅是通過URL的首部來識別的,而不會去嘗試判斷兩個域是否在同一個IP上(這涉及到DNS解析)

下面介紹幾種能實現跨域的方法

影象Ping

JS高程提到的一個方法,利用的是img的src可以跨域。

var img= new Image();
img.onload=img.onerror=function(){
    console.log('Done~!');

};

img.src='http://www.example.com/test?name=Nicolas'
;

這個方法唯一的能力就是可以知道是否接受到了響應,但是瀏覽器無法得到任何資訊,但是可以請求伺服器,比如可以請求伺服器做一個刪除的操作,或者建立的操作,因為querystring可以通過img.src提交給伺服器。

僅僅是 瀏覽器 –> 伺服器

1.只能傳送get請求,並且無法獲得任何伺服器的響應。
2. 僅僅是單向的資料傳遞,瀏覽器-->伺服器

JSONP

JSON with Padding
JSONP 兩部分組成: 回撥函式+資料
JSONP中的回撥函式
響應到來的時候,應該在頁面中呼叫的函式。
JSON就是資料
資料是傳入回撥函式中的JSON資料。

JSONP的原理

動態新增一個<script>標籤,而script標籤的src屬性是沒有跨域的限制的。 和img的src一樣可以不受限制從其他域載入資源。我們不能跨域請求資料,但是可以引入不同域的指令碼檔案。 所以我們JSONP中,src是我們請求伺服器的url,一般是這樣的形式:

http://crossdomain.com/jsonServerResponse?jsonp=jsonpCallback

用法

動態script元素來使用,用js動態生成script標籤來進行跨域操作了。

<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>

我們看到,我們在客戶端做的事情,就是定義一個回撥函式,在請求完畢後執行它,然後在這個函式裡面處理接收到的響應資料。
看到URL請求引數,也可以發現,我們需要將函式名傳遞給callback這個請求引數。

分析

其實這裡叫callback還是jsonp是由服務端決定的,服務端會根據接收到的這個請求,生成所需的json資料,當然你還可以附加一大堆請求引數到url上。然後服務端根據這些引數生成json資料傳入到回撥函式中。

返回的格式如:
jsonpCallback({msg:”xxx”})

執行流程

客戶端註冊一個callback,這裡是jsonpCallback,然後把callback的名字傳給伺服器,服務端得到這個函式名之後,要用這樣的方法

jsonpCallback(...)

包裹要輸出的json內容。 此時伺服器生成的json資料才能被客戶端正確接收。
然後以JS語法的方式生成一個function,function的名字就是傳遞上來的引數callback的值,這裡的值”jsonCallback”
最後將json資料直接入參的方式,放置到function中,這樣就生成了一段js語法的文件,並返回給客戶端。
返回一個script文件

<script>
...
</script>

客戶端解析script標籤,並執行返回的js文件,此時js文件資料,作為引數傳入到了客戶端預先定義好的callback函式裡。

可以說jsonp的方式原理上和<script src=”http://跨域/…xx.js”&ht;</script>是一致的(qq空間就是大量採用這種方式來實現跨域資料交換的)。JSONP是一種指令碼注入(Script Injection)行為,所以有一定的安全隱患。

服務端程式碼可能是這樣的

<?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)";//輸出,result作為引數傳遞

服務端做的事
我們其實是要querystring,獲取url中傳遞的引數,這裡是callback,然後把callback的值’jsonpCallback’包裹要傳遞的json字串。也就像這樣:
jsonpCallback({“name”:”xxx”,”id”:23});

返回的就是用這個callback的值包裹的json資料。
然後這個回撥函式就會被執行。因為你建立了script標籤然後函式在裡面被呼叫執行了。

jQuery的實現

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

jQuery原始碼部分:

//Build temporary JSONP function
if( s.dataType === "json" && (s.data && jsre.test(s.data) || jsre.test(s.url)) ) {
    jsonp = s.jsonpCallback || ("jsonp" + jsc++);

    // Replace the =? sequence both in the query string and the data
    if ( s.data ) {
    s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1");
    }
 /*...*/
 if ( s.cache === false && type === "GET" ) {
var ts = now();

// try replacing _= if it is there
var ret = s.url.replace(rts, "$1_=" + ts + "$2");

// if nothing was replaced, add timestamp to the end
s.url = ret + ((ret === s.url) ? (rquery.test(s.url) ? "&" : "?") + "_=" + ts : "");
}

// If data is available, append data to url for get requests
if ( s.data && type === "GET" ) {
s.url += (rquery.test(s.url) ? "&" : "?") + s.data;
}

// Watch for a new set of requests
if ( s.global && ! jQuery.active++ ) {
jQuery.event.trigger( "ajaxStart" );
}

// Matches an absolute URL, and saves the domain
var parts = rurl.exec( s.url ),
remote = parts && (parts[1] && parts[1] !== location.protocol || parts[2] !== location.host);

// If we're requesting a remote document
// and trying to load JSON or Script with a GET
if ( s.dataType === "script" && type === "GET" && remote ) {
var head = document.getElementsByTagName("head")[0] || document.documentElement;
var script = document.createElement("script");
script.src = s.url;

// Handle Script loading
if ( !jsonp ) {
var done = false;

// Attach handlers for all browsers
script.onload = script.onreadystatechange = function() {
 if ( !done && (!this.readyState ||
 this.readyState === "loaded" || this.readyState === "complete") ) {
 done = true;
 success();
 complete();

 // Handle memory leak in IE
 script.onload = script.onreadystatechange = null;
 if ( head && script.parentNode ) {
 head.removeChild( script );
 }
 }
};
}

// Use insertBefore instead of appendChild to circumvent an IE6 bug.
// This arises when a base node is used (#2709 and #4378).
head.insertBefore( script, head.firstChild );

// We handle everything using the script element injection
return undefined; 

}

jQuery一開始先判斷JSON型別的呼叫,然後為本次呼叫建立了臨時的JSONP方法,並且添加了一個隨機的數字(源於日期值)
然後建立了script標籤,構造這個script片段,並且追加到了原文件的head標籤後面。
還有就是判斷瀏覽器的指令碼onreadystatechange事件,判斷readyState是loaded或complete兩個都要判斷。
NOTE
因為無論是哪個屬性都表示資源已經可用了。有時候readyState會停在“loaded”有時候會跳過”loaded”直接完成)

JSONP的優點

  1. JSON可讀性好,在JS中容易處理
  2. 比XML輕了很多
  3. PHP對JSON的支援也不錯

JSONP的弊端

JSONP很好用,但是它有如下的缺點:

  1. JSONP是從其他域中載入程式碼執行的,如果其他域不安全(伺服器端不安全),很有可能在響應中夾帶一些惡意程式碼,此時除了完全放棄JSONP呼叫外沒有辦法。
  2. 其次,確定JSONP是否請求成功不容易。因為如果動態指令碼插入有效那麼就執行呼叫,如果無效就靜默失敗,失敗沒有任何提示~
  3. 用eval()解析也是容易出現安全問題