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

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

前端JSP代码:

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

前端JS代码:

  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. });
$("#wfAuditForm").ajaxSubmit({
type: 'post',
url: "data/resource/picture/save" ,
success: function(data){
alert( "success");
$( "#wfAuditForm").resetForm();
},
error: function(XmlHttpRequest, textStatus, errorThrown){
alert( "error");
}
});

后台:

  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的代码,发现这段代码中有这么一段很可疑:

  1. var found = false;
  2. for ( var j=0; j < files.length; j++)
  3. if (files[j])
  4. found = true;
  5. if (options.iframe || found) // options.iframe allows user to force iframe mode
  6. fileUpload();
  7. else
  8. $.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方法:

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

  1. catch(e){
  2. ok = false;
  3. $.handleError(opts, xhr, 'error', e);
  4. }
  5. // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
  6. if (ok) {
  7. opts.success(data, 'success');
  8. if (g) $.event.trigger("ajaxSuccess", [xhr, opts]);
  9. }
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

使用jquery.form.js的ajaxsubmit方法提交数据的Bug的更多相关文章

  1. jquery.form.js 让表单提交更优雅

    jquery.form.js 让表单提交更优雅.可以页面不刷新提交表单,比jQuery的ajax提交要功能强大. 1.引入 <script src="/src/jquery-1.9.1 ...

  2. jQuery通过jquery.form.js插件使用AJAX提交Form表单

    我简单使用了一下,jQuery Form插件有一下优点:  1.支持提交前验证. 2.支持提交后回调. 3.采用AJAX方式,有很好的用户体验 4.提交方式是灵活.只要指定要提交的form ID即可. ...

  3. [转]jquery.form.js的ajaxSubmit和ajaxForm使用

    参考 http://www.cnblogs.com/popzhou/p/4338040.html 依赖的脚本文件 <script src="../Javascript/jquery-1 ...

  4. 【Jquery+Express.js】 submit() 方法提交form

    前端页面 .html 生成一个动态模块 Modal <div class="modal fade" id="addStaff" tabindex=&quo ...

  5. 使用Jquery.form.js ajax表单提交插件弹出下载提示框

    现象: 使用jquery的from做ajax表单提交的时候,后台处理完毕返回json字符串,此时浏览器提示下载一个json文件而不是在success里面继续解析该json对象. 具体的原因: 浏览器兼 ...

  6. 文件上传功能 -- jquery.form.js/springmvc

    距离上一篇 文件上传下载样式 -- bootstrap(http://www.cnblogs.com/thomascui/p/5370947.html)已经三周时间了,期间一直考虑怎么样给大家提交一篇 ...

  7. input ,button, textarea 1)使用disabled , 2) 显示值, 3) 表单提交. 4) jquery.form.js ajaxSubmit() 无刷新ajax提交表单.

    1.使用disabled input , button  textarea 可以 被 禁用, 禁用的效果 : 1) 上面的点击事件无法使用 --- button       : 下面的 onclick ...

  8. jquery.form.js的重置表单增加hidden重置代码

    jquery.form.js的resetForm()方法无法重置hidden元素,打开文件在1460行加上以下代码即可

  9. jquery.form.js实现将form提交转为ajax方式提交的使用方法

    本文实例讲述了jquery.form.js实现将form提交转为ajax方式提交的方法.分享给大家供大家参考.具体分析如下: 这个框架集合form提交.验证.上传的功能. 这个框架必须和jquery完 ...

随机推荐

  1. IP地址与,域名,DNS服务器,端口号的联系与概念

    一,什么是IP地址? 每一个联入到Internet的计算机都需要一个世界上独一无二的IP地址,相当于人们的身份证号码! IP地址有A类,B类,C类,D类和E类之分,目前D类和E类都暂时作为保留地址! ...

  2. gb2312的6763个汉字

    0a啊阿埃挨哎唉哀皑癌蔼矮艾碍爱隘鞍氨安俺按暗岸胺案肮昂盎凹敖熬翱袄傲奥懊澳芭捌扒叭吧笆八疤巴拔跋靶把耙坝霸罢爸白柏百摆佰败拜稗斑班搬扳般颁板版扮拌伴瓣半办绊邦帮梆榜膀绑棒磅蚌镑傍谤苞胞包褒剥薄雹保堡 ...

  3. php thrift TServerSocket实现端口复用

    <?php namespace Message\Controller; use Think\Controller; use Thrift\Exception\TException; use Th ...

  4. 2017年浙工大迎新赛热身赛 L cayun日常之赏月【易错特判】

    题目描述(https://www.nowcoder.com/acm/contest/51#question) 在cayun星球月亮大小都有一个规律,月亮为每30天一个周期,在这30天的周期里,月亮的大 ...

  5. vue 微信内H5调起支付

    在微信内H5调起微信支付,主要依赖于一个微信的内置对象WeixinJSBridge,这个对象在其他浏览器中无效. 主要代码: import axios from 'axios'; export def ...

  6. TCP keepalive的机理及使用

    TCP 是面向连接的 , 在实际应用中通常都需要检测对端是否还处于连接中.如果已断开连接,主要分为以下几种情况: 1.连接的对端正常关闭,即使用 closesocket 关闭连接. 2.连接的对端非正 ...

  7. iOS 获取 APP 的 Launch Image

    http://www.cocoachina.com/ios/20151027/13780.html 作者:里脊串 授权本站转载. 启动图(LaunchImage)的管理其实在iOS开始中算比较简单的了 ...

  8. markdown-it + highlight.js简易实现

    markdown-it 官方demo markdown-it 文档 1.配置highlightjs,针对markdown中各种语言高亮,针对对应的标签 pre code 里面的样式 -- index. ...

  9. LeetCode208 Implement Trie (Prefix Tree). LeetCode211 Add and Search Word - Data structure design

    字典树(Trie树相关) 208. Implement Trie (Prefix Tree) Implement a trie with insert, search, and startsWith  ...

  10. 【Leetcode栈】有效的括号(20)

    题目 给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效. 有效字符串需满足: 1,左括号必须用相同类型的右括号闭合. 2,左括号必须以正确的顺序闭合. 注意 ...