引言
在实际的 Web 系统中,文件上传是非常常见的需求。然而,如果用户上传的文件体积过大,尤其是在上传还未完成的过程中,可能导致:
- 服务器内存、磁盘空间被大量占用;
- IO 队列阻塞,影响其他正常请求;
- 服务性能下降甚至宕机。
因此,在 Java 后台开发中,实现“上传过程中实时检测文件大小并主动中止上传”,不仅是一种安全防御机制,更是对系统资源的一种有效保护策略。
场景需求拆解
本需求的核心是:
- 不是上传完再判断,而是上传过程中边读边判断;
- 一旦发现文件大小超过阈值,立刻中止上传过程,并返回明确的错误响应;
- 兼容常见上传方式,如
application/octet-stream
和multipart/form-data
。
推荐实现方案
以下是两种可行的、生产级别推荐的实现方式:
一、方案一:基于流读取实现(适合非 multipart 表单)
1. 原理说明
直接使用 HttpServletRequest.getInputStream()
来读取上传内容,不借助 Spring 或 Servlet 的 Multipart 解析机制,从而可以手动读取字节流并实时判断数据总量。
2. 使用场景
适用于客户端以 Content-Type: application/octet-stream
上传文件的场景,如使用 curl
或前端使用 fetch
+ Blob 方式。
3. 核心实现代码
@WebServlet(name = "StreamUploadServlet", urlPatterns = "/upload/stream")
public class StreamUploadServlet extends HttpServlet {
private static final long MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException {
ServletInputStream inputStream = request.getInputStream();
long totalBytes = 0;
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
totalBytes += bytesRead;
if (totalBytes > MAX_FILE_SIZE) {
response.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE,
"文件大小超过10MB限制,上传已中止");
return;
}
// 可选:写入到临时文件或字节流中
}
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().write("文件上传成功,大小为:" + totalBytes + " 字节");
}
}
4. 前端调用方式示例
curl -X POST --data-binary @largefile.zip http://localhost:8080/upload/stream
或使用 JavaScript:
fetch('/upload/stream', {
method: 'POST',
headers: { 'Content-Type': 'application/octet-stream' },
body: fileBlob
});
二、方案二:使用 Apache Commons FileUpload 实现边读边判断(适用于 multipart/form-data)
1. 原理说明
Apache 提供的 Commons FileUpload 是一个底层的 multipart 解析库,支持:
- 自定义读取过程;
- 限制单个文件大小和整个请求大小;
- 设置上传进度监听器,可以在过程中检测字节数并抛出异常中断处理。
2. 适用场景
适合客户端使用标准 HTML 表单或 Ajax 提交 multipart/form-data
格式的上传请求。
3. 实现示例
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
// 限制文件大小
upload.setFileSizeMax(10 * 1024 * 1024);
// 设置上传监听器
upload.setProgressListener(new ProgressListener() {
@Override
public void update(long bytesRead, long contentLength, int itemIndex) {
if (bytesRead > 10 * 1024 * 1024) {
throw new FileUploadBase.FileSizeLimitExceededException(
"文件过大", bytesRead, 10 * 1024 * 1024);
}
}
});
try {
List<FileItem> items = upload.parseRequest(request);
for (FileItem item : items) {
if (!item.isFormField()) {
InputStream stream = item.getInputStream();
// 处理文件内容,例如保存到本地
}
}
response.getWriter().write("上传成功");
} catch (FileUploadBase.FileSizeLimitExceededException e) {
response.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, "上传文件大小超限");
}
三、辅助方案:服务器层防护(如 Nginx)
建议在 Java 后台之外,从网关或 Nginx 层限制上传请求体大小,从源头拦截大文件:
Nginx 配置:
server {
client_max_body_size 10M;
}
该配置将在 HTTP 层直接拒绝超过限制的请求,返回 413 状态码。
四、Spring Boot 配置上传大小限制(作为兜底防线)
在 Spring Boot 中配置文件大小限制是最常见的方式,但无法实现上传过程中中断,只能用作兜底策略:
spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 15MB
全局异常处理示例:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MaxUploadSizeExceededException.class)
public ResponseEntity<String> handleMaxSize(MaxUploadSizeExceededException ex) {
return ResponseEntity.status(HttpStatus.PAYLOAD_TOO_LARGE)
.body("上传文件超过大小限制");
}
}
补充:不推荐的方式说明(为何不能实现实时判断)
1. 拦截器(Interceptor)
Spring 的 HandlerInterceptor
拦截的是控制器方法调用之前,但此时请求体(包括 multipart 文件)已经被解析。你无法在这个阶段控制上传流,因此无法用于实时判断。
适合用来处理权限、认证等逻辑,不适合用于文件大小控制。
2. ServletRequestListener
Servlet API 中的 ServletRequestListener
只能监听请求创建和销毁事件,不提供对请求流或上传进度的访问,无法进行流式控制或中断上传。
通常用于记录日志、性能分析或审计目的,不适合用于上传拦截。
总结与推荐
方案 | 实现方式 | 是否实时拦截 | 适用场景 | 推荐等级 |
---|---|---|---|---|
流读取(getInputStream) | 原始流操作 | 是 | application/octet-stream 上传 | ⭐⭐⭐⭐⭐ |
Apache Commons FileUpload | multipart 手动解析 + 监听 | 是 | multipart/form-data 上传 | ⭐⭐⭐⭐ |
Spring Boot Multipart 限制 | 配置文件 | 否(事后拦截) | 通用场景 | ⭐⭐⭐ |
Nginx 限制 | 服务层配置 | 是 | 所有上传请求 | ⭐⭐⭐⭐ |
拦截器 / 监听器 | 框架机制 | 否 | 无法拦截文件流 | 不推荐 |
最佳实践建议
- 对于非表单上传(如 WebSocket、直传服务等),使用
getInputStream()
处理上传流; - 对于表单上传(multipart/form-data),使用 Apache Commons FileUpload 替代 Spring Multipart 解析器;
- 配合使用 Spring Boot 的上传大小限制和全局异常处理做兜底;
- 从网关或 Nginx 层进行最大请求大小限制,防止恶意攻击;
- 对上传操作进行统一封装和日志监控,方便后续扩展审计功能。