周五同事遇到一个很奇怪的问题,调到下班,虽然问题解决了,但是不知道问题的具体原因,回来翻了翻代码,才发现症结所在,下面就分享出来,供遇到同样问题的同行们参考:

 

先把问题描述一下,做的功能是使用ajax向后台来提交数据,为了向用户进行很好的错误提示,后台中将出现错误时的错误原因返回给前端,前端使用jquery.form.js的ajaxsubmit来提交数据,并在success方法中提示“操作成功”,在error方法中提示错误原因。整个form提交的数据包括一些简单的input和一个文件的上传。下面是代码:

 

前端JSP代码:

Java代码  使用jquery.form.js的ajaxsubmit方法提交数据的Bug使用jquery.form.js的ajaxsubmit方法提交数据的Bug
  1. < form id ="wfAuditForm" method ="post" enctype ="multipart/form-data">  
  2. < input type ="file" name ="posterUrlUploadPath" id ="posterUrlUploadPath" class ="fileUpload" title ="上传图片" />  

 

前端JS代码:

Java代码  使用jquery.form.js的ajaxsubmit方法提交数据的Bug使用jquery.form.js的ajaxsubmit方法提交数据的Bug
  1. $("#wfAuditForm").ajaxSubmit({  
  2.                     type: 'post',  
  3.                     url: "data/resource/picture/save" ,  
  4.                     success: function(data){  
  5.                         alert( "success");  
  6.                         $( "#wfAuditForm").resetForm();  
  7.                     },  
  8.                     error: function(XmlHttpRequest, textStatus, errorThrown){  
  9.                         alert( "error");  
  10.                     }  
  11.                 });  
 

后台:

Java代码  使用jquery.form.js的ajaxsubmit方法提交数据的Bug使用jquery.form.js的ajaxsubmit方法提交数据的Bug
  1. public void save(HttpServletResponse response, HttpServletRequest request, Integer hasUpload,PictureResource pic) {  
  2.      response.setStatus(HttpServletResponse. SC_CONFLICT);  
  3. }  
public void save(HttpServletResponse response, HttpServletRequest request, Integer hasUpload,PictureResource pic) {
     response.setStatus(HttpServletResponse. SC_CONFLICT);
}

 

问题是当提交的数据中file标签里面有值的话(有文件需要上传),即时后台返回的状态码不是200,也会触发js的success方法。

 

当然第一时间想到的是不是返回的状态码不是预期中的,于是使用了firebug对于通信进行了抓包,抓包后发现返回的的确是409(SC_CONFLICT),但是触发的还是success上面。后来意识到这种问题只有当有文件需要上传的时候才会发现,因此怀疑form提交的时候返回了两次response,一次是文件流从客户端到服务端的过程,一次是真正的数据提交的过程,因此使用了wireshark抓了几次包,抓出来的报文显示的确是只返回了一次response(当有文件上传的时候,会出现一个redirect的报文,这个在后面的博文中会有分析),这个说明跟http的网络通信及服务端处理没有关系。

 

问题到底出在什么地方呢?再次回过头来读jquery.form.js的代码,发现这段代码中有这么一段很可疑:

Js代码  使用jquery.form.js的ajaxsubmit方法提交数据的Bug使用jquery.form.js的ajaxsubmit方法提交数据的Bug
  1. var found = false;  
  2.     for ( var j=0; j < files.length; j++)  
  3.         if (files[j])  
  4.             found = true;  
  5.   
  6.     if (options.iframe || found) // options.iframe allows user to force iframe mode  
  7.         fileUpload();  
  8.     else  
  9.         $.ajax(options);  
var found = false;
    for ( var j=0; j < files.length; j++)
        if (files[j])
            found = true;
if (options.iframe || found) // options.iframe allows user to force iframe mode
    fileUpload();
else
    $.ajax(options);

这段代码的第一个for循环是遍历form中所有的file标签,一旦其中的一个file标签里面有值,就将found设置了true。后面的代码就是根据found来进行判断了,如果found为真(有需要上传的文件)将调用fileUpload方法,否则调用jquery的ajax方法。根据上面的现象描述,问题可能出现在fileUpload方法中。下面我们再看fileUpload方法:

Js代码  使用jquery.form.js的ajaxsubmit方法提交数据的Bug使用jquery.form.js的ajaxsubmit方法提交数据的Bug
  1. // private function for handling file uploads (hat tip to YAHOO!)  
  2.     function fileUpload() {  
  3.         var form = $form[0];  
  4.         var opts = $.extend({}, $.ajaxSettings, options);  
  5.           
  6.         var id = 'jqFormIO' + $.fn.ajaxSubmit.counter++;  
  7.         var $io = $('<iframe  />');  
  8.         var io = $io[0];  
  9.         var op8 = $.browser.opera && window.opera.version() < 9;  
  10.         if ($.browser.msie || op8) io.src = 'javascript:false;document.write("");';  
  11.         $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });  
  12.   
  13.         var xhr = { // mock object  
  14.             responseText: null,  
  15.             responseXML: null,  
  16.             status: 0,  
  17.             statusText: 'n/a',  
  18.             getAllResponseHeaders: function() {},  
  19.             getResponseHeader: function() {},  
  20.             setRequestHeader: function() {}  
  21.         };  
  22.           
  23.         var g = opts.global;  
  24.         // trigger ajax global events so that activity/block indicators work like normal  
  25.         if (g && ! $.active++) $.event.trigger("ajaxStart");  
  26.         if (g) $.event.trigger("ajaxSend", [xhr, opts]);  
  27.           
  28.         var cbInvoked = 0;  
  29.         var timedOut = 0;  
  30.           
  31.         // take a breath so that pending repaints get some cpu time before the upload starts  
  32.         setTimeout(function() {  
  33.             $io.appendTo('body');  
  34.             // jQuery's event binding doesn't work for iframe events in IE  
  35.             io.attachEvent ? io.attachEvent('onload', cb) : io.addEventListener('load', cb, false);  
  36.               
  37.             // make sure form attrs are set  
  38.             var encAttr = form.encoding ? 'encoding' : 'enctype';  
  39.             var t = $form.attr('target');  
  40.             $form.attr({  
  41.                 target:   id,  
  42.                 method:  'POST',  
  43.                 encAttr: 'multipart/form-data',  
  44.                 action:   opts.url  
  45.             });  
  46.   
  47.             // support timout  
  48.             if (opts.timeout)  
  49.                 setTimeout(function() { timedOut = true; cb(); }, opts.timeout);  
  50.   
  51.             form.submit();  
  52.             $form.attr('target', t); // reset target  
  53.         }, 10);  
  54.           
  55.         function cb() {  
  56.             if (cbInvoked++) return;  
  57.               
  58.             io.detachEvent ? io.detachEvent('onload', cb) : io.removeEventListener('load', cb, false);  
  59.   
  60.             var ok = true;  
  61.             try {  
  62.                 if (timedOut) throw 'timeout';  
  63.                 // extract the server response from the iframe  
  64.                 var data, doc;  
  65.                 doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document;  
  66.                 xhr.responseText = doc.body ? doc.body.innerHTML : null;  
  67.                 xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;  
  68.                   
  69.                 if (opts.dataType == 'json' || opts.dataType == 'script') {  
  70.                     var ta = doc.getElementsByTagName('textarea')[0];  
  71.                     data = ta ? ta.value : xhr.responseText;  
  72.                     if (opts.dataType == 'json')  
  73.                         eval("data = " + data);  
  74.                     else  
  75.                         $.globalEval(data);  
  76.                 }  
  77.                 else if (opts.dataType == 'xml') {  
  78.                     data = xhr.responseXML;  
  79.                     if (!data && xhr.responseText != null)  
  80.                         data = toXml(xhr.responseText);  
  81.                 }  
  82.                 else {  
  83.                     data = xhr.responseText;  
  84.                 }  
  85.             }  
  86.             catch(e){  
  87.                 ok = false;  
  88.                 $.handleError(opts, xhr, 'error', e);  
  89.             }  
  90.   
  91.             // ordering of these callbacks/triggers is odd, but that's how $.ajax does it  
  92.             if (ok) {  
  93.                 opts.success(data, 'success');  
  94.                 if (g) $.event.trigger("ajaxSuccess", [xhr, opts]);  
  95.             }  
  96.             if (g) $.event.trigger("ajaxComplete", [xhr, opts]);  
  97.             if (g && ! --$.active) $.event.trigger("ajaxStop");  
  98.             if (opts.complete) opts.complete(xhr, ok ? 'success' : 'error');  
  99.   
  100.             // clean up  
  101.             setTimeout(function() {   
  102.                 $io.remove();   
  103.                 xhr.responseXML = null;  
  104.             }, 100);  
  105.         };  
// private function for handling file uploads (hat tip to YAHOO!)
    function fileUpload() {
        var form = $form[0];
        var opts = $.extend({}, $.ajaxSettings, options);
    var id = 'jqFormIO' + $.fn.ajaxSubmit.counter++;
    var $io = $('&lt;iframe  /&gt;');
    var io = $io[0];
    var op8 = $.browser.opera &amp;&amp; window.opera.version() &lt; 9;
    if ($.browser.msie || op8) io.src = 'javascript:false;document.write("");';
    $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });

    var xhr = { // mock object
        responseText: null,
        responseXML: null,
        status: 0,
        statusText: 'n/a',
        getAllResponseHeaders: function() {},
        getResponseHeader: function() {},
        setRequestHeader: function() {}
    };
    
    var g = opts.global;
    // trigger ajax global events so that activity/block indicators work like normal
    if (g &amp;&amp; ! $.active++) $.event.trigger("ajaxStart");
    if (g) $.event.trigger("ajaxSend", [xhr, opts]);
    
    var cbInvoked = 0;
    var timedOut = 0;
    
    // take a breath so that pending repaints get some cpu time before the upload starts
    setTimeout(function() {
        $io.appendTo('body');
        // jQuery's event binding doesn't work for iframe events in IE
        io.attachEvent ? io.attachEvent('onload', cb) : io.addEventListener('load', cb, false);
        
        // make sure form attrs are set
        var encAttr = form.encoding ? 'encoding' : 'enctype';
        var t = $form.attr('target');
        $form.attr({
            target:   id,
            method:  'POST',
            encAttr: 'multipart/form-data',
            action:   opts.url
        });

        // support timout
        if (opts.timeout)
            setTimeout(function() { timedOut = true; cb(); }, opts.timeout);

        form.submit();
        $form.attr('target', t); // reset target
    }, 10);
    
    function cb() {
        if (cbInvoked++) return;
        
        io.detachEvent ? io.detachEvent('onload', cb) : io.removeEventListener('load', cb, false);

        var ok = true;
        try {
            if (timedOut) throw 'timeout';
            // extract the server response from the iframe
            var data, doc;
            doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document;
            xhr.responseText = doc.body ? doc.body.innerHTML : null;
            xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
            
            if (opts.dataType == 'json' || opts.dataType == 'script') {
                var ta = doc.getElementsByTagName('textarea')[0];
                data = ta ? ta.value : xhr.responseText;
                if (opts.dataType == 'json')
                    eval("data = " + data);
                else
                    $.globalEval(data);
            }
            else if (opts.dataType == 'xml') {
                data = xhr.responseXML;
                if (!data &amp;&amp; xhr.responseText != null)
                    data = toXml(xhr.responseText);
            }
            else {
                data = xhr.responseText;
            }
        }
        catch(e){
            ok = false;
            $.handleError(opts, xhr, 'error', e);
        }

        // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
        if (ok) {
            opts.success(data, 'success');
            if (g) $.event.trigger("ajaxSuccess", [xhr, opts]);
        }
        if (g) $.event.trigger("ajaxComplete", [xhr, opts]);
        if (g &amp;&amp; ! --$.active) $.event.trigger("ajaxStop");
        if (opts.complete) opts.complete(xhr, ok ? 'success' : 'error');

        // clean up
        setTimeout(function() { 
            $io.remove(); 
            xhr.responseXML = null;
        }, 100);
    };</pre>

 

很明显,这是通过使用隐藏iframe来模拟ajax实现的文件上传(参见该方法的介绍博文《谈谈使用iFrame模拟Ajax的问题》 )。注意在方法cb中有这么一段代码:

Java代码  使用jquery.form.js的ajaxsubmit方法提交数据的Bug使用jquery.form.js的ajaxsubmit方法提交数据的Bug
  1. catch(e){  
  2.                 ok = false;  
  3.                 $.handleError(opts, xhr, 'error', e);  
  4.             }  
  5.   
  6.             // ordering of these callbacks/triggers is odd, but that's how $.ajax does it  
  7.             if (ok) {  
  8.                 opts.success(data, 'success');  
  9.                 if (g) $.event.trigger("ajaxSuccess", [xhr, opts]);  
  10.             }  
catch(e){
                ok = false;
                $.handleError(opts, xhr, 'error', e);
            }
        // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
        if (ok) {
            opts.success(data, 'success');
            if (g) $.event.trigger("ajaxSuccess", [xhr, opts]);
        }</pre>

 

从这个代码中可以看出,仅仅是当出现异常的时候(关于js异常的情况,请参见介绍博文《Javascript的异常处理介绍》 ),才会触发我们设定的error方法,其余情况都会触发success,也就是说即时http返回的不是200,而是其他的错误码,只要不出现异常就不会触发error方法!

 

找到问题原因了,我们怎么来实现根据http返回的状态码来进行相应的处理呢?一种策略是将状态码写到返回的是text的文本中,然后在客户端根据文本进行判断。或许另外一种方法是重写这个cb方法,在其中根据http的状态码来进行不同的处理,不过我还没有找到获取返回的状态码的方法。

 

互联网码农一枚,欢迎微博互粉,进行交流:http://weibo.com/icemanhit

相关文章:

  • 2022-01-25
  • 2022-01-07
  • 2021-08-08
  • 2021-11-27
  • 2021-11-27
  • 2021-11-27
  • 2022-01-08
  • 2021-12-24
猜你喜欢
  • 2022-03-07
  • 2022-12-23
  • 2021-06-02
  • 2021-12-18
  • 2022-12-23
  • 2022-02-18
  • 2022-12-23
相关资源
相似解决方案