利用script標籤實現的跨網域名稱AJAX請求(ExtJS)

來源:互聯網
上載者:User

在AJAX應用環境中,由於安全的原因,瀏覽器不允許XMLHttpRequest組件請求跨域資源。在很多情況下,這個限制給我來帶來的諸多不便。很多同行,研究了各種各樣的解決方案:

1.通過修改document.domain和隱藏的IFrame來實現跨域請求。這種方案可能是最簡單的一種跨域請求的方案,但是它同樣是一種限制最大的方案。首先,它只能實現在同一個頂級網域名稱下的跨域請求;另外,當在一個頁面中還包含有其它的IFrame時,可能還會產生安全性異常,拒絕訪問。

2.通過請求當前域的代理,由伺服器代理去訪問另一個域的資源。XMLHttpRequest通過請求本域內的一個伺服器資源,將要訪問的目標資源提供給伺服器,交由伺服器去代理訪問目標資源。這種方案,可以實現完全的跨域訪問,但是開發,請求過程的消費會比較大。

3.通過HTML中可以請求跨域資源的標籤引用來達到目的,比如Image,Script,LINK這些標籤。在這些標籤中,Script無疑是最合適的。在請求每一個指令碼資源時,瀏覽器都會去解析並運行指令檔內定義的函數,或需要馬上執行的JavaScript代碼,我們可以通過伺服器返回一段指令碼或JSON對象,在瀏覽器解析執行,從而達到跨域請求的目的。使用script標籤來實現跨域請求,只能使用get方法請求伺服器資源。並且傳參的長度也受到地址欄長度的限制。

這裡,我們來介紹第三種方案。先來看一段在網路上找到的例子(具體來源已經不得而知了,如果有人知道請提供原文連結,謝謝):

JavaScript:

var scriptBlock = document.createElement("script");
StartGet();

function StartGet() {
scriptBlock.src = "";
scriptBlock.src = "http://localhost:6144/";
scriptBlock.type = "text/javascript";
scriptBlock.language = "javascript";
document.getElementsByTagName("head")[0].appendChild(scriptBlock);
scriptBlock.onreadystatechange = ReturnData;
}

function ReturnData() {
//alert(scriptBlock.readyState);
//uninitialized Object is not initialized with data.
//loading Object is loading its data.
//loaded Object has finished loading its data.
//interactive User can interact with the object even though it is not fully loaded.
//complete Object is completely initialized.

if ("loaded" == scriptBlock.readyState) {
var div = document.getElementById("htmldiv");
div.innerHTML = a.project[0].name; //a是返回的json裡面的變數
}
}

通過調用StartGet方法,發送一個跨域請求。用onreadystatechange事件監聽請求結束事件,並且將返回的結果顯示到頁面上。

在這個事件函數中,a的對象是從何而來的呢?它就是通過http://Domain2/GetData.aspx輸出的一段JSON對象,並被瀏覽器解析過。看看下面的伺服器端的代碼就應該明白了。

ASP.NET:

protected void Page_Load(object sender, EventArgs e)
{
   Response.Write("var a = {'project':[{'name':'a1'},{'name':'a2'}]};");
}

伺服器通過這段代碼輸出一段JSON對象的指令碼內容。

上面的例子就可以完整的描述通過Script來進跨域請求資源。但是,這裡還有一個問題script標籤的onreadystatechange事件並不是W3C標準的事件定義,只在IE中有效。下面的例子,它是ExtJS團隊給出的對Ext.data.Connection類的擴充,以支援跨域的請求。通過它的擴充我們可以方便的使用Ext.Ajax來請求跨域資源,並且保證的資源回收的安全。下面先看看它的代碼:

Ext.lib.Ajax.isCrossDomain = function(u) {
    var match = /(?:(\w*:)\/\/)?([\w\.]*(?::\d*)?)/.exec(u);
    if (!match[1]) return false; // No protocol, not cross-domain
    return (match[1] != location.protocol) || (match[2] != location.host);
};
 
Ext.override(Ext.data.Connection, {
 
    request : function(o){
        if(this.fireEvent("beforerequest", this, o) !== false){
            var p = o.params;
 
            if(typeof p == "function"){
                p = p.call(o.scope||window, o);
            }
            if(typeof p == "object"){
                p = Ext.urlEncode(p);
            }
            if(this.extraParams){
                var extras = Ext.urlEncode(this.extraParams);
                p = p ? (p + '&' + extras) : extras;
            }
 
            var url = o.url || this.url;
            if(typeof url == 'function'){
                url = url.call(o.scope||window, o);
            }
 
            if(o.form){
                var form = Ext.getDom(o.form);
                url = url || form.action;
 
                var enctype = form.getAttribute("enctype");
                if(o.isUpload || (enctype && enctype.toLowerCase() == 'multipart/form-data')){
                    return this.doFormUpload(o, p, url);
                }
                var f = Ext.lib.Ajax.serializeForm(form);
                p = p ? (p + '&' + f) : f;
            }
 
            var hs = o.headers;
            if(this.defaultHeaders){
                hs = Ext.apply(hs || {}, this.defaultHeaders);
                if(!o.headers){
                    o.headers = hs;
                }
            }
 
            var cb = {
                success: this.handleResponse,
                failure: this.handleFailure,
                scope: this,
                argument: {options: o},
                timeout : this.timeout
            };
 
            var method = o.method||this.method||(p ? "POST" : "GET");
 
            if(method == 'GET' && (this.disableCaching && o.disableCaching !== false) || o.disableCaching === true){
                url += (url.indexOf('?') != -1 ? '&' : '?') + '_dc=' + (new Date().getTime());
            }
 
            if(typeof o.autoAbort == 'boolean'){ // options gets top priority
                if(o.autoAbort){
                    this.abort();
                }
            }else if(this.autoAbort !== false){
                this.abort();
            }
            if((method == 'GET' && p) || o.xmlData || o.jsonData){
                url += (url.indexOf('?') != -1 ? '&' : '?') + p;
                p = '';
            }
            if (o.scriptTag || this.scriptTag || Ext.lib.Ajax.isCrossDomain(url)) {
               this.transId = this.scriptRequest(method, url, cb, p, o);
            } else {
               this.transId = Ext.lib.Ajax.request(method, url, cb, p, o);
            }
            return this.transId;
        }else{
            Ext.callback(o.callback, o.scope, [o, null, null]);
            return null;
        }
    },
    
    scriptRequest : function(method, url, cb, data, options) {
        var transId = ++Ext.data.ScriptTagProxy.TRANS_ID;
        var trans = {
            id : transId,
            cb : options.callbackName || "stcCallback"+transId,
            scriptId : "stcScript"+transId,
            options : options
        };
 
        url += (url.indexOf("?") != -1 ? "&" : "?") + data + String.format("&{0}={1}", options.callbackParam || this.callbackParam || 'callback', trans.cb);
 
        var conn = this;
        window[trans.cb] = function(o){
            conn.handleScriptResponse(o, trans);
        };
 
//      Set up the timeout handler
        trans.timeoutId = this.handleScriptFailure.defer(cb.timeout, this, [trans]);
 
        var script = document.createElement("script");
        script.setAttribute("src", url);
        script.setAttribute("type", "text/javascript");
        script.setAttribute("id", trans.scriptId);
        document.getElementsByTagName("head")[0].appendChild(script);
 
        return trans;
    },
 
    handleScriptResponse : function(o, trans){
        this.transId = false;
        this.destroyScriptTrans(trans, true);
        var options = trans.options;
        
//      Attempt to parse a string parameter as XML.
        var doc;
        if (typeof o == 'string') {
            if (window.ActiveXObject) {
                doc = new ActiveXObject("Microsoft.XMLDOM");
                doc.async = "false";
                doc.loadXML(o);
            } else {
                doc = new DOMParser().parseFromString(o,"text/xml");
            }
        }
 
//      Create the bogus XHR
        response = {
            responseObject: o,
            responseText: (typeof o == "object") ? Ext.util.JSON.encode(o) : String(o),
            responseXML: doc,
            argument: options.argument
        }
        this.fireEvent("requestcomplete", this, response, options);
        Ext.callback(options.success, options.scope, [response, options]);
        Ext.callback(options.callback, options.scope, [options, true, response]);
    },
    
    handleScriptFailure: function(trans) {
        this.transId = false;
        this.destroyScriptTrans(trans, false);
        var options = trans.options;
        response = {
            argument:  options.argument,
            status: 500,
            statusText: 'Server failed to respond',
            responseText: ''
        };
        this.fireEvent("requestexception", this, response, options, {
            status: -1,
            statusText: 'communication failure'
        });
        Ext.callback(options.failure, options.scope, [response, options]);
        Ext.callback(options.callback, options.scope, [options, false, response]);
    },
    
    // private
    destroyScriptTrans : function(trans, isLoaded){
        document.getElementsByTagName("head")[0].removeChild(document.getElementById(trans.scriptId));
        clearTimeout(trans.timeoutId);
        if(isLoaded){
            window[trans.cb] = undefined;
            try{
                delete window[trans.cb];
            }catch(e){}
        }else{
            // if hasn't been loaded, wait for load to remove it to prevent script error
            window[trans.cb] = function(){
                window[trans.cb] = undefined;
                try{
                    delete window[trans.cb];
                }catch(e){}
            };
        }
    }
});

在reqeust方法中,做好參數的處理工作後(這些都是原先的實現)。在發送請求時,判斷是否有scriptTag 屬性(值為true),如果scriptTag有定義並且為true,那麼調用scriptRequest 來通過script標籤發送跨域請求。在請求中,它會將所有的參數拼接成地址傳參的方式,並且還有一個callback參數(或自己指定的參數名),用於標識用戶端在接收返回的回調方法(在伺服器端產生的javascript代碼中調用),這個回呼函數會根據不同的請求動態產生,在同一個上下文環境中每次請求的回呼函數名都不一樣。通過指定參數就可以解決在script標籤中沒有onreadystatechange事件定義帶來的麻煩。在出錯處理上,它使用的是逾時的出錯處理,因為沒有事件,所以它會有一個請求逾時時間延遲函數調用來進行資源的回收工作。

經過上面的擴充,我們在使用Ext.Ajax.request方法時,只需要新增一個標誌標識它是一個跨域請求:scriptTag: true 。如下的調用:

Ext.Ajax.request({
    url: 'http://localhost:8080/aspicio/getxml.do',
    params: {
        listId: 'CountryListManager141Grid509',
        format: 'xml'
    },
    scriptTag: true, // Use script tag transport
    success: function(r) {
        console.log(r);
    }
});

下面是一段伺服器端的範例程式碼:

   1: //取得用戶端回呼函數名
   2: string callBack = Reqeust.QueryString("callback");
   3: //其它參數均可以通過Reqeust.QueryString得到。
   4: //向用戶端輸出javascript調用。
   5: Response.Write(callBack + "('[value:0]')";);

通過伺服器發起對回呼函數的調用,從而完成整個跨域請求過程。

 ****************************************************************************

什麼是JSONP協議?JSONP即JSON with Padding。由於同源策略的限制,XmlHttpRequest只允許請求當前源(網域名稱、協議、連接埠)的資源。如果要進行跨域請求,我們可以通過使用html的script標記來進行跨域請求,並在響應中返回要執行的script代碼,其中可以直接使用JSON傳遞javascript對象。這種跨域的通訊方式稱為JSONP。很明顯,JSONP是一種指令碼注入(Script Injection)行為,需要特別注意其安全性。Jquery中的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>jsonp測試例子</title>      <script type="text/javascript" src="http://www.yzswyl.cn/js/jquery.min.js"></script>      <script type="text/javascript">     jQuery(document).ready(function(){         $.ajax({             type: "get",             async: false,             url: "http://www.yzswyl.cn/demos/jsonp.php",             dataType: "jsonp",             jsonp: "callback",//傳遞給請求處理常式或頁面的,用以獲得jsonp回呼函數名的參數名(一般預設為:callback)             jsonpCallback:"feedBackState",//自訂的jsonp回呼函數名稱,預設為jQuery自動產生的隨機函數名              success: function(data){                 var $ul = $("<ul></ul>");                 $.each(data,function(i,v){                     $("<li/>").text(v["id"] + " " + v["name"]).appendTo($ul)                 });                 $("#remote").append($ul);             },             error: function(){                 alert('fail');             }         });     });     </script>     </head>  <body>  遠端資料如下:<br/>  <div id="remote"></div>   </body> </html>服務端代碼(本例採用PHP):<?php$jsonp = $_REQUEST["callback"];$str = '[{"id":"1","name":"測試1"},{"id":"2","name":"測試2"}]';$str = $jsonp . "(" .$str.")";echo $str;?>效果示範:Jsonp的原理和簡單一實例jquery是對其進行了封裝,你可能看不到真正的實現方法,我們用下面的一個例子進行說明:用戶端代碼:<!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" > <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <head>     <title>jsonp測試例子</title>     <script type="text/javascript" src="http://www.yzswyl.cn/js/jquery.min.js"></script>     <script type="text/javascript">     function CallJSONPServer(url){                                 // 調用JSONP伺服器,url為請求伺服器位址            var oldScript =document.getElementById(url);       // 如果頁面中註冊了調用的伺服器,則重新調用        if(oldScript){        oldScript.setAttribute("src",url);        return;        }        var script =document.createElement("script");       // 如果未註冊該伺服器,則註冊並請求之        script.setAttribute("type", "text/javascript");        script.setAttribute("src",url);        script.setAttribute("id", url);        document.body.appendChild(script);    }    function OnJSONPServerResponse(data){        var $ul = $("<ul></ul>");        $.each(data,function(i,v){            $("<li/>").text(v["id"] + " " + v["name"]).appendTo($ul)        });        $("#remote").append($ul);    }     </script>     </head>  <body>  <input type="button" value="點擊擷取遠端資料" onclick="CallJSONPServer('http://www.yzswyl.cn/demos/jsonp_original.php')" />  <div id="remote"></div>   </body> </html>服務端代碼:<?php$str = '[{"id":"1","name":"測試1"},{"id":"2","name":"測試2"}]';$str = "OnJSONPServerResponse(" .$str.")";echo $str;?>效果展示:別的不多說,相信看代碼大家應該明白它是怎麼實現的了。需要注意:1.由於 jquery 在ajax 處理中使用的是utf-8編碼傳遞參數的,所以jsonp處理端用utf-8的編碼最好,這樣省得編碼轉換了,如果不是utf-8記得轉換,否則中文會亂碼。2.請求的服務端url最好不要寫成http://www.yzswyl.cn/?act=add這樣的,應寫全其為:http://www.yzswyl.cn/index.php?act=add這樣的,在應用的過程中出現了不相容的情況。

  

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.