周五同事遇到一個很奇怪的問題,調到下班,雖然問題解決了,但是不知道問題的具體原因,回來翻了翻代碼,才發現症結所在,下面就分享出來,供遇到同樣問題的同行們參考:
先把問題描述一下,做的功能是使用ajax向后台來提交數據,為了向用戶進行很好的錯誤提示,后台中將出現錯誤時的錯誤原因返回給前端,前端使用jquery.form.js的ajaxsubmit來提交數據,並在success方法中提示“操作成功”,在error方法中提示錯誤原因。整個form提交的數據包括一些簡單的input和一個文件的上傳。下面是代碼:
前端JSP代碼:
- < form id ="wfAuditForm" method ="post" enctype ="multipart/form-data">
- < input type ="file" name ="posterUrlUploadPath" id ="posterUrlUploadPath" class ="fileUpload" title ="上傳圖片" />
前端JS代碼:
- $("#wfAuditForm").ajaxSubmit({
- type: 'post',
- url: "data/resource/picture/save" ,
- success: function(data){
- alert( "success");
- $( "#wfAuditForm").resetForm();
- },
- error: function(XmlHttpRequest, textStatus, errorThrown){
- alert( "error");
- }
- });
后台:
- 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的代碼,發現這段代碼中有這么一段很可疑:
- 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方法:
- // 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 = $('<iframe id="' + id + '" name="' + id + '" />');
- var io = $io[0];
- var op8 = $.browser.opera && window.opera.version() < 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 && ! $.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 && 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 && ! --$.active) $.event.trigger("ajaxStop");
- if (opts.complete) opts.complete(xhr, ok ? 'success' : 'error');
- // clean up
- setTimeout(function() {
- $io.remove();
- xhr.responseXML = null;
- }, 100);
- };