Linux下 Spring Boot 上传找不到临时目录, 出现500错误


错误原因

Linux 系统中,Spring Boot 应用以 java -jar 命令启动时,会在操作系统的 /tmp 目录下生成一个 tomcat(或 undertow )临时目录,上传的文件先要转换成临时文件保存在这个文件夹下面。由于临时 /tmp 目录下的文件,在长时间(10天)没有使用的情况下,系统执行了 tmp 目录清理服务(systemd-tmpfiles-clean.service),导致 /tmp/undertow...8090 文件被清理,然而在上传的时候,undertow 服务器需要创建/tmp/undertow...8090/undertow...upload 临时文件,但是调用 Files.createFile(...) 的时候就会发现找不到父目录,才导致了以上的错误。

具体错误日志(参考)

undertow

java.nio.file.NoSuchFileException: /tmp/undertow.17753558642503713859.8085/undertow7370242804103803588upload

Tomcat

The temporary upload location [/tmp/tomcat.7957874575370093230.8088/work/Tomcat/localhost/ROOT] is not valid

重现方法

找到类 io.undertow.server.handlers.form.MultiPartParserDefinition
定位到如下代码

@Override
public void beginPart(final HeaderMap headers) {
    this.currentFileSize = 0;
    this.headers = headers;
    final String disposition = headers.getFirst(Headers.CONTENT_DISPOSITION);
    if (disposition != null) {
        if (disposition.startsWith("form-data")) {
            currentName = Headers.extractQuotedValueFromHeader(disposition, "name");
            fileName = Headers.extractQuotedValueFromHeaderWithEncoding(disposition, "filename");
            if (fileName != null && fileSizeThreshold == 0) {
                try {
                    if (tempFileLocation != null) {
                        file = Files.createTempFile(tempFileLocation, "undertow", "upload");
                    } else {
                        file = Files.createTempFile("undertow", "upload");
                    }
                    createdFiles.add(file);
                    fileChannel = FileChannel.open(file, StandardOpenOption.READ, StandardOpenOption.WRITE);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

createdFiles.add(file); 处打断点,复制filepath 的值找到该文件并将其删除;放开断点,错误重现;

解决方案

方案1 (推荐)

applicaiton.yml(applicaiton.property) 中添加配置 :spring.servlet.multipart.location
spring.servlet.multipart.location 底层就是 createMultipartConfig 中的 factory.setLocation ,见源码:org.springframework.boot.autoconfigure.web.servlet.MultipartProperties#createMultipartConfig

spring:
  servlet:
    multipart:
      location: /data/tmp

手动指定目录后,必须保证该目录存在,并由读写的权限
创建该目录 mkdir -p /data/tmp

方法二

使用配置类配置, 类似方案1

@Bean
public MultipartConfigElement multipartConfigElement() {
    MultipartConfigFactory factory = new MultipartConfigFactory();
    factory.setLocation(System.getProperty("/data/tmp"));
    return factory.createMultipartConfig();
}

方案三(不推荐)

手动创建临时目录

mkdir -p /tmp/undertow.17753558642503713859.8085/undertow7370242804103803588upload

方案四(不推荐)

修改系统配置,排除该临时目录

vim /usr/lib/tmpfiles.d/tmp.con
# 文件最后添加
x /tmp/undertow*

作者: 小浪
版权声明: 本博客所有文章除特別声明外, 均采用 CC BY 4.0 许可协议。转载请注明来源 小浪!
  目录