求助, Java 接口上传 2G 以上大文件 EOFException: null - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
shiyu6226
V2EX    程序员

求助, Java 接口上传 2G 以上大文件 EOFException: null

  shiyu6226 2022-12-09 11:52:30 +08:00 4649 次点击
这是一个创建于 1044 天前的主题,其中的信息可能已经有所发展或是发生改变。

最近在写一个 web 端的私人网盘服务,测试发现上传 2G 以上大文件时 后台会出现异常,请问有大佬做过相关的需求吗?怎么解决这类问题?

异常日志如下

ERROR 19593 --- [io-18073-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.multipart.MultipartException: Failed to parse multipart servlet request; nested exception is java.io.IOException: org.apache.tomcat.util.http.fileupload.impl.IOFileUploadException: Processing of multipart/form-data request failed. java.io.EOFException] with root cause

java.io.EOFException: null ...

我修改了好多参数也不好使

@Configuration @Slf4j public class EmbeddedTomcatConfig implements WebServerFactoryCustomizer {

@Override public void customize(ConfigurableServletWebServerFactory factory) { log.info("Init EmbeddedTomcatConfig..."); ((TomcatServletWebServerFactory)factory).addConnectorCustomizers(new TomcatConnectorCustomizer() { @Override public void customize(Connector connector) { Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler(); protocol.setMaxConnections(3000); protocol.setMaxThreads(800); protocol.setAcceptCount(200); protocol.setSelectorTimeout(30000); protocol.setSessionTimeout(60000 * 2); protocol.setConnectionTimeout(60000 * 5); protocol.setDisableUploadTimeout(false); protocol.setConnectionUploadTimeout(60000 * 10); } }); } 

}

application 参数

spring.servlet.multipart.max-request-size=-1 spring.servlet.multipart.max-file-size=-1 server.tomcat.max-swallow-size=-1 server.tomcat.max-http-form-post-size=-1

控制层

@ResponseBody @ApiOperation(value = "上传文件",notes = "上传文件") @RequestMapping(value = "/FilesUpload",method = RequestMethod.POST) public BaseResponse uploadFiles( @RequestParam(required = true) MultipartFile files, HttpServletRequest request, HttpServletResponse response ) { if (files.isEmpty() || files.getSize() == 0) { response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED); return BaseResponse.initErrorBaseResponse("不能上传空文件!"); } try { return BaseResponse.initSuccessBaseResponse(fileExecuteService.uploadFiles(files,request), "操作成功"); } catch (Exception e) { response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED); return BaseResponse.initErrorBaseResponse(e.getMessage()); } } 
30 条回复    2022-12-10 18:18:38 +08:00
xiaohundun
    1
xiaohundun  
   2022-12-09 13:24:57 +08:00
应该不是文件大小配置的问题,因为不是这个异常,这个异常应该看看是不是网络的问题
zsj1029
    2
zsj1029  
   2022-12-09 13:41:02 +08:00   1
大文件请用流式传输,普通文件操作 2g 会占用 2g 物理内存,大了会炸
Stendan
    3
Stendan  
   2022-12-09 13:55:22 +08:00
koloonps
    4
koloonps  
   2022-12-09 14:01:45 +08:00
不用流式传输就需要在客户端把文件切好分开上传,不然分分钟 OOM
shiyu6226
    5
shiyu6226  
OP
   2022-12-09 14:29:12 +08:00
@xiaohundun 这个是用在内网环境下的,网络应该是不受影响
shiyu6226
    6
shiyu6226  
OP
   2022-12-09 14:29:37 +08:00
shiyu6226
    7
shiyu6226  
OP
   2022-12-09 14:33:08 +08:00
@zsj1029
@koloonps

实现方式是用的缓冲流写入的,实际运行过程中,内存使用一直保持在 500MB 上下,代码如下

private static boolean writeFileToLocal(String toLocalFilePath, MultipartFile file) throws Exception {
boolean flag = false;

BufferedOutputStream bufferedOutputStream = null;
BufferedInputStream bufferedInputStream = null;
try {
bufferedInputStream = new BufferedInputStream(file.getInputStream());
bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(toLocalFilePath));

int index;
byte[] bytes = new byte[4096];
while ((index = bufferedInputStream.read(bytes)) != -1) {
bufferedOutputStream.write(bytes, 0, index);
bufferedOutputStream.flush();
}
flag = true;
} catch (IOException e) {
log.error("文件写入失败," + e.getMessage());
if (new File(toLocalFilePath).exists()) {
new File(toLocalFilePath).delete();
}
throw new Exception(e.getMessage());
} finally {
if (bufferedOutputStream != null) {
try {
bufferedOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedInputStream != null) {
try {
bufferedInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
System.gc();
}
shiyu6226
    8
shiyu6226  
OP
   2022-12-09 14:37:12 +08:00
@Stendan
感谢,目前看来好像只有分片上传可行了
Xhack
    9
Xhack  
   2022-12-09 15:52:18 +08:00
try-with-resource
zsj1029
    10
zsj1029  
   2022-12-09 16:44:59 +08:00
@shiyu6226
```
@RequestMapping(
value = "url", method = RequestMethod.POST
)
public void uploadFile(
@RequestParam("file") MultipartFile file
) throws IOException {

InputStream input = upfile.getInputStream();
Path path = Paths.get(path);//check path
OutputStream output = Files.newOutputStream(path);
IOUtils.copy(in, out);
//org.apache.commons.io.IOUtils or you can create IOUtils.copy

}
```

You should close your stream after whole data is written.
zsj1029
    11
zsj1029  
   2022-12-09 16:55:37 +08:00   1
上面的简易文件流处理
另外
EOFException 的问题: 从文件中读取对象的时候,如何判断是否读取完毕。jvm 会给抛出 EOFException ,表示的是,文件中对象读取完毕。所以呢,你在判断是否读取结束的时候,捕获掉这个异常就可以,是捕获不是抛出。

重要的说三次,是捕获,捕获,捕获!
DinnyXu
    12
DinnyXu  
   2022-12-09 17:27:52 +08:00
几年开发了?控制层的代码写成这样?哈哈
V2Axiu
    13
V2Axiu  
   2022-12-09 17:35:43 +08:00
大文件还是分片吧。还能续传多好
eatFruit
    14
eatFruit  
   2022-12-09 17:40:30 +08:00
@DinnyXu 那请问要怎么写才算得上“好”呢
shiyu6226
    15
shiyu6226  
OP
   2022-12-09 19:09:58 +08:00 via iPhone
@DinnyXu
控制层不是越简单越好嘛…
aguesuka
    16
aguesuka  
   2022-12-09 20:09:42 +08:00
从你发的代码看不出啥问题.
首先得把问题定位到行, 你发的错误中只有信息而没有异常栈, 因为你的 controller catch 到异常没有打印日志(注意要打印异常栈). 打印异常栈后, 再判断是不是在你的 service 中报的错.

如果是在 service 中, 那直接把 service 改成

```
private static void writeFileToLocal(String toLocalFilePath, MultipartFile file) throws IOException {
try(InputStream inputStream = file.getInputStream()){
Files.copy(inputStream, Path.of(toLocalFilePath), StandardCopyOption.REPLACE_EXISTING);
}
}
```
不要直接使用 inputstream, 不要吞异常, 不要 gc;
aguesuka
    17
aguesuka  
   2022-12-09 20:20:07 +08:00
对了打印日志的正确姿势是 log.error(e.getMessage(), e);这样才会打印异常栈
guyeu
    18
guyeu  
   2022-12-09 20:38:00 +08:00
@aguesuka 假装这是正确姿势。。。e.getMessage()是可以返回 null 的
bertieranO0o
    19
bertieranO0o  
   2022-12-09 20:56:15 +08:00
@DinnyXu 老哥求指点这个哪里有问题
DinnyXu
    20
DinnyXu  
   2022-12-09 22:05:19 +08:00
@eatFruit
@shiyu6226
@bertieranO0o
不是代码的问题,是这种上传文件还是很老式的写法,现在很多上传文件或者图片都是使用 OSS 搭配使用,2G 的文件压缩一下上传到云端是很快的,而且还可以进行分片,将 2G 文件压缩后分成 N 个子压缩包进行断点续传,最终传到服务端的是一个或多个 URL ,后端再进行异步处理。
DinnyXu
    21
DinnyXu  
   2022-12-09 22:09:45 +08:00
说一个大概的思路,前端检测上传的文件大小,根据文件大小进行压缩分片,比如 1G 的文件,分成 1024 个压缩包,每个压缩包 1M ,然后进行轮询请求上传到 OSS ,将 1024 个文件放到一个文件夹, 获取 OSS 的文件夹路径传给后端,由后端根据此路径读取 OSS 上传的所有分片文件,然后进行异步处理组装。
shiyu6226
    22
shiyu6226  
OP
   2022-12-09 23:29:12 +08:00 via iPhone
@aguesuka
异常不在 service 也不在 controller ,我观察到的情况是 tomcat 缓存目录正在接收大文件时 到 2 个多 G 就中断 出异常了。
堆栈日志其实是打印全的,但是有点多,我就只发了主要的
aguesuka
    23
aguesuka  
   2022-12-10 00:04:05 +08:00
@guyeu null 就 null 呗, 异常栈都打出来了
aguesuka
    24
aguesuka  
   2022-12-10 00:14:28 +08:00
@guyeu e.getMessage 改成啥都可以, 重点是要把 error 当第二个参数传进去
aguesuka
    25
aguesuka  
   2022-12-10 00:55:01 +08:00
spring.servlet.multipart.max-file-size: -1
spring.servlet.multipart.max-request-size: -1
亲测 ok, 依赖只有 spring-boot-starter-web:3.0.0
aguesuka
    26
aguesuka  
   2022-12-10 02:00:27 +08:00
multipart 文件在转成 MultipartFile 的时候必须读完整个流, 所以会缓存到内存或硬盘里, 估计还有别的配置到上限了.
bertieranO0o
    27
bertieranO0o  
   2022-12-10 02:28:45 +08:00
@aguesuka 一般不建议文件大小设置无限大,给个业务范围内的合理上限值即可
bertieranO0o
    28
bertieranO0o  
   2022-12-10 02:31:19 +08:00
@DinnyXu 根本不需要这么复杂,就拿你举例的 OSS 来说,记得 19 年的时候 OSS 都已经有很成熟的支持分片上传的 API 了
DinnyXu
    29
DinnyXu  
   2022-12-10 15:28:29 +08:00
@bertieranO0o 你也知道你调用的是 API 哈,我说的是逻辑思维,API 谁不会调
bertieranO0o
    30
bertieranO0o  
   2022-12-10 18:18:38 +08:00
@DinnyXu 想听听你的“异步处理组装”的逻辑
关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2606 人在线   最高记录 6679       Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 45ms UTC 12:26 PVG 20:26 LAX 05:26 JFK 08:26
Do have faith in what you're doing.
ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86