關於使用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
這樣再部署到伺服器上,觀察一段時間發現錯誤已經不會再發生了,但是最近又出現該類錯誤,而且我並沒有改動任何檔案,有點無語了。
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();
}