天天看點

the request was rejected because its size (4652587) exceeds the configured maximum (2097152)

問題描述:

--struts2中檔案上傳的二個限制,

第一道是  struts.multipart.maxSize,如果不設定,struts2 的核心包下的default.properties檔案裡有預設的大小設定struts.multipart.maxSize=2097152,即2M. 這是struts2檔案上傳的第一道關.

第二道關是  inteceptor中的maximumSize. 當真實的檔案大小能通過第一道關時.針對不同的action中配置的inteceptor,maximumSize才能發揮相應的攔截作用.

比如struts.multipart.maxSize=50M. actionA中inteceptorA的maximumSize=30M. actionB中inteceptorB的maximumSize=10M.

struts.multipart.maxSize=50M對于inteceptorA,B都會起到第一關的作用.而inteceptorA和inteceptorB可以在通過第一關之後,根據自己的業務定制各自針對攔截器起作用的maximumSize

1.1.先判斷表單送出的資料大小,是否超過配置檔案“struts.multipart.maxSize”的值

通過struts.multipart.maxSize屬性來對檔案大小進行限定時,将直接影響到commons-fileupload元件的檔案大小設定,預設是2M。當上傳檔案超過了這個尺寸時,将從commons-fileupload元件中抛出SizeLimitExceededException異常。Struts2上傳檔案攔截器捕獲到這個異常後,将直接把該異常資訊設定為Action級别的錯誤資訊。

如果真實的檔案>50M. 抛出會抛出the request was rejected because its size (XXXX) exceeds the configured maximum (XXXX)異常,他是不能被國際化的,因為這個資訊是commons-fileupload元件抛出的,是不支援國際化這資訊.

2.再判斷上傳的檔案資料是否超過攔截器裡配置的“maximumSize”的大小;

fileUpload攔截器隻是當檔案上傳到伺服器上之後,才進行的檔案類型和大小判斷。Struts2架構底層預設用的是apache的commons-fileupload元件對上傳檔案進行接受處理。

如果此處不滿足,則會報出下面資訊:

警告: File too large: upload "DSC00034.JPG" "upload_38f8bfe7_12a07e9fd3d__7ff2_00000001.tmp" 4652362

2013-5-25 20:23:59 com.opensymphony.xwork2.util.logging.commons.CommonsLogger info

資訊: Removing file upload D:/Java/Tomcat6/work/Catalina/localhost/Struts2BaseDemo/upload_38f8bfe7_12a07e9fd3d__7ff2_00000001.tmp

3.最後檢查是否檔案類型是否滿足攔截器的條件

如果此處不滿足,則會報出下面資訊:

警告: 您上傳的檔案不是圖檔類型!

2013-5-25 18:19:54 com.opensymphony.xwork2.util.logging.commons.CommonsLogger info

資訊: Removing file upload D:/Java/Tomcat6/work/Catalina/localhost/Struts2BaseDemo/upload_38f8bfe7_12a07e9fd3d__7ff3_00000000.tmp

struts2.2 org.apache.commons.fileupload.FileUploadBase.java中

/** 
  * Creates a new instance. 
  * @param ctx The request context. 
  * @throws FileUploadException An error occurred while 
  *   parsing the request. 
  * @throws IOException An I/O error occurred. 
  */  
 FileItemIteratorImpl(RequestContext ctx)  
         throws FileUploadException, IOException {  
     if (ctx == null) {  
         throw new NullPointerException("ctx parameter");  
     }  
   
     String contentType = ctx.getContentType();  
     if ((null == contentType)  
             || (!contentType.toLowerCase().startsWith(MULTIPART))) {  
         throw new InvalidContentTypeException(  
                 "the request doesn't contain a "  
                 + MULTIPART_FORM_DATA  
                 + " or "  
                 + MULTIPART_MIXED  
                 + " stream, content type header is "  
                 + contentType);  
     }  
   
     InputStream input = ctx.getInputStream();  
   
     if (sizeMax >= 0) {  
         int requestSize = ctx.getContentLength();  
         if (requestSize == -1) {  
             input = new LimitedInputStream(input, sizeMax) {  
                 protected void raiseError(long pSizeMax, long pCount)  
                         throws IOException {  
                     FileUploadException ex =  
                         new SizeLimitExceededException(  
                             "the request was rejected because"  
                             + " its size (" + pCount  
                             + ") exceeds the configured maximum"  
                             + " (" + pSizeMax + ")",  
                             pCount, pSizeMax);  
                     throw new FileUploadIOException(ex);  
                 }  
             };  
         } else {  
 ///問題就在這裡  
             if (sizeMax >= 0 && requestSize > sizeMax) {  
                 throw new SizeLimitExceededException(  
                         "the request was rejected because its size ("  
                         + requestSize  
                         + ") exceeds the configured maximum ("  
                         + sizeMax + ")",  
                         requestSize, sizeMax);  
             }  
         }  
     }  
   
     String charEncoding = headerEncoding;  
     if (charEncoding == null) {  
         charEncoding = ctx.getCharacterEncoding();  
     }  
   
     boundary = getBoundary(contentType);  
     if (boundary == null) {  
         throw new FileUploadException(  
                 "the request was rejected because "  
                 + "no multipart boundary was found");  
     }  
   
     notifier = new MultipartStream.ProgressNotifier(listener,  
             ctx.getContentLength());  
     multi = new MultipartStream(input, boundary, notifier);  
     multi.setHeaderEncoding(charEncoding);  
   
     skipPreamble = true;  
     findNextItem();
           

如果InteceptorA上傳的是40M的真實檔案.那麼此時攔截器InteceptorA會通路國際化資訊:struts.messages.error.file.too.larges對應的值.

當且僅當上傳檔案<=30M的時候,InteceptorA才會成功上傳.

下面是解決struts.multipart.maxSize提示資訊不友好的問題.

當超過50M時.commons-fileupload抛出運作時異常,struts2會把這個異常看到是action級别的異常.是以會将異常資訊

the request was rejected because its size (XXXX) exceeds the configured maximum (XXXX)寫到actionError裡面.我們需要做的就是在action裡覆寫addActionError方法

/**
	 * 
	 * 替換檔案上傳中出現的錯誤資訊 
	 * 引用 import java.util.regex.Matcher; import
	 * java.util.regex.Pattern;
	 * 
	 * */

	@Override
	public void addActionError(String anErrorMessage) {
		// 這裡要先判斷一下,是我們要替換的錯誤,才處理
		if (anErrorMessage
				.startsWith("the request was rejected because its size")) {
			Matcher m = Pattern.compile("\\d+").matcher(anErrorMessage);
			String s1 = "";
			if (m.find())
				s1 = m.group();
			String s2 = "";
			if (m.find())
				s2 = m.group();
			// 将資訊替換掉
			super.addActionError("你上傳的檔案大小(" + s1 + ")超過允許的大小(" + s2 + ")");
			// 也可以改為在Field級别的錯誤
			// super.addFieldError("file","你上傳的檔案大小(" + s1 + ")超過允許的大小(" + s2 +
			// ")");

		} else {// 否則按原來的方法處理
			super.addActionError(anErrorMessage);
		}
	}
           

addActionError()級别的錯誤可以用<s:actionerror/>标簽在頁面上顯示錯誤消息

addFieldError() 級别錯誤可以用<s:fielderror/> 标簽在頁面上顯示錯誤消息

注意,由于inteceptor中途傳回,原來頁面上輸入的其他文本内容也都不見了,也就是說params注入失敗。

這個是沒辦法的,因為這個異常是在檔案上傳之前捕獲的,檔案未上傳,同時params也為注入,是以這時最好重定向到一個jsp檔案,提示上傳失敗,然後重寫填寫相應資訊。

解決辦法:最好跳到一個專門顯示錯誤的頁.而不要傳回操作頁.

-------------------------------------------------------------------------------------------

注意,攔截器所謂的同名配置覆寫,是重複執行的,比如defaultStack中是包含fileUpload,token的. 如果将<interceptor-ref name="defaultStack" />放到顯示定義的攔截器之後,會覆寫顯示定義的攔截器.

下面是正确的攔截器順序:

<action name="BatchMIADOperation!*" method="{1}"
            class="com.*****.***.action.multiidea.batchad.BatchMIADOperationAction">
            <interceptor-ref name="defaultStack" />
            <interceptor-ref name="fileUpload">
                <param name="maximumSize">5242880</param>
                <!--
                    <param name="allowedTypes">
                    application/vnd.ms-excel
                    </param>
                -->
            </interceptor-ref>
            <interceptor-ref name="token">
                <param name="excludeMethods">
                    init,search,updateBatchCpcMatch,batchExportMIAD,downloadWhenError
                </param>
            </interceptor-ref>
            <result name="input">
                /WEB-INF/jsp/multiidea/batchad/BatchMIAD.jsp
            </result>
            <result name="success">
                /WEB-INF/jsp/multiidea/batchad/BatchMIAD.jsp
            </result>
            <result name="invalid.token">
                /WEB-INF/jsp/multiidea/batchad/BatchMIAD.jsp
            </result>
        </action>
           

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

經過我的測試和對源代碼的Debug,如果上傳檔案大于2M時,在頁面上就出現了一堆英文的錯誤資訊,大緻是:therequestwas rejected because its size....exceeds theconfiguredmaximum...并且在fieUpload中将來自MultiPartRequestWrapper型request對象的錯誤資訊給加到了Action的錯誤中。

  這時候,你在ApplicationResources.properties中自定義的上傳檔案過大的錯誤資訊根本不起作用。原因就如書上所言,在底層commons-fileupload元件中就把異常給抛出來了檔案根本沒被上傳,是以到了fileUpload攔截器時,根據取不到檔案,當然也就沒法對檔案的類型和大小進行判斷了。

  然而,這個異常直接帶來兩個問題:

  1、在頁面上顯示了英文的錯誤資訊。這樣的資訊顯然不是我們想要的。

  2、由于錯誤的産生,原來頁面上輸入的其他文本内容也都不見了,也就是說params注入失敗。

  帶着這兩個問題,我們來探尋一下Struts2對于請求的處理過程。

  注:這并不是一篇關于Struts2請求過程的介紹,主要是為了解決以上兩個問題,才引起的簡單分析。

  首先當然我們要拿FilterDispatcher開刀。

  在doFilter方法中調用了prepareDispatcherAndWrapRequest方法,為了包裝出Struts2自己的request對象,在prepareDispatcherAndWrapRequest方法中調用Dispatcher類的wrapRequest方法,在這個方法裡,會根據請求内容的類型(送出的是文本的,還是multipart/form-data格式),決定是使用tomcat的HttpServletRequestWrapper類分離出請求中的資料,還是使用Struts2的MultiPartRequestWrapper來分離請求中的資料。

  注:向伺服器請求時,資料是以流的形式向伺服器送出,内容是一些有規則東東,我們平時在jsp中用request内置對象取meter時,實際上是由tomcat的HttpServletRequestWrapper類分解好了的,無需我們再分解這些東西了。

  當然,在這裡,我們研究的是上傳檔案的情況,是以,由于form中設定的送出内容是媒體格式的,是以,Dispatcher類的wrapRequest方法會将請求交由MultiPartRequestWrapper類來處理。

  MultiPartRequestWrapper這個類是Struts2的類,并且繼承了tomcat的HttpServletRequestWrapper類,也是我們将用來代替HttpServletRequest這個類的類,看名字也知道,是對多媒體請求的包裝類。

  Struts2本身當然不會再造個輪子,來解析請求,而是交由Apache的commons-fileupload元件來解析了。

  在MultiPartRequestWrapper的構造方法中,會調用MultiPartRequest(預設為JakartaMultiPartRequest類)的parse方法來解析請求。

  在Struts2的JakartaMultiPartRequest類的parse方法中才會真正來調用commons-fileupload元件的ServletFileUpload類對請求進行解析,至此,Struts2已經實作了将請求轉交commons-fileupload元件對請求解析的全過程。剩下的就是等commons-fileupload元件對請求解析完畢後,拿到分解後的資料,根據field名,依次将分解後的field名和值放到ms(HashMap類型)裡,同時JakartaMultiPartRequest類重置了HttpServletRequest的好多方法,比如熟知的getmeter、getmeterNames、getmeterValues,實際上都是從解析後得到的那個ms對象裡拿資料,在這個過程,commons-fileupload元件也乖乖的把上傳的檔案分析好了,JakartaMultiPartRequest也毫不客氣的把分解後的檔案一個一個的放到了files(HashMap類型)中,實際上此時,commons-fileupload元件已經所有要上傳的檔案上傳完了。

  至此,Struts2實作了對HttpServletRequest類的包裝,當回到MultiPartRequestWrapper類後,再取一下上述解析過程中發生的錯誤,然後把錯誤加到了自己的errors清單中了。同樣我們會發現在MultiPartRequestWrapper類中,也把HttpServletRequest類的好多方法重載了,畢竟是個包裝類嘛,實際上對于上傳檔案的請求,在Struts2後期的進行中用到的request都是MultiPartRequestWrapper類對象,比如我們調用getmeter時,直接調用的是MultiPartRequestWrapper的getmeter方法,間接調的是JakartaMultiPartRequest類對象的getmeter方法。

  注:從這裡,我們就可以看出,JakartaMultiPartRequest是完全設計成可以替換的類了。

  然後繼續向回返,到了Dispatcher類的wrapRequest方法,直接把MultiPartRequestWrapper對象傳回了,我們就終于回到了FilterDispatcher類的prepareDispatcherAndWrapRequest方法,此時,我們拿到了完全解析好了的request對象(MultiPartRequestWrapper類),該對象又進一步被傳回到了FilterDispatcher類的doFilter方法,也就是回到了出發點,至此,doFilter中拿到的request對象就是一個将請求中的資料分解好的了HttpServletRequest對象,我們完全可以用getmeter方法取其中的資料了,同時,我們也可以用getFiles得到檔案數組了。

  doFilter方法中,會進一步調用actionMapper的getMapping方法對url進行解析,找出命名空間和action名等,以備後面根據配置檔案調用相應的攔截器和action使用。

  關于doFilter方法中下一步對Dispatcher類的serviceAction方法的調用,不再描述,總之在action被調用之前,會首先走到fileUpload攔截器(對應的是FileUploadInterceptor類),在這個攔截器中,會先看一下request是不是MultiPartRequestWrapper,如果不是,就說明不是上傳檔案用的request,fildUpload攔截器會直接将控制權交給下一個攔截器;如果是,就會把request對象強轉為MultiPartRequestWrapper對象,然後調用hasErrors方法,看看有沒有上傳時候産生的錯誤,有的話,就直接加到了Action的錯誤(Action級别的)中了。

  另外,在fileUpload攔截器中會将MultiPartRequestWrapper對象中放置的檔案全取出來,把檔案、檔案名、檔案類型取出來,放到request的meters中,這樣到了ms攔截器時,就可以輕松的将這些内容注入到Action中了,這也就是為什麼fileUpload攔截器需要放在ms攔截器前面的理由。在檔案都放到request的meters對象裡之後,fileUpload攔截器會繼續調用其他攔截器直到Action等執行完畢,他還要做一個掃尾的工作:把臨時檔案夾中的檔案删除(這些檔案是由commons-fileupload元件上傳的,供你在自己的Action中将檔案copy到指定的目錄下,當action執行完了後,這些臨時檔案當然就沒用了)。

  你好,你還在看嗎?呵呵,是不是太多了,也太亂了,沒辦法,Struts2就是這樣的調用的。也不知道Struts2有沒有公開其Sequence圖,我是想畫一個,不過,太懶,還是看着代碼說說吧。

  如果上面看煩了,也完全可以不看了,直接看下面的。

  在上面一番分析之後,檔案上傳的全過程就結束了。

  我們回到我們的問題上來。

  先看第一個:

  1、在頁面上顯示了英文的錯誤資訊。這顯然不是我們想要的。

  沒辦法了,commons-fileupload元件沒想到國際化,在FileUploadInterceptor攔載器中,也沒想着國際化,直接放到Action的錯誤中了,就沒他事了,三種做法:

  (1)在錯誤顯示之前,把這條錯誤給換掉,應該難度不大,我沒做留給你做了。

  (2)或者重寫一下JakartaMultiPartRequest這個類,把捕捉到的異常資訊換成自己的,然後,通過Struts2的配置檔案,把我們重寫的這個parser換上去用。

  (3)直接改commons-fileupload元件的類,換成中文的。

  我具體說一下第(3)種做法:找到FileUploadBase類,把902行~908行改一下。

  FileUploadException ex =

  new SizeLimitExceededException(

  "the request was rejected because"

  + " its size (" + pCount

  + ") exceeds the configured maximum"

  + " (" + pSizeMax + ")",

  pCount, pSizeMax);

  =>

  FileUploadException ex = new SizeLimitExceededException(

  "伺服器拒絕了您的請求,原因可能是向伺服器送出的資料發生了丢失。", pCount, pSizeMax);

  把914行~918行改一下。

  throw new SizeLimitExceededException(

  "the request was rejected because its size ("

  + requestSize

  + ") exceeds the configured maximum ("

  + sizeMax + ")",

  =>

  thrownewSizeLimitExceededException("伺服器拒絕了您的請求,原因是送出資料量過大(通常是由于上傳檔案過大),請傳回上頁重試。"

  + " (最大位元組數:" + sizeMax / 1024

  + "K)", requestSize, sizeMax);

  再看一下第二個問題。

  2、由于錯誤的産生,原來頁面上輸入的内容也全部不見了,也就是說ms注入失敗。

  關于這個問題我在javaeye上搜尋到一篇文章(使用的commons-fileupload元件的jar包似乎比較老)。

  http://www.javaeye.com/topic/197345

  雖然按照此文,當上傳失敗時,能夠将其他輸入内容顯示出來,但是這樣做的結果是全部的檔案肯定會上傳到伺服器上,也就是說,雖然是頁面上報了檔案因為太大,請求被拒絕的錯,但是檔案依然會被上傳到伺服器上,commons-fileupload元件根本沒會去攔檔案的上傳。

  在這裡要說明一下,如果你不抛出這個異常,請求的流會繼續向伺服器上傳,隻有當整個流上傳完了之後,commons-fileupload元件才能正确的分析出檔案部分、文本部分。是以,在這裡抛出異常是不得已的作法,如果不抛異常,後果是雖然頁面報錯,但檔案還是會被傳到伺服器的上,這一步根本沒擋住輸入流的上傳,如果沒擋住的話,大家想想會有什麼後果?

  是以,綜上所述,對于第二個問題,如果出現了這個異常,我們根本無法讓原來輸入的内容還顯示出來的,因為commons-fileupload元件并沒有解析全部的輸入内容,直接給出異常了,到了ms攔截器中,request裡就是空的,根本取不到meter,是以也就無法注入到Action中了。這種情況下,隻能顯示一個告知使用者由于送出資料量過大,伺服器拒絕了請求的錯誤資訊,比較好的方法是,直接跳到一個專門的頁面,提示使用者,然後讓使用者點傳回來再次輸入,否則使用者會感覺上傳檔案大就大吧,怎麼連我輸入的其他一些内容也沒給儲存住。當然,如果能用Ajax來上傳檔案,對客戶的操作體驗可能是最好的,但是,這樣可能會導緻伺服器上有些挂空的檔案(上傳後從來沒被用過),需要想法清除的。

  整個分析下來,我們說第二個問題基本上是無法避免的。