使用jquery.form.js的ajaxsubmit方法提交数据的Bug

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

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

前端JSP代码:

Java代码  收藏代码
  1. < form id ="wfAuditForm" method ="post" enctype ="multipart/form-data">  
  2. < input type ="file" name ="posterUrlUploadPath" id ="posterUrlUploadPath" class ="fileUpload" title ="上传图片" />  

 

前端JS代码:

Java代码  收藏代码
  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代码  收藏代码
  1. public void save(HttpServletResponse response, HttpServletRequest request, Integer hasUpload,PictureResource pic) {  
  2.      response.setStatus(HttpServletResponse. SC_CONFLICT);  
  3. }  

 

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

 

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

 

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

Js代码  收藏代码
  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);  

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

Js代码  收藏代码
  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 id="' + id + '" name="' + id + '" />');  
  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.         };  

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

Java代码  收藏代码
  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.             }  

 

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

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

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

  1. da shang
    donate-alipay
               donate-weixin weixinpay

发表评论↓↓