前言
文件上传功能,想必大家都实现过,但最近自己却踩到一个不怎么被人注意的坑:上传文件过大导致浏览器出现 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个文件,单文件过大)
我们理想的处理逻辑思路有两种:
- 当上传数据超过这里的限制时会抛出相应的异常,这时我们使用 Spring MVC 的异常处理机制对异常进行处理并跳转到错误提示页面。
- 使用拦截器拦截上传请求,发现上传数据过大后,做进一步的处理。
但是......实际效果却是大相径庭!
只要上传数据大小超过这两个参数的任何一个,浏览器就会出现 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