浅谈Spring WebFlux文件上传解析
字数 2277 2025-08-06 12:21:05
Spring WebFlux 文件上传解析技术详解
一、Spring WebFlux 文件上传基础
1.1 核心组件
在 Spring WebFlux 中,文件上传主要通过 org.springframework.http.codec.multipart.FilePart 类处理,与 Spring MVC 的 MultipartFile 不同,它是基于 Reactor 的非阻塞实现。
基本使用示例:
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Mono<Map<String, Object>> upload(
@RequestPart("file") final FilePart filePart,
final FormData formData
) {
final File directory = new File(UPLOAD_DIRECTORY);
if(!directory.exists()){
directory.mkdirs();
}
final File file = new File(directory, filePart.filename());
return filePart
.transferTo(file)
.then(Mono.fromCallable(() -> {
final Map<String, Object> map = new HashMap<>();
map.put("name", file.getName());
map.put("lastModified", file.lastModified());
map.put("size", file.length());
return map;
}));
}
1.2 请求处理流程
DefaultServerWebExchange 是核心处理类,封装了 HTTP 请求和响应,其 initMultipartData 方法负责初始化 multipart 请求数据。
二、Multipart 请求判断机制
2.1 判断逻辑
Spring WebFlux 通过以下方式判断是否为 multipart 请求:
- 获取请求头的
Content-Type - 调用
MediaType#isCompatibleWith方法判断
判断规则:
- 当
Content-Type为multipart/*或multipart/form-data时认为是 multipart 请求 - 支持大小写转换
2.2 与其他框架对比
| 框架 | 判断方式 |
|---|---|
| Struts2 | content_type.contains("multipart/form-data") |
| Spring MVC (StandardServletMultipartResolver) | StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/") |
| Spring MVC (CommonsMultipartResolver) | 检查请求方法和 FileUploadBase.isMultipartContent() |
三、文件上传解析过程
3.1 解析器类型
Spring WebFlux 提供两种解析器:
- DefaultPartHttpMessageReader (默认)
- SynchronossPartHttpMessageReader (基于 Synchronoss NIO Multipart 库)
配置 Synchronoss 解析器示例:
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
SynchronossPartHttpMessageReader reader = new SynchronossPartHttpMessageReader();
reader.setMaxParts(1);
reader.setMaxDiskUsagePerPart(10L * 1024L);
reader.setEnableLoggingRequestDetails(true);
MultipartHttpMessageReader multipartReader = new MultipartHttpMessageReader(reader);
multipartReader.setEnableLoggingRequestDetails(true);
configurer.defaultCodecs().multipartReader(multipartReader);
}
}
3.2 DefaultPartHttpMessageReader 解析流程
- 根据 header 调用
PartGenerator#newPart处理每个 part - 判断是否为 form field
- 解析
filename参数:- 优先解析
filename*=(RFC 5987 编码) - 其次解析
filename="value" - 最后直接获取 value
- 优先解析
文件名解析关键点:
- 支持
=?编码方式?B?编码内容?=格式的 Base64 编码 filename*=处理 UTF-8 编码文件名,格式为UTF-8'任意内容'文件名
3.3 SynchronossPartHttpMessageReader 解析流程
- 调用
MultipartUtils#getFileName处理 headers - 将 headerName 转为小写获取
content-disposition - 使用
ParameterParser解析器处理:- 按
=划分 paramName 和 paramValue - 对参数值进行解码 (
MimeUtility#decodeText)
- 按
- 对文件名进行 trim 操作
3.4 两种解析器关键区别
| 特性 | DefaultPartHttpMessageReader | SynchronossPartHttpMessageReader |
|---|---|---|
filename*= 支持 |
支持 | 不支持 |
| 文件名 trim | 不处理 | 自动 trim |
| 多 filename 处理 | 取第一个 | 取最后一个 |
=? 开头解析 |
正则匹配 | 空白字符处理 |
四、安全注意事项
4.1 路径穿越风险
两种解析器均未对 ../ 类路径进行过滤,可能导致:
- 跨目录文件写入
- 定时任务注入 (
/etc/cron.d/) - SSH 公钥写入
- JDK 系统 jar 替换
4.2 与 Spring MVC 的差异
Spring WebFlux 的 @RequestParam 仅支持 URL 传参,不支持 form-data 和 multipart,必须使用 ServerWebExchange 显式处理:
// 处理 form-data
@PostMapping("/manage")
public Mono<String> manage(ServerWebExchange exchange) {
return exchange.getFormData()
.flatMap(formData -> {
String param = formData.getFirst("param");
return Mono.just("Param: " + param);
});
}
// 处理 multipart
@PostMapping(value = "/handleMultipartRequest", consumes = "multipart/form-data")
public Mono<String> handleMultipartRequest(ServerWebExchange exchange) {
return exchange.getMultipartData()
.flatMap(parts -> {
Part part = parts.getFirst("param");
if (part instanceof FormFieldPart){
return Mono.just("param: " + ((FormFieldPart) part).value());
}
return Mono.just("param: " + null);
});
}
五、最佳实践建议
-
文件名安全处理:
- 校验文件名是否包含路径穿越字符
- 对上传文件重命名
- 限制上传目录
-
解析器选择:
- 默认解析器功能更全面
- Synchronoss 解析器适合需要精细控制的场景
-
性能调优:
- 设置合理的
maxParts和maxDiskUsagePerPart - 启用日志记录 (
setEnableLoggingRequestDetails)
- 设置合理的
-
安全防护:
- 严格校验 Content-Type
- 实现文件类型检查
- 考虑使用专门的文件存储服务