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

2022-12-09 11:52:30 +08:00
 shiyu6226

最近在写一个 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());
    }
}
4062 次点击
所在节点    程序员
30 条回复
xiaohundun
2022-12-09 13:24:57 +08:00
应该不是文件大小配置的问题,因为不是这个异常,这个异常应该看看是不是网络的问题
zsj1029
2022-12-09 13:41:02 +08:00
大文件请用流式传输,普通文件操作 2g 会占用 2g 物理内存,大了会炸
Stendan
2022-12-09 13:55:22 +08:00
koloonps
2022-12-09 14:01:45 +08:00
不用流式传输就需要在客户端把文件切好分开上传,不然分分钟 OOM
shiyu6226
2022-12-09 14:29:12 +08:00
@xiaohundun 这个是用在内网环境下的,网络应该是不受影响
shiyu6226
2022-12-09 14:29:37 +08:00
shiyu6226
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
2022-12-09 14:37:12 +08:00
@Stendan
感谢,目前看来好像只有分片上传可行了
Xhack
2022-12-09 15:52:18 +08:00
try-with-resource
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
2022-12-09 16:55:37 +08:00
上面的简易文件流处理
另外
EOFException 的问题: 从文件中读取对象的时候,如何判断是否读取完毕。jvm 会给抛出 EOFException ,表示的是,文件中对象读取完毕。所以呢,你在判断是否读取结束的时候,捕获掉这个异常就可以,是捕获不是抛出。

重要的说三次,是捕获,捕获,捕获!
DinnyXu
2022-12-09 17:27:52 +08:00
几年开发了?控制层的代码写成这样?哈哈
V2Axiu
2022-12-09 17:35:43 +08:00
大文件还是分片吧。还能续传多好
eatFruit
2022-12-09 17:40:30 +08:00
@DinnyXu 那请问要怎么写才算得上“好”呢
shiyu6226
2022-12-09 19:09:58 +08:00
@DinnyXu
控制层不是越简单越好嘛…
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
2022-12-09 20:20:07 +08:00
对了打印日志的正确姿势是 log.error(e.getMessage(), e);这样才会打印异常栈
guyeu
2022-12-09 20:38:00 +08:00
@aguesuka 假装这是正确姿势。。。e.getMessage()是可以返回 null 的
bertieranO0o
2022-12-09 20:56:15 +08:00
@DinnyXu 老哥求指点这个哪里有问题😂
DinnyXu
2022-12-09 22:05:19 +08:00
@eatFruit
@shiyu6226
@bertieranO0o
不是代码的问题,是这种上传文件还是很老式的写法,现在很多上传文件或者图片都是使用 OSS 搭配使用,2G 的文件压缩一下上传到云端是很快的,而且还可以进行分片,将 2G 文件压缩后分成 N 个子压缩包进行断点续传,最终传到服务端的是一个或多个 URL ,后端再进行异步处理。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/901299

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX