SpringBoot上传文件异常 org.springframework.web.multipart.MultipartException

现象

上传文件会报如下错误

2023-06-16 14:11:36 -Failed to parse multipart servlet request; nested exception is java.io.IOException: The temporary upload location [/tmp/tomcat.118932910164342315.9180/work/Tomcat/localhost/ROOT] is not valid
org.springframework.web.multipart.MultipartException: Failed to parse multipart servlet request; nested exception is java.io.IOException: The temporary upload location [/tmp/tomcat.118932910164342315.9180/work/Tomcat/localhost/ROOT] is not valid
	at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.handleParseFailure(StandardMultipartHttpServletRequest.java:122)
	at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.parseRequest(StandardMultipartHttpServletRequest.java:113)
	at org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.<init>(StandardMultipartHttpServletRequest.java:86)
	at org.springframework.web.multipart.support.StandardServletMultipartResolver.resolveMultipart(StandardServletMultipartResolver.java:93)
	at org.springframework.web.servlet.DispatcherServlet.checkMultipart(DispatcherServlet.java:1128)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:960)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:925)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:974)
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:877)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:661)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:851)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:61)
	at org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108)
	at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137)
	at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
	at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66)
	at org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain(AbstractShiroFilter.java:449)
	at org.apache.shiro.web.servlet.AbstractShiroFilter$1.call(AbstractShiroFilter.java:365)
	at org.apache.shiro.subject.support.SubjectCallable.doCall(SubjectCallable.java:90)
	at org.apache.shiro.subject.support.SubjectCallable.call(SubjectCallable.java:83)
	at org.apache.shiro.subject.support.DelegatingSubject.execute(DelegatingSubject.java:383)
	at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:362)
	at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at com.soft863.system.filter.AllowOriginFilter.doFilterInternal(AllowOriginFilter.java:39)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at com.alibaba.druid.support.http.WebStatFilter.doFilter(WebStatFilter.java:123)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at com.soft863.system.filter.AllowOriginFilter.doFilterInternal(AllowOriginFilter.java:39)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:109)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:496)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1468)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.lang.Thread.run(Thread.java:748)
Caused by: java.io.IOException: The temporary upload location [/tmp/tomcat.118932910164342315.9180/work/Tomcat/localhost/ROOT] is not valid
	at org.apache.catalina.connector.Request.parseParts(Request.java:2859)
	at org.apache.catalina.connector.Request.parseParameters(Request.java:3232)
	at org.apache.catalina.connector.Request.getParameter(Request.java:1137)
	at org.apache.catalina.connector.RequestFacade.getParameter(RequestFacade.java:381)
	at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:84)
	... 23 common frames omitted

原因及解决方案

原因

该问题经常出现在Linux操作系统中,因为Springboot(其实应该是tomcat)处理带有文件的请求时,是先将文件存放在临时目录当中,这个临时目录是在容器启动的时候指定的,默认在临时文件夹下,有时候操作系统将临时目录清掉之后,就会出现java.io.IOException: The temporary upload location [/tmp/tomcat.118932910164342315.9180/work/Tomcat/localhost/ROOT] is not valid这个异常。

解决方案

通过配置文件指定临时文件存放的路径

spring:
  servlet:
      location: /opt/creg/temp_file

如果是tomcat部署的修改context.xml文件

<Context ...> 
    ... 
    <Parameter name="javax.servlet.context.tempdir" value="/xxx/yyy/....." 
     override="false"/> 
    ... 
</Context>

阅读源码掌握报错的根因

首先当一个post请求到来之后,DispatcherServlet会通过下面的方法检测content-type是否是multipart,如果是调用StandardMultipartHttpServletRequest来处理这个请求,入口在return this.multipartResolver.resolveMultipart(request);

    protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
    
    
        if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
    
    
            if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
    
    
                if (request.getDispatcherType().equals(DispatcherType.REQUEST)) {
    
    
                    this.logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
                }
            } else if (this.hasMultipartException(request)) {
    
    
                this.logger.debug("Multipart resolution previously failed for current request - skipping re-resolution for undisturbed error rendering");
            } else {
    
    
                try {
    
    
                    return this.multipartResolver.resolveMultipart(request);
                } catch (MultipartException var3) {
    
    
                    if (request.getAttribute("javax.servlet.error.exception") == null) {
    
    
                        throw var3;
                    }
                }

                this.logger.debug("Multipart resolution failed for error dispatch", var3);
            }
        }

        return request;
    }

下面我们来看一下StandardMultipartHttpServletRequest是怎么处理的

    private void parseRequest(HttpServletRequest request) {
    
    
        try {
    
    
            Collection<Part> parts = request.getParts();
            this.multipartParameterNames = new LinkedHashSet(parts.size());
            MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap(parts.size());
            Iterator var4 = parts.iterator();

            while(var4.hasNext()) {
    
    
                Part part = (Part)var4.next();
                String headerValue = part.getHeader("Content-Disposition");
                ContentDisposition disposition = ContentDisposition.parse(headerValue);
                String filename = disposition.getFilename();
                if (filename != null) {
    
    
                    if (filename.startsWith("=?") && filename.endsWith("?=")) {
    
    
                        filename = StandardMultipartHttpServletRequest.MimeDelegate.decode(filename);
                    }

                    files.add(part.getName(), new StandardMultipartHttpServletRequest.StandardMultipartFile(part, filename));
                } else {
    
    
                    this.multipartParameterNames.add(part.getName());
                }
            }

            this.setMultipartFiles(files);
        } catch (Throwable var9) {
    
    
            this.handleParseFailure(var9);
        }

    }

整体处理在上面这一块代码,而处理文件主要在第一句即Collection parts = request.getParts();这个getParts方法在tomcat运行时org.apache.catalina.connector下面,获取临时文件路径主要是下面这一段,如果你没有指定location,就会默认在临时文件夹下面,指定了就会在你指定的路径下面

                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 = (File)context.getServletContext().getAttribute("javax.servlet.context.tempdir");
                }

                if (!location.exists() && context.getCreateUploadTargets()) {
    
    
                    log.warn(sm.getString("coyoteRequest.uploadCreate", new Object[]{
    
    location.getAbsolutePath(), this.getMappingData().wrapper.getName()}));
                    if (!location.mkdirs()) {
    
    
                        log.warn(sm.getString("coyoteRequest.uploadCreateFail", new Object[]{
    
    location.getAbsolutePath()}));
                    }
                }

猜你喜欢

转载自blog.csdn.net/baidu_29609961/article/details/131249877