zoukankan      html  css  js  c++  java
  • 利用script标签实现的跨域名AJAX请求(ExtJS)

    在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事件监听请求结束事件,并且将返回的结果显示到页面上。

    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]')";);

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

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

    什么是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这样的,在应用的过程中出现了不兼容的情况。
    

      

  • 相关阅读:
    POJ 1144 Network(割点)
    POJ 3177 Redundant Paths & POJ 3352 Road Construction(双连通分量)
    ASCII码
    数组
    Java语法基础
    eclipse汉化过程
    指针
    面向对象
    第一课JAVA开发环境配置
    初学编写JAVA程序
  • 原文地址:https://www.cnblogs.com/zhuawang/p/2815505.html
Copyright © 2011-2022 走看看