前言
文件上傳功能,想必大家都實(shí)現(xiàn)過(guò),但最近自己卻踩到一個(gè)不怎么被人注意的坑:上傳文件過(guò)大導(dǎo)致瀏覽器出現(xiàn) ERR_CONNECTION_RESET
回顧
回想一下 Spring MVC 配置文件上傳的過(guò)程:
@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;
}
}
踩坑
問(wèn)題就出在這兩個(gè)參數(shù)的設(shè)置上:
resolver.setMaxUploadSizePerFile(5*1024*1024L);
resolver.setMaxUploadSize(10*1024*1024L);
第一行,我們把上傳中單個(gè)文件的最大值限制為 5MB;
第二行,我們把上傳數(shù)據(jù)總大小的最大值限制為 10MB。
即如下情況均是過(guò)大:
- 5MB + 5MB + 1MB (上傳3個(gè)文件,總量過(guò)大)
- 6MB + 4MB(上傳2個(gè)文件,單文件過(guò)大)
我們理想的處理邏輯思路有兩種:
- 當(dāng)上傳數(shù)據(jù)超過(guò)這里的限制時(shí)會(huì)拋出相應(yīng)的異常,這時(shí)我們使用 Spring MVC 的異常處理機(jī)制對(duì)異常進(jìn)行處理并跳轉(zhuǎn)到錯(cuò)誤提示頁(yè)面。
- 使用攔截器攔截上傳請(qǐng)求,發(fā)現(xiàn)上傳數(shù)據(jù)過(guò)大后,做進(jìn)一步的處理。
但是......實(shí)際效果卻是大相徑庭!
只要上傳數(shù)據(jù)大小超過(guò)這兩個(gè)參數(shù)的任何一個(gè),瀏覽器就會(huì)出現(xiàn) ERR_CONNECTION_RESET,表示連接已被重置。一開(kāi)始我認(rèn)為是攔截器沒(méi)有起作用,但經(jīng)過(guò)多次測(cè)試,才發(fā)現(xiàn)只要上傳數(shù)據(jù)過(guò)大,服務(wù)器程序(Tomcat、Jetty etc.)直接斷開(kāi)連接,請(qǐng)求根本到不了攔截器,連處理異常的機(jī)會(huì)都不給你。這次是服務(wù)器程序要背鍋了。
解決方案
既然如此,那就來(lái)者不拒!我們把這兩個(gè)參數(shù)設(shè)置的盡可能大,把處理邏輯交給攔截器和異常處理器,或者使用參考鏈接里給出的方案——配置相應(yīng)服務(wù)器程序的屬性,如 Tomcat 則需要配置 Connector 中的 maxSwallowSize ,默認(rèn)只有 2MB。詳情請(qǐng)閱讀: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 表達(dá)式解析,避免類型轉(zhuǎn)換異常
@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("上傳數(shù)據(jù)大小:{}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 實(shí)例根本沒(méi)有被創(chuàng)建就直接使用了?這是 Lombok 中 @slf4j 注解的功勞。這是一個(gè)代碼簡(jiǎn)化工具包,十分推薦大家了解并使用。
參考
[1] Could not upload large file to server by using Spring MVC