日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

修改tomcat的临时文件夹_tomcat 临时文件夹被移除的问题

發布時間:2024/10/12 编程问答 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 修改tomcat的临时文件夹_tomcat 临时文件夹被移除的问题 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

SpringBoot搭建的應用,一直工作得好好的,突然發現上傳文件失敗,提示org.springframework.web.multipart.MultipartException: Failed to parse multipart servlet request; nested exception is java.io.IOException: The temporary upload location [/tmp/tomcat.6239989728636105816.19530/work/Tomcat/localhost/ROOT] is not valid目錄非法,實際查看目錄,結果還真沒有,下面就這個問題的表現,分析下SpringBoot針對文件上傳的處理過程

I. 問題分析

0. 堆棧分析

問題定位,最佳的輔助手段就是堆棧分析,首先撈出核心的堆棧信息

org.springframework.web.multipart.MultipartException: Failed to parse multipart servlet request; nested exception is java.io.IOException: The temporary upload location [/tmp/tomcat.6239989728636105816.19530/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.(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)

從堆棧內容來看,問題比較清晰,目錄非法,根據path路徑,進入目錄,結果發現,沒有這個目錄,那么問題的關鍵就是沒有目錄為什么會導致異常了,這個目錄到底有啥用

先簡單描述下上面的原因,上傳的文件會緩存到本地磁盤,而緩存的路徑就是上面的/tmp/tomcat.6239989728636105816.19530/work/Tomcat/localhost/ROOT,接著引入的疑問就是:

為什么上傳的文件要緩存到本地

為什么臨時目錄會不存在

什么地方實現文件緩存

1. 場景模擬

要確認上面的問題,最直觀的方法就是擼源碼,直接看代碼就有點蛋疼了,接下來采用debug方式來層層剝離,看下根源再哪里。

首先是搭建一個簡單的測試項目,進行場景復現, 首先創建一個接收文件上傳的Controller,如下

@RestController

@RequestMapping(path = "/file")

public class FileUploadRest {

/**

* 保存上傳的文件

*

* @param file

* @return

*/

private String saveFileToLocal(MultipartFile file) {

try {

String name = "/tmp/out_" + System.currentTimeMillis() + file.getName();

FileOutputStream writer = new FileOutputStream(new File(name));

writer.write(file.getBytes());

writer.flush();

writer.close();

return name;

} catch (Exception e) {

e.printStackTrace();

return e.getMessage();

}

}

@PostMapping(path = "upload")

public String upload(@RequestParam("file") MultipartFile file) {

String ans = saveFileToLocal(file);

return ans;

}

}

其次就是使用curl來上傳文件

curl http://127.0.0.1:8080/file/upload -F "file=@/Users/user/Desktop/demo.jpg" -v

然后在接收文件上傳的方法中開啟斷點,注意下面紅框中的?location, 就是文件上傳的臨時目錄

2. 源碼定位

上面的截圖可以確認確實將上傳的文件保存到了臨時目錄,驗證方式就是進入那個目錄進行查看,會看到一個tmp文件,接下來我們需要確定的是在什么地方,實現將數據緩存到本地的。

注意下圖,左邊紅框是這次請求的完整鏈路,我們可以通過逆推鏈路,去定位可能實現文件緩存的地方

如果對spring和tomcat的源碼不熟的話,也沒什么特別的好辦法,從上面的鏈路中,多打一些斷點,采用傳說中的二分定位方法來縮小范圍。

通過最開始的request對象和后面的request對象分析,發現一個可以作為參考標準的就是上圖中右邊紅框的request#parts屬性;開始是null,文件保存之后則會有數據,下面給一個最終定位的動圖

所以關鍵就是org.springframework.web.filter.HiddenHttpMethodFilter#doFilterInternal?中的?String paramValue = request.getParameter(this.methodParam);?這一行代碼

到這里在單步進去,主要的焦點將集中在?org.apache.catalina.connector.Request#parseParts

進入上面方法的邏輯,很容易找到具體的實現位置?org.apache.tomcat.util.http.fileupload.FileUploadBase#parseRequest,這個方法的實現比較有意思,有必要貼出來看一下

public List parseRequest(RequestContext ctx)

throws FileUploadException {

List items = new ArrayList<>();

boolean successful = false;

try {

FileItemIterator iter = getItemIterator(ctx);

// 注意這里,文件工廠類,里面保存了臨時目錄的地址

// 這個對象首次是在 org.apache.catalina.connector.Request#parseParts 方法的

FileItemFactory fac = getFileItemFactory();

if (fac == null) {

throw new NullPointerException("No FileItemFactory has been set.");

}

while (iter.hasNext()) {

final FileItemStream item = iter.next();

// Don't use getName() here to prevent an InvalidFileNameException.

final String fileName = ((FileItemIteratorImpl.FileItemStreamImpl) item).name;

// 創建一個臨時文件對象

FileItem fileItem = fac.createItem(item.getFieldName(), item.getContentType(),

item.isFormField(), fileName);

items.add(fileItem);

try {

// 流的拷貝,這塊代碼也挺有意思,將輸入流數據寫入輸出流

// 后面會貼出源碼,看下開源大佬們的玩法,和我們自己寫的有啥區別

Streams.copy(item.openStream(), fileItem.getOutputStream(), true);

} catch (FileUploadIOException e) {

throw (FileUploadException) e.getCause();

} catch (IOException e) {

throw new IOFileUploadException(String.format("Processing of %s request failed. %s",

MULTIPART_FORM_DATA, e.getMessage()), e);

}

final FileItemHeaders fih = item.getHeaders();

fileItem.setHeaders(fih);

}

successful = true;

return items;

} catch (FileUploadIOException e) {

throw (FileUploadException) e.getCause();

} catch (IOException e) {

throw new FileUploadException(e.getMessage(), e);

} finally {

if (!successful) {

for (FileItem fileItem : items) {

try {

fileItem.delete();

} catch (Exception ignored) {

// ignored TODO perhaps add to tracker delete failure list somehow?

}

}

}

}

}

核心代碼就兩點,一個是文件工廠類,一個是流的拷貝;前者定義了我們的臨時文件目錄,也是我們解決前面問題的關鍵,換一個我自定義的目錄永不刪除,不就可以避免上面的問題了么;后面一個則是數據復用方面的

首先看下FileItemFactory的實例化位置,在org.apache.catalina.connector.Request#parseParts中,代碼如下

具體的location實例化代碼為

// TEMPDIR = "javax.servlet.context.tempdir";

location = ((File) context.getServletContext().getAttribute(ServletContext.TEMPDIR));

3. 問題review

a. 解決問題

到上面,基本上就撈到了最終的問題,先看如何解決這個問題

方法1

應用重啟

方法2

增加服務配置,自定義baseDir

server.tomcat.basedir=/tmp/tomcat

方法3

注入bean,手動配置臨時目錄

@Bean

MultipartConfigElement multipartConfigElement() {

MultipartConfigFactory factory = new MultipartConfigFactory();

factory.setLocation("/tmp/tomcat");

return factory.createMultipartConfig();

}

方法4

配置不刪除tmp目錄下的tomcat

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

# 添加一行

x /tmp/tomcat.*

b. 流拷貝

tomcat中實現流的拷貝代碼如下,org.apache.tomcat.util.http.fileupload.util.Streams#copy(java.io.InputStream, java.io.OutputStream, boolean, byte[])?, 看下面的實現,直觀影響就是寫得真特么嚴謹

public static long copy(InputStream inputStream,

OutputStream outputStream, boolean closeOutputStream,

byte[] buffer)

throws IOException {

OutputStream out = outputStream;

InputStream in = inputStream;

try {

long total = 0;

for (;;) {

int res = in.read(buffer);

if (res == -1) {

break;

}

if (res > 0) {

total += res;

if (out != null) {

out.write(buffer, 0, res);

}

}

}

if (out != null) {

if (closeOutputStream) {

out.close();

} else {

out.flush();

}

out = null;

}

in.close();

in = null;

return total;

} finally {

IOUtils.closeQuietly(in);

if (closeOutputStream) {

IOUtils.closeQuietly(out);

}

}

}

c. 自問自答

前面提出了幾個問題,現在給一個簡單的回答,因為篇幅問題,后面會單開一文,進行詳細說明

什么地方緩存文件

上面的定位過程給出答案,具體實現邏輯在?org.apache.tomcat.util.http.fileupload.FileUploadBase#parseRequest

為什么目錄會不存在

springboot啟動時會創建一個/tmp/tomcat.*/work/Tomcat/localhost/ROOT的臨時目錄作為文件上傳的臨時目錄,但是該目錄會在n天之后被系統自動清理掉,這個清理是由linux操作系統完成的,具體的配置如下?vim /usr/lib/tmpfiles.d/tmp.conf

# This file is part of systemd.

#

# systemd is free software; you can redistribute it and/or modify it

# under the terms of the GNU Lesser General Public License as published by

# the Free Software Foundation; either version 2.1 of the License, or

# (at your option) any later version.

# See tmpfiles.d(5) for details

# Clear tmp directories separately, to make them easier to override

v /tmp 1777 root root 10d

v /var/tmp 1777 root root 30d

# Exclude namespace mountpoints created with PrivateTmp=yes

x /tmp/systemd-private-%b-*

X /tmp/systemd-private-%b-*/tmp

x /var/tmp/systemd-private-%b-*

X /var/tmp/systemd-private-%b-*/tmp

為什么要緩存文件

因為流取一次消費之后,后面無法再從流中獲取數據,所以緩存方便后續復用;這一塊后面詳細說明

4. 小結

定位這個問題的感覺,就是對SpringBoot和tomcat的底層,實在是不太熟悉,作為一個以Spring和tomcat吃飯的碼農而言,發現問題就需要改正,列入todo列表,后續需要深入一下

總結

以上是生活随笔為你收集整理的修改tomcat的临时文件夹_tomcat 临时文件夹被移除的问题的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。