用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();
}