AJAX 的出現使得網頁可以通過在后台與服務器進行少量數據交換,實現網頁的局部刷新。但是出於安全的考慮,ajax不允許跨域通信。如果嘗試從不同的域請求數據,就會出現錯誤。如果能控制數據駐留的遠程服務器並且每個請求都前往同一域,就可以避免這些安全錯誤。但是,如果僅停留在自己的服務器上,Web 應用程序還有什么用處呢?如果需要從多個第三方服務器收集數據時,又該怎么辦?
1、Ajax為什么不能跨域?到底是卡在哪個環節了?(下面項目中具體說,這里先說下結論)。 Ajax其實就是向服務器發送一個GET或POST請求,然后取得服務器響應結果,返回客戶端。理論上這是沒有任何問題的,然而普通ajax跨域請求,在服務器端不會有任何問題,只是服務端響應數據返回給瀏覽器的時候,瀏覽器根據響應頭的Access-Control-Allow-Origin字段的值來判斷是否有權限獲取數據,一般情況下,服務器端如果沒有在這個字段做特殊處理的話,跨域是沒有權限訪問的,所以響應數據被瀏覽器給攔截了,所以在ajax回調函數里是獲取不到數據的(來自園友補充)。所以現在ajax跨域的問題可以轉化為數據怎么拿回客戶端的問題。
2、既然不能直接訪問第三方站點,我們可以在服務器上面做代理,通過ajax向代理發送請求,代理獲得數據后在返回給客戶端,當然這是一種解決辦法,但是一次請求要從客戶端經過代理到第三方站點,然后再原路返回,響應速度是個問題。
3、我們發現我們可以將一些js、css等文件放在第三方的服務器上面,如CDN等來加快網頁的打開速度,這樣是沒有任何問題的,也就是說web頁面可以加載放在任意站點的js、css、圖片等資源,不會受到"跨域"的影響。這個時候,我們會想到:既然我們可以調用第三方站點的js,那么如果我們將數據放到第三方站點的js中不就可以將數據帶到客戶端了嗎?
下面我們來做一個實驗,來驗證一下我們的猜想成不成立:
打開Visual Studio,新建一個Web項目,這里用WebForm,然后我們在項目中添加一個名為remoteJs的js文件,寫入如下代碼:
function GetRemoteData() { return "remote data"; }
很簡單,就一個方法,返回一個字符串,下面我們來寫一個客戶端調用,既然是跨域,那就寫個html靜態頁面來測試吧,新建local.html,輸入以下代碼:
<!DOCTYPE html> <html> <head> <title>本地站點</title> <meta charset="UTF-8"> <script type="text/javascript" src="http://localhost:4071/remoteJs.js"></script> </head> <script type="text/javascript"> var data = GetRemoteData(); alert(data); </script> <body> </body> </html>
讓我們的Web項目跑起來,然后打開local.html,可以看到彈出一個窗口,顯示信息remote data。這里證明我們的想法是正確的。接下來的問題是,我們如何根據需要發送請求和獲取請求的結果呢?下面我們來認識一下JSONP。
1、什么是JSONP
JSONP(JSON with Padding)是JSON的一種“使用模式”,可用於解決主流瀏覽器的跨域數據訪問的問題。其核心思想是利用JS標簽里面的跨域特性進行跨域數據訪問,在JS標簽里面存在的是一個跨域的URL,實際執行的時候通過這個URL獲得一段字符串,這段返回的字符串必須是一個合法的JS調用,通過EVAL這個字符串來完成對獲得的數據的處理。
JSONP是一個非官方的協議,它允許在服務器端集成Script tags返回至客戶端,通過javascript callback的形式實現跨域訪問(這僅僅是JSONP簡單的實現形式)。
2、JSONP的實現
下面我們通過一個例子來說明一下JSONP是如何實現ajax跨域請求的。這里我們模擬圖書館圖書的查詢,在剛剛我們建立的web項目里面添加一個名為SearchBook的一般處理程序,寫入如下代碼:
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace BookLibrary { public class SearchBook : IHttpHandler { public void ProcessRequest(HttpContext context) { context.Response.ContentType = "text/plain"; string callback = context.Request["callback"]; context.Response.Write(callback + "({'BookName':'English','Pages':562})"); } public bool IsReusable { get { return false; } } } }
暫時先不解釋,我們寫完客戶端看到效果后在詳細說明,然后修改剛剛的local.html,代碼如下:
<!DOCTYPE html> <html> <head> <title>跨域請求</title> <meta charset="UTF-8"> </head> <body> <input type="button" value="發送請求" onclick="GetAjaxData();" /> </body> <script type="text/javascript"> var GetData = function (data) { alert(data.BookName + " " + data.Pages); }; function GetAjaxData(){ var url = "http://localhost:4071/SearchBook.ashx?callback=GetData"; var script = document.createElement('script'); script.setAttribute('src', url); document.getElementsByTagName('head')[0].appendChild(script); } </script> </html>
這個html頁面有一個按鈕,綁定方法GetAjaxData,當我們單擊發送請求時,就會向Web站點發送請求,獲取查詢的數據,我們讓Web站點跑起來,然后打開local.html,單擊按鈕,看到彈出如下信息:
我們成功的取得了web站點的數據,實現了跨域請求。下面我們來說一下他的實現原理:
var url = "http://localhost:4071/SearchBook.ashx?callback=GetData";
這一行代碼我們定義了請求的url,問號前面的是web站點一般處理程序SearchBook的地址,問號后面我們傳入了一個參數callback,值為GetData,也就是我們上面定義的方法名,及回調函數名稱。當然我們可以傳入更多的參數。
var script = document.createElement('script'); script.setAttribute('src', url); document.getElementsByTagName('head')[0].appendChild(script);
這三行代碼就是添加script節點,url指向第三方站點,執行結果如圖:
那么我們剛剛寫的一般處理程序返回的結果就不用說了吧,他就是返回一個字符串,內容為:
看到這里清楚了吧,就是第三方站點生成一個對回調函數的調用,傳入查詢結果,然后通過<script>加載到客戶端執行。看到這里是不是想到了什么?是不是和C#里面的委托有些共同點?整體流程如圖:
可以看到,整個過程,本地站點一直處於主動的地位,主動的發送請求,主動的加載遠程js.而第三方站點則處於被動的響應。
3、普通Ajax請求在哪個環節出錯了
下面,我們用JQuery的ajax來說明一下ajax請求到底是卡在哪個環節了,修改GetAjaxData方法如下:
$.ajax({ type: "get", async: false, url: "http://localhost:4071/SearchBook.ashx", dataType: "text", success: function (data) { alert(data.BookName + " " + data.Pages); }, error: function () { alert('fail'); } });
在SearchBook里面context.Response.Write(callback + "({'BookName':'English','Pages':562})");這行下端點,然后運行,會發現可以走到斷點,然后就出錯了。
4、用JQuery實現ajax跨域
其實JQuery里面也封裝了跨域的ajax方法,我們來看一下上面的方法用JQuery怎么寫:
<script type="text/javascript"> function GetAjaxData() { $.ajax({ type: "get", async: false, url: "http://localhost:4071/SearchBook.ashx", dataType: "jsonp", jsonp: "callback",//傳遞給請求處理程序或頁面的,標識jsonp回調函數名(一般為:callback) jsonpCallback: "GetData",//callback的function名稱 success: function (data) { alert(data.BookName + " " + data.Pages); }, error: function () { alert('fail'); } }); } </script>
注意,JQuery寫法里面Url后面就不用再寫?來傳遞參數了,jsonp的值相當於?后面的值及參數名稱,jsonpCallback的值就是參數的value.success就是執行成功后調用的方法。
哎,不對啊,怎么沒有GetData方法了?JQuery到底是怎么實現的呢?下面我們來調試一下JQuery,來看一下里面是怎么實現的,調試js,當然還是要用Chrome,看圖:
這張圖中,我們看到有個對象s,在做url拼接操作,看到選中那行了吧,?后面拼的是s.jsonp,最后拼接的是callbackName.繼續向下走:
我們看到s.url的值,為拼接后的值,是不是和我們自己寫的js代碼里面的url一模一樣,繼續向下走:
我們看到JQuery又在剛剛的url后面添加下划線等號,然后又跟了一串數字,至於什么用,我也說不上來,繼續向下走:
看到了什么,success方法,哈哈,這是JQuery在變量參數,繼續走:
看到什么了?沒錯,這就是JQuery最終調用的方法,最后一行代碼,添加了script節點,和我自己寫js實現的原理一樣。繼續向下走,看看還有什么:
看到JQuery執行完后,又刪除了剛剛添加的script節點,還是JQuery想的周到啊~~
下面我們來看一下,我們自己寫的js執行后的DOM結構:
看到了吧,script節點會隨着請求的次數一路飆升,不過並不會引起錯誤,刷新后就消失了。而JQuery執行后,DOM結構是不變的。
1、ajax和jsonp這兩種技術在調用方式上“看起來”很像,目的也一樣,都是請求一個url,然后把服務器返回的數據進行處理,因此jquery和ext等框架都把jsonp作為ajax的一種形式進行了封裝;
2、ajax和jsonp其實本質上是不同的東西。ajax的核心是通過XmlHttpRequest獲取非本頁內容,而jsonp的核心則是通過HTTP來動態添加<script>標簽來調用服務器提供的js腳本。
3、其實ajax與jsonp的區別不在於是否跨域,ajax通過服務端代理一樣可以實現跨域,jsonp本身也不排斥同域的數據的獲取。
4、jsonp是一種方式或者說非強制性協議,如同ajax一樣,它也不一定非要用json格式來傳遞數據,如果你願意,字符串都行,只不過這樣不利於用jsonp提供公開服務。
5、jsonp整個過程中,本地站點一直處於主動的地位,主動的發送請求,主動的加載遠程js.而第三方站點則處於被動的響應。
作者:雲霏霏
QQ交流群:243633526
博客地址:http://www.cnblogs.com/yunfeifei/
聲明:本博客原創文字只代表本人工作中在某一時間內總結的觀點或結論,與本人所在單位沒有直接利益關系。非商業,未授權,貼子請以現狀保留,轉載時必須保留此段聲明,且在文章頁面明顯位置給出原文連接。
如果大家感覺我的博文對大家有幫助,請推薦支持一把,給我寫作的動力。