如何在 Spring 中处理 MultipartException

Posted

技术标签:

【中文标题】如何在 Spring 中处理 MultipartException【英文标题】:How to handle MultipartException in Spring 【发布时间】:2015-05-20 09:18:24 【问题描述】:

我的配置是here。

我根据那个帖子的答案做了一些修改。

filterMultipartResolver

@Bean
public StandardServletMultipartResolver filterMultipartResolver() 
    return new StandardServletMultipartResolver();

AppInitializer

public class AppInitializer implements WebApplicationInitializer 

    @Override
    public void onStartup(final ServletContext servletContext) throws ServletException 
        // Create the 'root' Spring application context
        final WebApplicationContext context = getContext();
        // Manage the lifecycle of the root application context
        servletContext.addListener(new ContextLoaderListener(context));
        final Dynamic dispatcher = servletContext.addServlet("DispatcherServlet", new DispatcherServlet(context));
        dispatcher.setMultipartConfig(getMultipartConfigElement());
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/");
    

    private static AnnotationConfigWebApplicationContext getContext() 
        final AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(AppConfig.class);
        return context;
    

    private static MultipartConfigElement getMultipartConfigElement()
        return new MultipartConfigElement(Props.FILE_TMP_DIRECTORY, 3 * 1024 * 1024, 3 * 1024 * 1024, 3 * 1024 * 1024);
    

我听从了this post 的建议,把这个MultipartExceptionHandler 收录了

public class MultipartExceptionHandler extends OncePerRequestFilter 
    static final Logger log = LoggerFactory.getLogger(MultipartExceptionHandler.class);

    @Override
    protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain) throws ServletException, IOException 
        try 
            filterChain.doFilter(request, response);
         catch (final MultipartException me) 
            log.error(me.getMessage());
            response.sendRedirect(UrlUtils.buildFullRequestUrl(request) + "&error=size-limit");
        
    

注册

public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer 

    @Override
    protected void beforeSpringSecurityFilterChain(final ServletContext servletContext) 
        insertFilters(servletContext, new MultipartExceptionHandler());
        insertFilters(servletContext, new MultipartFilter());
    

当我尝试上传超过最大大小的文件时,这就是我在日志中得到的内容

DEBUG 2015-03-17 19:54:18,372: org.springframework.web.multipart.support.MultipartFilter - Using MultipartResolver 'filterMultipartResolver' for MultipartFilter
DEBUG 2015-03-17 19:54:18,372: org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'filterMultipartResolver'
DEBUG 2015-03-17 19:54:18,372: org.springframework.web.multipart.support.MultipartFilter - Resolving multipart request [/registrazione] with MultipartFilter
ERROR 2015-03-17 19:54:18,384: it.openex.pmfew.filters.MultipartExceptionHandler - Could not parse multipart servlet request; nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (4522604) exceeds the configured maximum (3145728)
DEBUG 2015-03-17 19:54:18,386: org.springframework.web.multipart.support.MultipartFilter - Using MultipartResolver 'filterMultipartResolver' for MultipartFilter
DEBUG 2015-03-17 19:54:18,386: org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'filterMultipartResolver'
DEBUG 2015-03-17 19:54:18,386: org.springframework.web.multipart.support.MultipartFilter - Resolving multipart request [/registrazione] with MultipartFilter
ERROR 2015-03-17 19:54:18,386: it.openex.pmfew.filters.MultipartExceptionHandler - Could not parse multipart servlet request; nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (4522604) exceeds the configured maximum (3145728)

它重复了 7 次,并且过滤器中指定的重定向没有发生:我只是得到一个空白页。

我不确定会发生什么,因为有时在过滤器中添加调试断点使其工作,甚至添加Thread.sleep(100)

更新 1

有关该问题的更多信息。

每次我重新启动 Tomcat 时,过滤器都会被调用 2 次或 7 次。 只有一个 POST 调用。

chrome 中的响应是(failed) net::ERR_CONNECTION_RESET

请求是org.apache.catalina.connector.RequestFacade 的实例,包含org.apache.catalina.connector.Request 的实例。在后者内部,属性partsParseException 带有日志中显示的异常。 每次请求都是相同的。

链中有4个过滤器:

MultipartExceptionHandler(我添加的过滤器) org.springframework.web.multipart.support.MultipartFilter org.springframework.web.filter.DelegatingFilterProxy org.apache.tomcat.websocket.server.WsFilter

我将 Tomcat 升级到了最新版本:8.0.20。

我尝试重定向到像 / 这样的简单 url,并在过滤器中捕获所有类型的异常,包括可能由 catch 块抛出的异常。

结果是这样的

public class MultipartExceptionHandler extends OncePerRequestFilter 
    static final Logger log = LoggerFactory.getLogger(MultipartExceptionHandler.class);
    @Override
    protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain) throws ServletException, IOException 
        try 
            filterChain.doFilter(request, response);
         catch (final MultipartException me) 
            try
                log.error(me.getMessage());
                response.sendRedirect(UrlMap.HOME);
             catch (final Exception e)
                log.error(e.getMessage());
            

        catch (final Exception e)
            log.error(e.getMessage());
        
    

令人不安的是,它的行为似乎不是确定性的:使用 Intellij Idea,有时如果我在 `MultipartExceptionHandler' 的 catch 块中放置一个断点,然后恢复程序,它可以完美运行。

如果没有,我只需要重新启动 Tomcat 1 或 2 次,直到该技巧再次起作用。

没有断点,程序永远无法运行。

更新 2

我对应用程序的行为进行了更多测试。

我发现它只是时不时地开箱即用,无需重新启动 Tomcat 或做任何花哨的事情。

这是一个重试上传文件直到它顺利的问题,如果出现错误,按下浏览器返回按钮返回上传页面。

它工作 4-5 次中的 1 次,没有任何明显的重复模式。

如果失败,这是堆栈跟踪。在日志中它连续重复了2次,之后就没有其他内容了。

INFO  2015-03-18 12:21:59,870: it.openex.pmfew.filters.MultipartExceptionHandler - #############################
INFO  2015-03-18 12:21:59,870: it.openex.pmfew.filters.MultipartExceptionHandler - /registrazione?execution=e3s2
INFO  2015-03-18 12:21:59,870: it.openex.pmfew.filters.MultipartExceptionHandler - #############################
DEBUG 2015-03-18 12:21:59,870: org.springframework.web.multipart.support.MultipartFilter - Using MultipartResolver 'filterMultipartResolver' for MultipartFilter
DEBUG 2015-03-18 12:21:59,870: org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'filterMultipartResolver'
DEBUG 2015-03-18 12:21:59,870: org.springframework.web.multipart.support.MultipartFilter - Resolving multipart request [/registrazione] with MultipartFilter
ERROR 2015-03-18 12:21:59,870: it.openex.pmfew.filters.MultipartExceptionHandler - Could not parse multipart servlet request; nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (4522604) exceeds the configured maximum (3145728)
org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request; nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (4522604) exceeds the configured maximum (3145728)
    at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.parseRequest(StandardMultipartHttpServletRequest.java:99)
    at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.<init>(StandardMultipartHttpServletRequest.java:77)
    at org.springframework.web.multipart.support.StandardServletMultipartResolver.resolveMultipart(StandardServletMultipartResolver.java:76)
    at org.springframework.web.multipart.support.MultipartFilter.doFilterInternal(MultipartFilter.java:108)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at it.openex.pmfew.filters.MultipartExceptionHandler.doFilterInternal(MultipartExceptionHandler.java:29)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:501)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
    at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:610)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:516)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1086)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:659)
    at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:223)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1558)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1515)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (4522604) exceeds the configured maximum (3145728)
    at org.apache.catalina.connector.Request.parseParts(Request.java:2792)
    at org.apache.catalina.connector.Request.getParts(Request.java:2636)
    at org.apache.catalina.connector.RequestFacade.getParts(RequestFacade.java:1083)
    at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.parseRequest(StandardMultipartHttpServletRequest.java:84)
    ... 27 more
Caused by: org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (4522604) exceeds the configured maximum (3145728)
    at org.apache.tomcat.util.http.fileupload.FileUploadBase$FileItemIteratorImpl.<init>(FileUploadBase.java:811)
    at org.apache.tomcat.util.http.fileupload.FileUploadBase.getItemIterator(FileUploadBase.java:256)
    at org.apache.tomcat.util.http.fileupload.FileUploadBase.parseRequest(FileUploadBase.java:280)
    at org.apache.catalina.connector.Request.parseParts(Request.java:2725)
    ... 30 more

相反,如果进展顺利,则堆栈跟踪仅显示一次,然后附加(由于帖子的大小限制,我删除了一些部分)

INFO  2015-03-18 12:20:38,789: it.openex.pmfew.filters.MultipartExceptionHandler - #############################
INFO  2015-03-18 12:20:38,789: it.openex.pmfew.filters.MultipartExceptionHandler - Url called -> /registrazione?execution=e1s2&error=size-limit
INFO  2015-03-18 12:20:38,789: it.openex.pmfew.filters.MultipartExceptionHandler - #############################
DEBUG 2015-03-18 12:20:38,789: org.springframework.web.multipart.support.MultipartFilter - Using MultipartResolver 'filterMultipartResolver' for MultipartFilter
DEBUG 2015-03-18 12:20:38,789: org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'filterMultipartResolver'
DEBUG 2015-03-18 12:20:38,789: org.springframework.web.multipart.support.MultipartFilter - Request [/registrazione] is not a multipart request
DEBUG 2015-03-18 12:20:38,789: org.springframework.security.web.FilterChainProxy - /registrazione?execution=e1s2&error=size-limit at position 1 of 13 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
DEBUG 2015-03-18 12:20:38,789: org.springframework.security.web.FilterChainProxy - /registrazione?execution=e1s2&error=size-limit at position 2 of 13 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
DEBUG 2015-03-18 12:20:38,789: org.springframework.security.web.context.HttpSessionSecurityContextRepository - No SecurityContext was available from the HttpSession: org.apache.catalina.session.StandardSessionFacade@2fabad6. A new one will be created.
DEBUG 2015-03-18 12:20:38,789: org.springframework.security.web.FilterChainProxy - /registrazione?execution=e1s2&error=size-limit at position 3 of 13 in additional filter chain; firing Filter: 'HeaderWriterFilter'
DEBUG 2015-03-18 12:20:38,789: org.springframework.security.web.FilterChainProxy - /registrazione?execution=e1s2&error=size-limit at position 4 of 13 in additional filter chain; firing Filter: 'CharacterEncodingFilter'
DEBUG 2015-03-18 12:20:38,789: org.springframework.security.web.FilterChainProxy - /registrazione?execution=e1s2&error=size-limit at position 5 of 13 in additional filter chain; firing Filter: 'CsrfFilter'
DEBUG 2015-03-18 12:20:38,789: org.springframework.security.web.FilterChainProxy - /registrazione?execution=e1s2&error=size-limit at position 6 of 13 in additional filter chain; firing Filter: 'LogoutFilter'
DEBUG 2015-03-18 12:20:38,789: org.springframework.security.web.FilterChainProxy - /registrazione?execution=e1s2&error=size-limit at position 7 of 13 in additional filter chain; firing Filter: 'UsernamePasswordAuthenticationFilter'
DEBUG 2015-03-18 12:20:38,789: org.springframework.security.web.FilterChainProxy - /registrazione?execution=e1s2&error=size-limit at position 8 of 13 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
DEBUG 2015-03-18 12:20:38,789: org.springframework.security.web.FilterChainProxy - /registrazione?execution=e1s2&error=size-limit at position 9 of 13 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
DEBUG 2015-03-18 12:20:38,789: org.springframework.security.web.FilterChainProxy - /registrazione?execution=e1s2&error=size-limit at position 10 of 13 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
DEBUG 2015-03-18 12:20:38,789: org.springframework.security.web.FilterChainProxy - /registrazione?execution=e1s2&error=size-limit at position 11 of 13 in additional filter chain; firing Filter: 'SessionManagementFilter'
DEBUG 2015-03-18 12:20:38,789: org.springframework.security.web.FilterChainProxy - /registrazione?execution=e1s2&error=size-limit at position 12 of 13 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
DEBUG 2015-03-18 12:20:38,789: org.springframework.security.web.FilterChainProxy - /registrazione?execution=e1s2&error=size-limit at position 13 of 13 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
DEBUG 2015-03-18 12:20:38,789: org.springframework.security.web.access.intercept.FilterSecurityInterceptor - Secure object: FilterInvocation: URL: /registrazione?execution=e1s2&error=size-limit; Attributes: [permitAll]
DEBUG 2015-03-18 12:20:38,789: org.springframework.security.web.FilterChainProxy - /registrazione?execution=e1s2&error=size-limit reached end of additional filter chain; proceeding with original chain
DEBUG 2015-03-18 12:20:38,789: org.springframework.web.servlet.DispatcherServlet - DispatcherServlet with name 'DispatcherServlet' processing GET request for [/registrazione]
DEBUG 2015-03-18 12:20:38,789: org.springframework.webflow.mvc.servlet.FlowHandlerMapping - Mapping request with URI '/registrazione' to flow with id 'registrazione'
DEBUG 2015-03-18 12:20:38,789: org.springframework.webflow.executor.FlowExecutorImpl - Resuming flow execution with key 'e1s2
DEBUG 2015-03-18 12:20:38,790: org.springframework.webflow.conversation.impl.SessionBindingConversationManager - Locking conversation 1
DEBUG 2015-03-18 12:20:38,790: org.springframework.webflow.execution.repository.impl.DefaultFlowExecutionRepository - Getting flow execution with key 'e1s2'
DEBUG 2015-03-18 12:20:38,790: org.springframework.webflow.definition.registry.FlowDefinitionRegistryImpl - Getting FlowDefinition with id 'registrazione'
DEBUG 2015-03-18 12:20:38,809: org.springframework.webflow.execution.repository.impl.DefaultFlowExecutionRepository - Putting flow execution '[FlowExecutionImpl@23da7555 flow = 'registrazione', flowSessions = list[[FlowSessionImpl@4c63c9fd flow = 'registrazione', state = 'companyLogo', scope = map['viewScope' -> map['fileForm' -> it.openex.pmcommonw.form.FileForm@ae6eeae], 'menuDTO' -> list[it.openex.pmfew.dtos.MenuEntryDTO@2fcdd386, it.openex.pmfew.dtos.MenuEntryDTO@3ba665a6, it.openex.pmfew.dtos.MenuEntryDTO@5767063d], 'userCompanyInfoForm' -> it.openex.pmcommonw.form.UserCompanyInfoForm@6ac911e1]]]]' into repository
DEBUG 2015-03-18 12:20:38,810: org.springframework.webflow.execution.repository.impl.DefaultFlowExecutionRepository - Adding snapshot to group with id 2
DEBUG 2015-03-18 12:20:38,813: org.springframework.webflow.conversation.impl.SessionBindingConversationManager - Unlocking conversation 1
DEBUG 2015-03-18 12:20:38,813: org.springframework.web.servlet.DispatcherServlet - Successfully completed request
DEBUG 2015-03-18 12:20:38,813: org.springframework.security.web.access.ExceptionTranslationFilter - Chain processed normally

更新 3

直到现在我都尝试上传一个 4.5MB 大的文件。

使用 30MB 的文件,它永远不会工作。

更新 4

查看 Chrome 开发者工具中的 POST 请求,在 Timing 选项卡上,我可以看到请求已停止。 More info about this state.

Firefox 每次都使用 4.5MB 的大文件,正确重定向到错误页面。对于较大的文件(比如 7MB),它不起作用,浏览器会返回消息

The connection was reset

The connection to the server was reset while the page was loading.

更新 5

MultipartExceptionHandler 行内切换

response.sendRedirect(UrlUtils.buildFullRequestUrl(request) + "&error=size-limit");

final RequestDispatcher requestDispatcher = request.getRequestDispatcher("/");
requestDispatcher.forward(request, response);

它在 Chrome 中运行 4 次中有 3 次,对于大文件,它总是像往常一样失败。日志中没有任何变化。

【问题讨论】:

我知道我迟到了,但是如果请求太大,Tomcat 服务器会取消请求吗?使用 Spring Boot 2,您可以设置以下属性: server.tomcat.max-swallow-size=100MB 在此处查看更多信息:***.com/a/49098174/519035 【参考方案1】:

听起来像是多个请求。根据日志,发生异常时会调用MultipartExceptionHandler

无法解析多部分 servlet 请求;嵌套异常是 java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException:

也许你可以在protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain)之后添加一个记录器,在这里设置一个断点并检查请求。由于

,请求可能为 null 或其他原因

无法解析多部分 servlet 请求; ...请求被拒绝,因为它的大小...

在这种情况下UrlUtils.buildFullRequestUrl(request)也会抛出一个未被捕获的异常。

public final class UrlUtils 
    public static String buildFullRequestUrl(HttpServletRequest r) 
         return buildFullRequestUrl(r.getScheme(), r.getServerName(), r.getServerPort(), r.getRequestURI(),
                 r.getQueryString());
    
...

尝试手动设置重定向 uri,例如 response.sendRedirect(uri + "&amp;error=size-limit")。这意味着响应不能为空。

尝试使用模拟请求和模拟异常为您的文件上传编写测试,并断言预期的重定向 uri。

【讨论】:

谢谢托马斯,我根据你的建议更新了问题。 你能从你的日志中添加一个完整的堆栈跟踪吗?另请参阅 catalina 日志。【参考方案2】:

我处于同样的情况:我的 MultipartExceptionHandler 被多次调用 (3) 即使请求为 1(我可以在浏览器检查器中看到它)。

这导致我出现错误:ERR_CONNECTION_RESET

public class MultipartExceptionHandler extends OncePerRequestFilter 
    
    private final Logger log = LoggerFactory.getLogger(this.getClass());    

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException 
        try 
            filterChain.doFilter(request, response);
         catch (MaxUploadSizeExceededException e) 
            handle(request, response, e);
         catch (ServletException e) 
            if(e.getRootCause() instanceof MaxUploadSizeExceededException) 
                handle(request, response, (MaxUploadSizeExceededException) e.getRootCause());
             else 
                throw e;
            
         finally 
            String url = Utils.buildFullRequestUrl(request);
            log.debug("URL: " + url);
        
    

    private void handle(HttpServletRequest request, HttpServletResponse response, MaxUploadSizeExceededException e) throws IOException 
        String redirect = UrlUtils.buildFullRequestUrl(request) + "?upload-error=true";
        response.sendRedirect(redirect);
    



你有什么建议吗?

谢谢。

已解决

就我而言,问题是我错过了设置 Tomcat ma​​xSwallowSize 参数。

【讨论】:

以上是关于如何在 Spring 中处理 MultipartException的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Web 容器中使用 Spring 批处理获取 @Postconstruct?

如何在 Spring 批处理中重复一个步骤

如何在 Spring MVC-Spring Security 应用程序中处理 GWT RPC 调用的会话过期异常

如何在自定义 Spring Security 登录成功处理程序中注入会话 bean

如何在 Spring Data JPA 排序中定义空处理?

如何在 Spring MVC 中针对 HTML 和 JSON 请求以不同方式处理异常