淺析Ajax跨域原理及JQuery中的實現分析

雲霏霏發表於2014-12-04

  AJAX 的出現使得網頁可以通過在後臺與伺服器進行少量資料交換,實現網頁的區域性重新整理。但是出於安全的考慮,ajax不允許跨域通訊。如果嘗試從不同的域請求資料,就會出現錯誤。如果能控制資料駐留的遠端伺服器並且每個請求都前往同一域,就可以避免這些安全錯誤。但是,如果僅停留在自己的伺服器上,Web 應用程式還有什麼用處呢?如果需要從多個第三方伺服器收集資料時,又該怎麼辦?

 

 一、關於ajax跨域的思考

  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。

 

 二、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/

 宣告:本部落格原創文字只代表本人工作中在某一時間內總結的觀點或結論,與本人所在單位沒有直接利益關係。非商業,未授權,貼子請以現狀保留,轉載時必須保留此段宣告,且在文章頁面明顯位置給出原文連線。

如果大家感覺我的博文對大家有幫助,請推薦支援一把,給我寫作的動力。

 

相關文章