Spring MVC: 大文件上传之 ERR_CONNECTION_RESET

前言

文件上传功能,想必大家都实现过,但最近自己却踩到一个不怎么被人注意的坑:上传文件过大导致浏览器出现 ERR_CONNECTION_RESET

回顾

回想一下 Spring MVC 配置文件上传的过程:

@Configuration
@EnableWebMvc
@ComponentScan("your.controller")
public class WebConfig extends WebMvcConfigurerAdapter {
    // 在该类中配置文件上传解析器的 Bean 交给 Spring 管理即可
    @Bean
    public CommonsMultipartResolver multipartResolver() throws IOException {
        CommonsMultipartResolver  resolver = new CommonsMultipartResolver();
        resolver.setDefaultEncoding("utf-8");
        resolver.setUploadTempDir(
                new FileSystemResource(uploadTempDir));
        resolver.setMaxInMemorySize(0); 
        resolver.setMaxUploadSizePerFile(5*1024*1024L); 
        resolver.setMaxUploadSize(10*1024*1024L);
        return resolver;
    }
}

踩坑

问题就出在这两个参数的设置上:

resolver.setMaxUploadSizePerFile(5*1024*1024L); 
resolver.setMaxUploadSize(10*1024*1024L);

第一行,我们把上传中单个文件的最大值限制为 5MB
第二行,我们把上传数据总大小的最大值限制为 10MB
即如下情况均是过大:

  • 5MB + 5MB + 1MB (上传3个文件,总量过大)
  • 6MB + 4MB(上传2个文件,单文件过大)

我们理想的处理逻辑思路有两种:

  1. 当上传数据超过这里的限制时会抛出相应的异常,这时我们使用 Spring MVC 的异常处理机制对异常进行处理并跳转到错误提示页面。
  2. 使用拦截器拦截上传请求,发现上传数据过大后,做进一步的处理。

但是......实际效果却是大相径庭!


只要上传数据大小超过这两个参数的任何一个,浏览器就会出现 ERR_CONNECTION_RESET,表示连接已被重置。一开始我认为是拦截器没有起作用,但经过多次测试,才发现只要上传数据过大,服务器程序(Tomcat、Jetty etc.)直接断开连接,请求根本到不了拦截器,连处理异常的机会都不给你。这次是服务器程序要背锅了。

解决方案

既然如此,那就来者不拒!我们把这两个参数设置的尽可能大,把处理逻辑交给拦截器和异常处理器,或者使用参考链接里给出的方案——配置相应服务器程序的属性,如 Tomcat 则需要配置 Connector 中的 maxSwallowSize ,默认只有 2MB。详情请阅读:Tomcat 8 - HTTP Connector

resolver.setMaxUploadSizePerFile(666*1024*1024*1024L); // 666GB
resolver.setMaxUploadSize(666*1024*1024*1024L); // 666GB

拦截器

@Slf4j
@Component
@PropertySource("classpath:app.properties")
public class FileUploadInterceptor implements HandlerInterceptor {
    // 使用 SpEL 表达式解析,避免类型转换异常
    @Value("#{T(Long).parseLong('${upload.maxsize}')}")
    private Long maxSize;

    @Override
    public boolean preHandle(
            HttpServletRequest request,
            HttpServletResponse response,
            Object handler) {
        if (request != null &&
                ServletFileUpload.isMultipartContent(request)) {
            ServletRequestContext requestContext = new ServletRequestContext(request);
            long requestSize = requestContext.contentLength();
            log.info("上传数据大小:{}MB", requestSize/1024/1024);
            if (requestSize > maxSize) {
                throw new MaxUploadSizeExceededException(maxSize);
            }
        }
        return true;
    }

    @Override
    public void postHandle(
            HttpServletRequest request,
            HttpServletResponse response,
            Object handler,
            ModelAndView modelAndView) throws Exception {
    }

    @Override
    public void afterCompletion(
            HttpServletRequest request,
            HttpServletResponse response,
            Object handler, Exception ex) throws Exception {
    }
}

异常处理

@Slf4j
@ControllerAdvice
public class AppExceptionHandler {
    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public String handleUploadOverSize(
            MaxUploadSizeExceededException e,
            Model model) {
        log.error(e.getMessage());
        model.addAttribute("message", "上传失败!文件太大了!!!");
        return "home";
    }
}

tips: 大家也许注意到代码里 log 实例根本没有被创建就直接使用了?这是 Lombok 中 @slf4j 注解的功劳。这是一个代码简化工具包,十分推荐大家了解并使用。

参考

[1] Could not upload large file to server by using Spring MVC

你可能感兴趣的:(Spring MVC: 大文件上传之 ERR_CONNECTION_RESET)