struts2 使用 jakarta 上传文件时commons fileupload的异常捕捉(转自alfred.w)

时间:2022-08-28 12:32:45
struts2 使用 jakarta 上传文件时commons fileupload的异常捕捉(转自alfred.w)

问题:

        struts2 使用jakarta 上传文件时,如果上传文件的大小超出commons fileupload(jakarta上传文件还是依赖commons-fileupload)设置的大小就会在进入action以前抛出异常.
        如果想返回用户的输入界面(input),那么页面原来的参数会丢失。

首先看一下struts2 执行一个action的过程

1.  将用户请求发给org.apache.struts2.dispatcher.Dispatcher,
     wrapRequest(HttpServletRequest request, ServletContext servletContext)  方法会判断是否"multipart/form-data",如果是建立一个multiPartRequest 的实例,并且建立MultiPartRequestWrapper

写道 ...if (content_type != null && content_type.indexOf("multipart/form-data") != -1) {
       MultiPartRequest multi = getContainer().getInstance(MultiPartRequest.class);
       request = new MultiPartRequestWrapper(multi, request, getSaveDir(servletContext));
} else {
       request = new StrutsRequestWrapper(request);
}

 

2. 建立 MultiPartRequestWrapper 时解析(parse) request,

Java代码
  1. public void parse(HttpServletRequest servletRequest, String saveDir)   
  2.             throws IOException {   
  3.         DiskFileItemFactory fac = new DiskFileItemFactory();   
  4.         // Make sure that the data is written to file   
  5.         fac.setSizeThreshold(0);   
  6.         if (saveDir != null) {   
  7.             fac.setRepository(new File(saveDir));   
  8.         }   
  9.   
  10.         // Parse the request   
  11.         try {   
  12.             ServletFileUpload upload = new ServletFileUpload(fac);   
  13.             upload.setSizeMax(maxSize);   
  14.             //upload 解析request并取得页面参数   
  15.         List items = upload.parseRequest(createRequestContext(servletRequest));   
  16.         ......   
  17.           

 3.我们看一下ServletFileUpload(commons-fileupload v1.1.1) 的parseRequest做了什么

Java代码
  1. public List /* FileItem */ parseRequest(RequestContext ctx)   
  2.            throws FileUploadException {   
  3.        if (ctx == null) {   
  4.            throw new NullPointerException("ctx parameter");   
  5.        }   
  6.   
  7.        ArrayList items = new ArrayList();   
  8.        String contentType = ctx.getContentType();   
  9.   
  10.        if ((null == contentType)   
  11.            || (!contentType.toLowerCase().startsWith(MULTIPART))) {   
  12.            throw new InvalidContentTypeException(   
  13.                "the request doesn't contain a "  
  14.                + MULTIPART_FORM_DATA   
  15.                + " or "  
  16.                + MULTIPART_MIXED   
  17.                + " stream, content type header is "  
  18.                + contentType);   
  19.        }   
  20.        int requestSize = ctx.getContentLength();   
  21.           
  22.        if (requestSize == -1) {   
  23.            throw new UnknownSizeException(   
  24.                "the request was rejected because its size is unknown");   
  25.        }   
  26.        //关键就这里了,大小超出的异常,这里是所有上传文件合计的大小,如果超出就抛出异常   
  27.        //这时上层是拿不到保存参数的items的   
  28.        if (sizeMax >= 0 && requestSize > sizeMax) {   
  29.            throw new SizeLimitExceededException(   
  30.                "the request was rejected because its size (" + requestSize   
  31.                + ") exceeds the configured maximum (" + sizeMax + ")",   
  32.                requestSize, sizeMax);   
  33.        }   
  34.   
  35.        String charEncoding = headerEncoding;   
  36.        if (charEncoding == null) {   
  37.            charEncoding = ctx.getCharacterEncoding();   
  38.        }   
  39.   
  40.        try {   
  41.            byte[] boundary = getBoundary(contentType);   
  42.            if (boundary == null) {   
  43.                throw new FileUploadException(   
  44.                        "the request was rejected because "  
  45.                        + "no multipart boundary was found");   
  46.            }   
  47.   
  48.            InputStream input = ctx.getInputStream();   
  49.   
  50.            MultipartStream multi = new MultipartStream(input, boundary);   
  51.            multi.setHeaderEncoding(charEncoding);   
  52.   
  53.            boolean nextPart = multi.skipPreamble();   
  54.            while (nextPart) {   
  55.                Map headers = parseHeaders(multi.readHeaders());   
  56.                String fieldName = getFieldName(headers);   
  57.                if (fieldName != null) {   
  58.                    String subContentType = getHeader(headers, CONTENT_TYPE);   
  59.                    if (subContentType != null && subContentType   
  60.                        .toLowerCase().startsWith(MULTIPART_MIXED)) {   
  61.                        // Multiple files.   
  62.                        byte[] subBoundary = getBoundary(subContentType);   
  63.                        multi.setBoundary(subBoundary);   
  64.                        boolean nextSubPart = multi.skipPreamble();   
  65.                        while (nextSubPart) {   
  66.                            headers = parseHeaders(multi.readHeaders());   
  67.                            if (getFileName(headers) != null) {   
  68.                                FileItem item =   
  69.                                        createItem(headers, false);   
  70.                                OutputStream os = item.getOutputStream();   
  71.                                try {   
  72.                                    multi.readBodyData(os);   
  73.                                } finally {   
  74.                                    os.close();   
  75.                                }   
  76.                                items.add(item);   
  77.                            } else {   
  78.                                // Ignore anything but files inside   
  79.                                // multipart/mixed.   
  80.                                multi.discardBodyData();   
  81.                            }   
  82.                            nextSubPart = multi.readBoundary();   
  83.                        }   
  84.                        multi.setBoundary(boundary);   
  85.                    } else {   
  86.                        FileItem item = createItem(headers,   
  87.                                getFileName(headers) == null);   
  88.                        OutputStream os = item.getOutputStream();   
  89.                        try {   
  90.                            multi.readBodyData(os);   
  91.                        } finally {   
  92.                            os.close();   
  93.                        }   
  94.                        items.add(item);   
  95.                    }   
  96.                } else {   
  97.                    // Skip this part.   
  98.                    multi.discardBodyData();   
  99.                }   
  100.                nextPart = multi.readBoundary();   
  101.            }   
  102.        } catch (IOException e) {   
  103.            throw new FileUploadException(   
  104.                "Processing of " + MULTIPART_FORM_DATA   
  105.                    + " request failed. " + e.getMessage());   
  106.        }   
  107.   
  108.        return items;   
  109.    }  

 4.这之后才开始逐个进入interceptor,见DefaultActionInvocation.invoke()

Java代码
  1. ....   
  2. //递归interceptor   
  3. if (interceptors.hasNext()) {   
  4.                 final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next();   
  5.                 UtilTimerStack.profile("interceptor: "+interceptor.getName(),    
  6.                         new UtilTimerStack.ProfilingBlock<String>() {   
  7.                             public String doProfiling() throws Exception {   
  8.                                 resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);   
  9.                                 return null;   
  10.                             }   
  11.                 });   
  12.             } else {   
  13.                         //如果有errors,resultCode会得到‘input’   
  14.                 resultCode = invokeActionOnly();   
  15.             }   
  16. ...  

 5.我们的目标就是返回input并且保留页面原来的参数,那么就要不要让ServletFileUpload抛出异常,并且要让strusts使用我们自己的jakart.

 6.写自己的ServletFileUpload

Java代码
  1. /*  
  2.  * Copyright 2001-2005 The Apache Software Foundation  
  3.  *  
  4.  * Licensed under the Apache License, Version 2.0 (the "License");  
  5.  * you may not use this file except in compliance with the License.  
  6.  * You may obtain a copy of the License at  
  7.  *  
  8.  *     http://www.apache.org/licenses/LICENSE-2.0  
  9.  *  
  10.  * Unless required by applicable law or agreed to in writing, software  
  11.  * distributed under the License is distributed on an "AS IS" BASIS,  
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
  13.  * See the License for the specific language governing permissions and  
  14.  * limitations under the License.  
  15.  */  
  16. package com.infowarelab.newcentury.web.util;   
  17.   
  18. import java.io.IOException;   
  19. import java.io.InputStream;   
  20. import java.io.OutputStream;   
  21. import java.util.ArrayList;   
  22. import java.util.List;   
  23. import java.util.Map;   
  24.   
  25. import javax.servlet.http.HttpServletRequest;   
  26.   
  27. import org.apache.commons.fileupload.FileItem;   
  28. import org.apache.commons.fileupload.FileItemFactory;   
  29. import org.apache.commons.fileupload.FileUpload;   
  30. import org.apache.commons.fileupload.FileUploadException;   
  31. import org.apache.commons.fileupload.MultipartStream;   
  32. import org.apache.commons.fileupload.RequestContext;   
  33. import org.apache.commons.fileupload.servlet.ServletRequestContext;   
  34. import org.apache.log4j.Logger;   
  35.   
  36. /**  
  37.   * come from commons-fileupload  
  38.   * @author alfred  
  39.  */  
  40. public class ServletFileUpload extends FileUpload {   
  41.   
  42.     // ---------------------------------------------------------- Class methods   
  43.   
  44.     /**  
  45.      * Logger for this class  
  46.      */  
  47.     private static final Logger logger = Logger.getLogger(ServletFileUpload.class);   
  48.     private List<String> errors = new ArrayList<String>();   
  49.   
  50.     /**  
  51.      * Constructs an uninitialised instance of this class. A factory must be  
  52.      * configured, using <code>setFileItemFactory()</code>, before attempting  
  53.      * to parse requests.  
  54.      *   
  55.      * @see FileUpload#FileUpload(FileItemFactory)  
  56.      */  
  57.     public ServletFileUpload() {   
  58.         super();   
  59.     }   
  60.   
  61.     /**  
  62.      * Constructs an instance of this class which uses the supplied factory to  
  63.      * create <code>FileItem</code> instances.  
  64.      *   
  65.      * @see FileUpload#FileUpload()  
  66.      */  
  67.     public ServletFileUpload(FileItemFactory fileItemFactory) {   
  68.         super(fileItemFactory);   
  69.     }   
  70.     /**  
  71.      * overide parseRequest  
  72.      */  
  73.     public List /* FileItem */parseRequest(RequestContext ctx) throws FileUploadException {   
  74.         if (ctx == null) {   
  75.             throw new NullPointerException("ctx parameter");   
  76.         }   
  77.   
  78.         ArrayList items = new ArrayList();   
  79.         String contentType = ctx.getContentType();   
  80.   
  81.         if ((null == contentType) || (!contentType.toLowerCase().startsWith(MULTIPART))) {   
  82.             throw new InvalidContentTypeException("the request doesn't contain a " + MULTIPART_FORM_DATA + " or "  
  83.                     + MULTIPART_MIXED + " stream, content type header is " + contentType);   
  84.         }   
  85.         int requestSize = ctx.getContentLength();   
  86.   
  87.         if (requestSize == -1) {   
  88.             // throw new UnknownSizeException(   
  89.             // "the request was rejected because its size is unknown");   
  90.             logger.error("the request was rejected because its size is unknown");   
  91.             errors.add("the request was rejected because its size is unknown");   
  92.         }   
  93.   
  94.         String charEncoding = getHeaderEncoding();   
  95.         if (charEncoding == null) {   
  96.             charEncoding = ctx.getCharacterEncoding();   
  97.         }   
  98.   
  99.         try {   
  100.             byte[] boundary = getBoundary(contentType);   
  101.             if (boundary == null) {   
  102.                 // throw new FileUploadException(   
  103.                 // "the request was rejected because "   
  104.                 // + "no multipart boundary was found");   
  105.                 logger.error("the request was rejected because no multipart boundary was found");   
  106.                 errors.add("the request was rejected because no multipart boundary was found");   
  107.             }   
  108.   
  109.             InputStream input = ctx.getInputStream();   
  110.   
  111.             MultipartStream multi = new MultipartStream(input, boundary);   
  112.             multi.setHeaderEncoding(charEncoding);   
  113.   
  114.             boolean nextPart = multi.skipPreamble();   
  115.             while (nextPart) {   
  116.                 Map headers = parseHeaders(multi.readHeaders());   
  117.                 String fieldName = getFieldName(headers);   
  118.                 if (fieldName != null) {   
  119.                     String subContentType = getHeader(headers, CONTENT_TYPE);   
  120.                     if (subContentType != null && subContentType.toLowerCase().startsWith(MULTIPART_MIXED)) {   
  121.                         // Multiple files.   
  122.                         byte[] subBoundary = getBoundary(subContentType);   
  123.                         multi.setBoundary(subBoundary);   
  124.                         boolean nextSubPart = multi.skipPreamble();   
  125.                         while (nextSubPart) {   
  126.                             headers = parseHeaders(multi.readHeaders());   
  127.                             if (getFileName(headers) != null) {   
  128.                                 FileItem item = createItem(headers, false);   
  129.                                 OutputStream os = item.getOutputStream();   
  130.                                 try {   
  131.                                     multi.readBodyData(os);   
  132.                                 } finally {   
  133.                                     os.close();   
  134.                                 }   
  135.                                 items.add(item);   
  136.                             } else {   
  137.                                 // Ignore anything but files inside   
  138.                                 // multipart/mixed.   
  139.                                 multi.discardBodyData();   
  140.                             }   
  141.                             nextSubPart = multi.readBoundary();   
  142.                         }   
  143.                         multi.setBoundary(boundary);   
  144.                     } else {   
  145.                         FileItem item = createItem(headers, getFileName(headers) == null);   
  146.                         OutputStream os = item.getOutputStream();   
  147.                         try {   
  148.                             multi.readBodyData(os);   
  149.                         } finally {   
  150.                             os.close();   
  151.                         }   
  152.                         items.add(item);   
  153.                     }   
  154.                 } else {   
  155.                     // Skip this part.   
  156.                     multi.discardBodyData();   
  157.                 }   
  158.                 nextPart = multi.readBoundary();   
  159.             }   
  160.             // remove SizeLimitExceededException   
  161.             if (getSizeMax() >= 0 && requestSize > getSizeMax()) {   
  162.                 // throw new SizeLimitExceededException(   
  163.                 // "the request was rejected because its size (" + requestSize   
  164.                 // + ") exceeds the configured maximum (" + getSizeMax() + ")",   
  165.                 // requestSize, getSizeMax());   
  166.                 logger.error("the request was rejected because its size (" + requestSize   
  167.                         + ") exceeds the configured maximum (" + getSizeMax() + ")");   
  168.             }   
  169.         } catch (IOException e) {   
  170.             logger.error("Processing of " + MULTIPART_FORM_DATA + " request failed. " + e.getMessage());   
  171.             errors.add("Processing of " + MULTIPART_FORM_DATA + " request failed. " + e.getMessage());   
  172.             // throw new FileUploadException(   
  173.             // "Processing of " + MULTIPART_FORM_DATA   
  174.             // + " request failed. " + e.getMessage());   
  175.         }    
  176.            
  177.         return items;   
  178.     }   
  179.   
  180.     /**  
  181.      * @return the errors  
  182.      */  
  183.     public List<String> getErrors() {   
  184.         return errors;   
  185.     }   
  186.   
  187.     /**  
  188.      * @param errors the errors to set  
  189.      */  
  190.     public void setErrors(List<String> errors) {   
  191.         this.errors = errors;   
  192.     }   
  193.   
  194. }  

 

7.copy org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest,只是import上面自己的ServletFileUpload.这样就可以保存页面的所有参数了。

8.更改struts配置文件加入你自己的JakartaMultiReques

Xml代码
  1. <bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest"    
  2. ame="jakarta_yourself"    
  3.        class="com.xxxxx.util.JakartaMultiPartRequest"    
  4. cope="default" optional="true" />  

 9.更改struts.properties

struts.multipart.parser=jakarta_yourself

10.就OK啦