关于使用webuploader上传文件出现的The temporary upload location is not valid

用webUploader插件上传文件的时候,经常出现如下问题:

用google浏览器进行调试,console页问题如下:

network页问题如下:

下面我们重点看一下spring给我们提供的这几个类里面是如何对我这次请求进行处理的:

1、FrameWorkServlet:是一个抽象类

public abstract class FrameworkServlet extends HttpServletBean
    implements ApplicationContextAware

service方法是这样定义的:

protected void service(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
        if(HttpMethod.PATCH == httpMethod || httpMethod == null)
            processRequest(request, response);
        else
            super.service(request, response);
    }

以上HttpMethod是一个枚举类型,resolve方法返回当前请求的method的枚举类型,如果请求类型是PATCH类型(关于patch请求类型的说明请参考:https://segmentfault.com/q/1010000005685904/为啥这样区分,目前不太清楚)或为空的话,调用processRequest(request,response)方法,否则调用super.service(request,response)(这个方法在javax.servlet.http.HttpServlet类中)方法,我们本次请求是一个post请求,所以调用了后者。

2、HttpServlet:是一个抽象类

public abstract class HttpServlet extends GenericServlet

service方法是这样定义的:

protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException
    {
        String method = req.getMethod();
        if(method.equals("GET"))
        {
            long lastModified = getLastModified(req);
            if(lastModified == -1L)
            {
                doGet(req, resp);
            } else
            {
                long ifModifiedSince;
                try
                {
                    ifModifiedSince = req.getDateHeader("If-Modified-Since");
                }
                catch(IllegalArgumentException iae)
                {
                    ifModifiedSince = -1L;
                }
                if(ifModifiedSince < (lastModified / 1000L) * 1000L)
                {
                    maybeSetLastModified(resp, lastModified);
                    doGet(req, resp);
                } else
                {
                    resp.setStatus(304);
                }
            }
        } else
        if(method.equals("HEAD"))
        {
            long lastModified = getLastModified(req);
            maybeSetLastModified(resp, lastModified);
            doHead(req, resp);
        } else
        if(method.equals("POST"))
            doPost(req, resp);
        else
        if(method.equals("PUT"))
            doPut(req, resp);
        else
        if(method.equals("DELETE"))
            doDelete(req, resp);
        else
        if(method.equals("OPTIONS"))
            doOptions(req, resp);
        else
        if(method.equals("TRACE"))
        {
            doTrace(req, resp);
        } else
        {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object errArgs[] = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(501, errMsg);
        }
    }

javax.servlet.http.HttpServlet是底层Servlet对请求的处理类,以上我们看到只有get、post、put、delete、options、trace、head7种请求方式,所以我们可以猜想,FrameWorkServlet的service方法对请求进行了分别处理,在不改变javax.servlet.http.HttpServlet类的情况下,增加了对PATCH的处理,这只是我们的猜想,我们接着往下看。

又回调了FrameworkServlet中的doPost方法:

protected final void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        processRequest(request, response);
    }

我们可以看到doPost方法里面调用了processRequest(request,response)方法,其实我们不妨看一下FrameworkServlet类中对其他请求类型做的处理:

都调用了同一个方法,那我们以上的猜想看来是正确的。

接下来我们再看processRequest()方法里面的处理逻辑:

doService(request,response)方法是一个抽象方法,应该是要交给FrameworkServlet的子类去实现的

此处用了模板方法模式,抛出的异常信息只到这里就结束了,所以我们必须找到处理该请求的FrameworkServlet的子类,也就是DispatcherServlet类中的doService方法,

我们可以看到到这里,会解析该请求是否属于multipartRequest请求,默认不是multipartRequest请求,我们接下来看一下checkMultipart(request)方法:

MultipartResolver是一个接口,他有两个实现类,StandardMultipartHttpServletRequest和CommonsMultipartResolver,

StandardServletMultipartResolver:

翻译一下大概意思就是:

该类是MultipartResolver接口的标准实现类,基于Servlet 3.0 {@link javax.servlet.http.Part} API,作为一个spring DispatcherServlet 上下文的的MultipartResolver实现类,在实例级别没有任何额外的配置。

使用该类需要我们做如下其中的一种配置:1、在web.xml中用multipart-config模块标记对应的servlet;2、在程序中注册该sevlet的时候用javax.servlet.MultipartConfigElement配置一下;3、如果你是自定义的servlet,你需要在你的servlet上加一个javax.servlet.annotation.MultipartConfig注解。像文件最大值或存储路径这样的相关的配置在servlet注册的时候就要被应用,servlet3.0不允许他们在MultipartResolver级别被设置。

CommonsMultipartResolver:

 翻译一下:

MultipartResolver的实现类,基于Apache Commons FileUpload1.2或以上版本(官方网址:http://commons.apache.org/proper/commons-fileupload),提供maxUploadSize, maxInMemorySize, defaultEncoding等属性的配置(继承自CommonsFileUploadSupport),见ServletFileUpload / DiskFileItemFactory(“sizeMax”,“sizeThreshold”,*“headerEncoding”)有关默认值和可接受值的详细信息。保存临时文件到servlet容器的临时路径,需要通过应用程序上下文<i>或带有ServletContext的构造函数(用于独立使用)去初始化。

如果以上翻译看不懂没关系,翻译的不好,磕磕绊绊的,我们先接着往下看。

我们现在用的是StandardServletMultipartResolver(这也是springBoot默认实例化的MultipartResolver对象)这个实现类里面的resolveMultipart方法,

我们可以通过spring.http.multipart.enabled=false将默认方式关闭。

@Override
	public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
		return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
	}

StandardMultipartHttpServletRequest:

public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing) throws MultipartException {
		super(request);
		if (!lazyParsing) {
			parseRequest(request);
		}
	}

parseRequest(request);

private void parseRequest(HttpServletRequest request) {
		try {
			Collection<Part> parts = request.getParts();
			this.multipartParameterNames = new LinkedHashSet<String>(parts.size());
			MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<String, MultipartFile>(parts.size());
			for (Part part : parts) {
				String disposition = part.getHeader(CONTENT_DISPOSITION);
				String filename = extractFilename(disposition);
				if (filename == null) {
					filename = extractFilenameWithCharset(disposition);
				}
				if (filename != null) {
					files.add(part.getName(), new StandardMultipartFile(part, filename));
				}
				else {
					this.multipartParameterNames.add(part.getName());
				}
			}
			setMultipartFiles(files);
		}
		catch (Exception ex) {
			throw new MultipartException("Could not parse multipart servlet request", ex);
		}
	}

我们的异常就是在这个方法里面抛出来的,现在我们仔细来分析一下这个方法:这个方法主要是用来解析本次请求中上传了几个文件,将所有上传的文件用filename,file的形式存储起来,并设置为不可修改集合。

我们先来看一下request.getParts()这个方法:

跳转到org.apache.catalina.connector.Request这个类中的getParts() ---->  parseParts(true),这个方法才开始真正对本次请求进行解析,其中

先从包装器wrapper中获取我们启动时注册好的MultipartConfigElement对象,获取里面的location,也就是我们自定义的文件上传路径,如果我们没有设置的话就将ServletContext上下文中的默认路径javax.servlet.context.tempdir的值赋给我们的location,也就是我们在response中看到的那个invalid的路径了,服务在linux上会将该临时目录创建在tmp路径下,但是linux会定期清除tmp目录下的文件,所以服务运行一段时间上传文件时就会出现上述错误,因此我们可以在application.yml中加上location配置:

spring:
  http:
    multipart:
      enabled: true
      max-file-size: 200MB
      max-request-size: 200MB
      file-size-threshold: 200MB
      location: /home/developer/project/triber/tosift/upload/tmp

我们运行服务后就会在\\E:\\home\\developer\\project\\triber\\tosift\\upload\\tmp这个目录下自动生成work文件夹,如果把这个文件夹删除,就会上述错误。

注意,如果在这期间会涉及到多个springBoot项目,每个项目的配置文件都要这样配置才不会出错)这样再部署到服务器上,观察一段时间发现错误已经不会再发生了,但是最近又出现该类错误,而且我并没有改动任何文件,有点无语了。

springBoot的版本更新了,配置方式也变了。把我们配置的http变成servlet,还要加上如下配置才行:

@Configuration
public class MultipartConfig {
	
	/**
	 * 文件上传临时路径
	 * */
	@Bean
	MultipartConfigElement multipartConfigElement(
			@Value("${spring.servlet.multipart.max-file-size}") String maxFileSize,
			@Value("${spring.servlet.multipart.max-request-size}") String maxRequestSize, 
			@Value("${spring.servlet.multipart.file-size-threshold}") String fileSizeThreshold,
			@Value("${spring.servlet.multipart.location}") String location){
		MultipartConfigFactory multipartConfigFactory = new MultipartConfigFactory();
		//单个文件大小
		multipartConfigFactory.setMaxFileSize(maxFileSize);
		//单次请求文件大小
		multipartConfigFactory.setMaxRequestSize(maxRequestSize);
		//
		multipartConfigFactory.setFileSizeThreshold(fileSizeThreshold);
		//上传路径
//		multipartConfigFactory.setLocation(location);
		return multipartConfigFactory.createMultipartConfig();
	}

猜你喜欢

转载自blog.csdn.net/qq_35689573/article/details/82864155