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

  • Post author:
  • Post category:其他


问题描述:

–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来上传文件,对客户的操作体验可能是最好的,但是,这样可能会导致服务器上有些挂空的文件(上传后从来没被用过),需要想法清除的。

整个分析下来,我们说第二个问题基本上是无法避免的。