利用script标签实现的跨域名AJAX请求(ExtJS)

在AJAX应用环境中,由于安全的原因,浏览器不允许XMLHttpRequest组件请求跨域资源。在很多情况下,这个限制给我来带来的诸多不便。很多同行,研究了各种各样的解决方案:

1. 通过修改document.domain和隐藏的IFrame来实现跨域请求。这种方案可能是最简单的一种跨域请求的方案,但是它同样是一种限制最大的方 案。首先,它只能实现在同一个顶级域名下的跨域请求;另外,当在一个页面中还包含有其它的IFrame时,可能还会产生安全性异常,拒绝访问。

2.通过请求当前域的代理,由服务器代理去访问另一个域的资源。XMLHttpRequest通过请求本域内的一个服务器资源,将要访问的目标资源提供给服务器,交由服务器去代理访问目标资源。这种方案,可以实现完全的跨域访问,但是开发,请求过程的消费会比较大。

3. 通过HTML中可以请求跨域资源的标签引用来达到目的,比如Image,Script,LINK这些标签。在这些标签中,Script无疑是最合适的。在 请求每一个脚本资源时,浏览器都会去解析并运行脚本文件内定义的函数,或需要马上执行的JavaScript代码,我们可以通过服务器返回一段脚本或 JSON对象,在浏览器解析执行,从而达到跨域请求的目的。使用script标签来实现跨域请求,只能使用get方法请求服务器资源。并且传参的长度也受 到地址栏长度的限制。

这里,我们来介绍第三种方案。先来看一段在网络上找到的例子(具体来源已经不得而知了,如果有人知道请提供原文链接,谢谢):

JavaScript:

   1:

 var

 scriptBlock = document.createElement("script"

);
   2:

  
   3:

 function

 StartGet()
   4:

 {
   5:

     scriptBlock.src = ""

;
   6:

     scriptBlock.src = "http://Domain2/GetData.aspx"

;
   7:

     scriptBlock.type = "text/javascript"

;
   8:

     scriptBlock.language = "javascript"

;
   9:

     document.getElementsByTagName("head"

)[0].appendChild(scriptBlock);
  10:

     scriptBlock.onreadystatechange = ReturnData;
  11:

 }
  12:

  
  13:

 function

 ReturnData()
  14:

 {
  15:

     //alert(scriptBlock.readyState);

  16:

  
  17:

     //uninitialized        Object is not initialized with data. 

  18:

     //loading            Object is loading its data. 

  19:

     //loaded            Object has finished loading its data. 

  20:

     //interactive       User can interact with the object even though it is not fully loaded. 

  21:

     //complete          Object is completely initialized. 

  22:

  
  23:

     if

("loaded"

 == scriptBlock.readyState)
  24:

     {
  25:

         var

 div = document.getElementById("htmldiv"

);
  26:

         div.innerHTML = a.project[0].name; //a是返回的json里面的变量

  27:

     }    
  28:

 }

通过调用StartGet方法,发送一个跨域请求。用onreadystatechange事件监听请求结束事件,并且将返回的结果显示到页面上。

Thumbsup 在这个事件函数中,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]')"

;);

通过服务器发起对回调函数的调用,从而完成整个跨域请求过程。

 

转载:http://www.cnblogs.com/hjf1223/archive/2008/05/31/1211221.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值