您现在的位置是:首页>博客详情

java文件上传失败问题解决

FreshMan2023年12月14日 13:43tomcat,tmp1003

简介Failed to parse multipart servlet request; nested exception is java.io.IOException: The temporary upload location [/tmp/tomcat.5688631209104267196.27206/work/Tomcat/localhost/ROOT] is not valid

异常信息:

Failed to parse multipart servlet request; nested exception is java.io.IOException: The temporary upload location [/tmp/tomcat.5688631209104267196.27206/work/Tomcat/localhost/ROOT] is not valid

接口实现:

@Override

public Resp uploadFile(@RequestPart(value = "file") MultipartFile file) {

  // 业务处理

}

异常分析:

异常提示tomcat容器的临时工作目录被删除了

代码分析:

1.请求进入org.springframework.web.servlet.DispatcherServlet#doDispatch方法
2.验证是否是multipart请求

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

HttpServletRequest processedRequest = request;

HandlerExecutionChain mappedHandler = null;

boolean multipartRequestParsed = false;


WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);


try {

ModelAndView mv = null;

Exception dispatchException = null;


try {

// 验证multipart

processedRequest = checkMultipart(request);

// 代码省略...

3.checkMultipart方法

protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {

if (this.multipartResolver != null && 

// org.springframework.web.multipart.support.StandardServletMultipartResolver#isMultipart

// 验证是否是post请求,Content-type是否是multipart/开头

this.multipartResolver.isMultipart(request)) {

if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {

logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " +

"this typically results from an additional MultipartFilter in web.xml");

}

else if (hasMultipartException(request) ) {

logger.debug("Multipart resolution failed for current request before - " +

"skipping re-resolution for undisturbed error rendering");

}

else {

try {

// 解析文件

// org.springframework.web.multipart.support.StandardServletMultipartResolver#resolveMultipart

// 创建StandardMultipartHttpServletRequest对象,解析文本请求org.springframework.web.multipart.support.StandardMultipartHttpServletRequest#parseRequest

return this.multipartResolver.resolveMultipart(request);

}

catch (MultipartException ex) {

if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {

logger.debug("Multipart resolution failed for error dispatch", ex);

// Keep processing error dispatch with regular request handle below

}

else {

throw ex;

}

}

}

}

// If not returned before: return original request.

return request;

}

4.parseRequest方法

private void parseRequest(HttpServletRequest request) {

try {

// 获取文本参数

// 进入org.apache.catalina.connector.Request#getParts方法

Collection<Part> parts = request.getParts();

this.multipartParameterNames = new LinkedHashSet<>(parts.size());

MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<>(parts.size());

for (Part part : parts) {

// 代码省略...

}

setMultipartFiles(files);

}

catch (Throwable ex) {

handleParseFailure(ex);

}

}

5.getParts方法

public Collection<Part> getParts() throws IOException, IllegalStateException, ServletException {

// 进入解析方法

        this.parseParts(true);

        if (this.partsParseException != null) {

            if (this.partsParseException instanceof IOException) {

            // 抛出了异常提示中的IO异常

                throw (IOException)this.partsParseException;

            }


            if (this.partsParseException instanceof IllegalStateException) {

                throw (IllegalStateException)this.partsParseException;

            }


            if (this.partsParseException instanceof ServletException) {

                throw (ServletException)this.partsParseException;

            }

        }

        return this.parts;

    }

6.parseParts方法

private void parseParts(boolean explicit) {

        if (this.parts == null && this.partsParseException == null) {

            Context context = this.getContext();

            MultipartConfigElement mce = this.getWrapper().getMultipartConfigElement();

            if (mce == null) {

                if (!context.getAllowCasualMultipartParsing()) {

                    if (explicit) {

                        this.partsParseException = new IllegalStateException(sm.getString("coyoteRequest.noMultipartConfig"));

                        return;

                    }


                    this.parts = Collections.emptyList();

                    return;

                }


                mce = new MultipartConfigElement((String)null, (long)this.connector.getMaxPostSize(), (long)this.connector.getMaxPostSize(), this.connector.getMaxPostSize());

            }


            Parameters parameters = this.coyoteRequest.getParameters();

            parameters.setLimit(this.getConnector().getMaxParameterCount());

            boolean success = false;


            try {

            // 获取本地路径,来自于@MultipartConfig的location,没有则返回空串

                String locationStr = mce.getLocation();

                File location;

                if (locationStr != null && locationStr.length() != 0) {

                    location = new File(locationStr);

                    if (!location.isAbsolute()) {

                        location = (new File((File)context.getServletContext().getAttribute("javax.servlet.context.tempdir"), locationStr)).getAbsoluteFile();

                    }

                } else {

                // 因为没有配置location,所有会到这里取tomcat的临时文件工作目录

                    location = (File)context.getServletContext().getAttribute("javax.servlet.context.tempdir");

                }

// 如果临时目录不存在就会抛异常

// 上述的异常原因就是因为目录被删了,导致的异常

                if (location.isDirectory()) {

                    // 代码省略...

                }

                // 给异常提示

parameters.setParseFailedReason(FailReason.MULTIPART_CONFIG_INVALID);

                this.partsParseException = new IOException(sm.getString("coyoteRequest.uploadLocationInvalid", new Object[]{location}));

            } finally {

                if (this.partsParseException != null || !success) {

                    parameters.setParseFailedReason(FailReason.UNKNOWN);

                }

            }

        }

    }

7.为什么javax.servlet.context.tempdir临时目录会被删除?

原因是因为在Linux系统中/tmp目录下的文件默认10天清理一次

cat /usr/lib/tmpfiles.d/tmp.conf

在这里插入图片描述

8.怎样解决问题?
方法一:修改Linux配置文件

在/usr/lib/tmpfiles.d/tmp.conf中添加临时目录清除排除规则

方法二:项目中指定basedir路径

// Tomcat base directory. If not specified, a temporary directory is used.

server.tomcat.basedir=/data/file

注意:配置的前提是Spring Boot 的web项目且使用的是tomcat容器,配置是针对tomcat容器的,basedir会在org.springframework.boot.autoconfigure.web.embedded.TomcatWebServerFactoryCustomizer#customize代码中设置的,并且server.tomcat.basedir配置不是实时生效的,需要重启项目,tomcat会重新加载配置,替换临时目录。

测试目录是否生效很简单,从tomcat的上下文中读取一下看看就可以了:

@RestController

@RequestMapping("/test")

public class TestController {

    @Autowired

    private HttpServletRequest httpServletRequest;


    @GetMapping("/ok")

    public String testOk() {

        // javax.servlet.context.tempdir

        Object attribute = httpServletRequest.getServletContext().getAttribute(ServletContext.TEMPDIR);

        return attribute == null ? "tmpdir is blank" : attribute.toString();

    }

}

org.springframework.boot.autoconfigure.web.embedded.TomcatWebServerFactoryCustomizer#customize代码中会设置basedir

@Override

public void customize(ConfigurableTomcatWebServerFactory factory) {

ServerProperties properties = this.serverProperties;

ServerProperties.Tomcat tomcatProperties = properties.getTomcat();

PropertyMapper propertyMapper = PropertyMapper.get();

// 会设置org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#baseDirectory字段的值

propertyMapper.from(tomcatProperties::getBasedir).whenNonNull()

.to(factory::setBaseDirectory);

设置basedir之后的webserver初始化会使用:
org.springframework.context.support.AbstractApplicationContext#refresh
org.springframework.context.support.AbstractApplicationContext#onRefresh
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#onRefresh
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#createWebServer

org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#getWebServer

@Override

public WebServer getWebServer(ServletContextInitializer... initializers) {

Tomcat tomcat = new Tomcat();

// 这个地方就会更改临时文件目录,如果没有配置就会创建默认的临时文件目录

File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");

tomcat.setBaseDir(baseDir.getAbsolutePath());

默认的临时文件目录创建:

/**

* Return the absolute temp dir for given web server.

* @param prefix server name

* @return the temp dir for given server.

*/

protected final File createTempDir(String prefix) {

try {

File tempDir = File.createTempFile(prefix + ".", "." + getPort());

tempDir.delete();

tempDir.mkdir();

tempDir.deleteOnExit();

return tempDir;

}

catch (IOException ex) {

throw new WebServerException(

"Unable to create tempDir. java.io.tmpdir is set to "

+ System.getProperty("java.io.tmpdir"),

ex);

}

}

创建临时文件

public static File createTempFile(String prefix, String suffix, File directory) throws IOException

    {

        if (prefix.length() < 3)

            throw new IllegalArgumentException("Prefix string too short");

        if (suffix == null)

            suffix = ".tmp";

// TempDirectory.location()获取的就是java.io.tmpdir目录,Linux中就是/tmp

// private static final File tmpdir = new File(AccessController.doPrivileged(new GetPropertyAction("java.io.tmpdir")));

        File tmpdir = (directory != null) ? directory

                                          : TempDirectory.location();

在这里插入图片描述

上一篇: PostgreSQL 排查慢 SQL

下一篇: 没有了