Commons FileUpdate包很容易爲你的Servlet和web應用程序添加健壯的、高性能的文件上傳功能。
FileUpload解析遵循RFC 1876(在HTML中基於表單的文件上傳)HTTP請求。即,若是一個HTTP請求使用POST方法提交,並
且使用「multipart/form-data」的內容類型,而後FileUpload解析請求,使結果易於調用者使用。
從1.3開始,FileUpload處理RFC 2047編碼頭值。java
FileUpload能使用大量不一樣的方式,依賴於你的應用程序的需求。在簡單的狀況下,你將調用簡單的方法解析Servlet請求,
而後處理item列表做爲它們應用到你的應用程序。在天平的另外一端,你可能決定自定義FileUpload充分的控制單個item存儲
的方式;例如,你可能決定將流的內容寫入數據庫。
這裏,咱們將描述FileUpload的基本原則,並闡述一些更簡單的——而且更通用的——使用模式。
FileUpload依賴於Commons IO。web
一個文件上傳請求包含一個根據RFC 1867(在HTML中基於表單的文件上傳)編碼的有序item列表。FileUpload能解析這麼一個請求,並提供給你的應用程序單獨的上傳item列表。每一個item實現FileItem接口,無論它底層實現。
本文描述Commons FileUpload類庫的傳統API。傳統API是便利方式。然而,對於最終性能,你可能喜歡快速的流API。
每一個文件item有你的應用程序可能感興趣的許多屬性。例如,每一個item有一個名字和內容類型,並提供一個InputStream訪問
它的數據。換句話說,你可能須要處理不一樣的item,依賴因而否item是常規表單——即,數據來自原始文本框或相似於HTML字段——或上傳文件。FileItem接口提供方法作出這一決定,並以最適當的方式訪問數據。
FileUpload使用FileItemFactory建立新文件item。這給FileUpload更大的靈活性。工廠最終控制每一個item如何建立。工廠實
現當前過渡FileUpload存儲item的數據在內存或磁盤,依賴於item的大小(例如,數據的字節)。然而,該行爲能自定義適合你的應用程序。數據庫
從1.1開始,FileUpload支持Servlet和Portlet環境的文件上傳請求。在兩種環境中的使用幾乎相同,所以,本文只講述
Servlet環境。
若是你構建一個Portlet應用程序,如下兩點不一樣你應該閱讀API文檔:apache
ServletFileUpload類->PortletFileUpload類
HttpServletRequest類->ActionRequest類數組
你處理上傳item以前,固然,你須要解析請求。確保,請求是一個簡單的真實文件上傳請求,但FileUpload使其變得簡單,
經過提供一個靜態方法作到這一點。安全
// 檢查,咱們有一個文件上傳請求
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
網絡
如今,咱們準備解析請求爲item。
app
如下是最簡單的使用情景:函數
上傳item應該保留在內容中,只要它至關小。性能
大型item應該寫入磁盤上的臨時文件。
很是大的上傳請求應該不被容許。
內置默認的保存在內存中的一個item的最大大小,一個上傳請求的最大大小,和可接受的臨時文件位置。
在這種狀況下,處理請求不該該更簡單:
// 建立基於磁盤文件item的工廠
DiskFileItemFactory factory = new DiskFileItemFactory();
// 配置一個倉庫(確保一個安全的臨時位置被使用)
ServletContext servletContext = this.getServletConfig().getServletContext();
File repository = (File) servletContext.getAttribute("javax.servlet.context.tempdir");
factory.setRepository(repository);
// 建立一個新的文件上傳處理器
ServletFileUpload upload = new ServletFileUpload(factory);
// 解析請求
List<FileItem> items = upload.parseRequest(request);
這是咱們的全部須要。
解析的結果是文件item List,每一個FileItem接口的實現。
若是你使用的情景是上面描述的最簡單的狀況,可是你須要一點更多的控制,你能易於定製上傳處理器或文件item工廠的行爲。
如下例子顯示各類配置選項:
// 建立基於磁盤文件item的工廠
DiskFileItemFactory factory = new DiskFileItemFactory();
// 設置工廠約束
factory.setSizeThreshold(yourMaxMemorySize);
factory.setRepository(yourTempDirectory);
// 建立一個新的文件上傳處理器
ServletFileUpload upload = new ServletFileUpload(factory);
// 設置所有請求大小約束
upload.setSizeMax(yourMaxRequestSize);
// 解析請求
List<FileItem> items = upload.parseRequest(request);
固然,每一個配置方法獨立於其它方法,但若是你想要同時配置工廠,你能使用構造函數,像這樣:
// 建立一個基於磁盤的文item工廠
DiskFileItemFactory factory = new DiskFileItemFactory(yourMaxMemorySize, yourTempDirectory);
你應該須要在請求解析上更高級的控制,例如,在其它地方存儲item——例如,在數據庫中。
一旦解析完成,你將有一個須要處理的文件item List。在大多數狀況下,你將想要處理不一樣於普通表單字段的文件上傳,
所以你能夠像這樣處理:
// 處理上傳item
Iterator<FileItem> iter = items.iterator();
while (iter.hasNext()) {
FileItem item = iter.next();
if (item.isFormField()) {
processFormField(item);
} else {
processUploadedFile(item);
}
}
對於表單字段,你將只對item名稱和它的String值感興趣。正如你料想的,訪問這些很是簡單。
// 處理常規表單字段
if (item.isFormField()) {
String name = item.getFieldName();
String value = item.getString();
...
}
對於文件上傳,有幾種不一樣的東西,你可能會想知道你處理以前的內容。下面是一些你可能感興趣的方法示例。
// 處理文件上傳
if (!item.isFormField()) {
String fieldName = item.getFieldName();
String fileName = item.getName();
String contentType = item.getContentType();
boolean isInMemory = item.isInMemory();
long sizeInBytes = item.getSize();
...
}
使用上傳文件,你一般不想經過內存要訪問它們,除非它們很小,或者你沒有其它選擇。你寧願將想要處理的內容做爲一個流,或寫入整個文件到它的最終位置。FileUpload提供簡單的方法完成這兩種。
// 處理文件上傳
if (writeToFile) {
File uploadedFile = new File(...);
item.write(uploadedFile);
} else {
InputStream uploadedStream = item.getInputStream();
...
uploadedStream.close();
}
注意,在默認的FileUpload實現中,若是數據已經在臨時文件中,write()將試圖重命名文件到指定目標。
若是你須要訪問內存中的上傳數據,你須要簡單調用get()方法獲取數據做爲byte數組。
// 處理內存中的文件上傳
byte[] data = item.get();
...
若是你使用DiskFileItem,你上傳的文件處理以前被寫入臨時文件。這些臨時文件被自動刪除,若是他們再也不使用,若是相應的java.io.File實例被垃圾回收。經過org.apache.commons.io.FileCleaner類默默開啓一個清理線程。這個清理線程應該被中止,若是它再也不須要。在Servlet環境中,經過使用一個特定Servlet上下文監聽器(FileCleanerCleanup)來完成。爲了這麼作,在你的web.xml中添加:
<web-app>
...
<listener>
<listener-class>
org.apache.commons.fileupload.servlet.FileCleanerCleanup
</listener-class>
</listener>
...
</web-app>
FileCleanerCleanup提供一個org.apache.commons.io.FileCleaningTracker實例。當建立org.apache.commons.fileupload.disk.DiskFileItemFactory時必須使用該實例。
public static DiskFileItemFactory newDiskFileItemFactory(ServletContext context, File repository) {
FileCleaningTracker fileCleaningTracker = FileCleanerCleanup.getFileCleaningTracker(context);
DiskFileItemFactory factory = new DiskFileItemFactory(DiskFileItemFactory.DEFAULT_SIZE_THRESHOLD, repository);
factory.setFileCleaningTracker(fileCleaningTracker);
return factory;
}
爲了禁用臨時文件跟蹤,你能夠設置FileCleaningTracker爲null。所以,建立文件將再也不被跟蹤。尤爲,它們將再也不被自動刪除。
病毒掃描和web容器運行在相同的系統中對於使用FileUpload的應用程序會致使一些意想不到的行爲。本文描述一些你可能遇到的行爲,而且提供一些如何處理它們的方式。
FileUpload的默認實現將致使上傳的item超過某一閥值被寫入磁盤。就這樣一個文件關閉,系統上的任何病毒掃描程序會來檢查它,而且可能隔離文件——即,把它移動到一個不能致使文件的地方。固然,這將給應用程序開發人員一個驚喜,由於上傳文件item再也不有效。話句話說,上傳item在相同閥值下將保存在內容中,所以將不會被病毒掃描器發覺。這容許一個病毒可能以某種形式被保留(儘管從不寫入磁盤,病毒掃描程序應該定位和檢查它)。
一個經常使用的解決辦法是將全部上傳的文件放置系統的一個目錄中,並配置病毒掃描器忽略該目錄。這將確保應用程序不會丟掉文件,然而脫離病毒掃描程序的職責範圍,對上傳的文件進行病毒掃描能夠由外部處理,移動乾淨或以清理了文件到「批准」位置,或在應用程序中集成病毒掃描程序。
若是你指望真實的大文件上傳,那麼它將很好的報告給你的用戶已經接收了多少。每一個HTML頁面容許實現一個進度條,經過返回一個multipart/replace響應或這樣的東西。
查看上傳進度能夠經過提供一個進度監聽器完成:
// 建立一個進度監聽器
ProgressListener progressListener = new ProgressListener(){
public void update(long pBytesRead, long pContentLength, int pItems) {
System.out.println("We are currently reading item " + pItems);
if (pContentLength == -1) {
System.out.println("So far, " + pBytesRead + " bytes have been read.");
} else {
System.out.println("So far, " + pBytesRead + " of " + pContentLength
+ " bytes have been read.");
}
}
};
upload.setProgressListener(progressListener);
幫本身一個忙,實現你的第一個進度監聽器,像上面這樣,由於它顯示了一個陷阱:進度監聽器被頻繁調用。依賴於Servlet引擎和其它環境工廠,它可能調用任何網絡包!換句話說,你的進程監聽器可能成爲性能問題!一個典型的解決方法是減小進度監聽器的活躍度。例如,你能夠只在兆字節數量改變時發出消息:
// 建立進度監聽器
ProgressListener progressListener = new ProgressListener(){
private long megaBytes = -1;
public void update(long pBytesRead, long pContentLength, int pItems) {
long mBytes = pBytesRead / 1000000;
if (megaBytes == mBytes) {
return;
}
megaBytes = mBytes;
System.out.println("We are currently reading item " + pItems);
if (pContentLength == -1) {
System.out.println("So far, " + pBytesRead + " bytes have been read.");
} else {
System.out.println("So far, " + pBytesRead + " of " + pContentLength
+ " bytes have been read.");
}
}
};
假設,文件item實際被用戶訪問以前必須存儲在某一地方。這種方式很方便,由於它容許易於訪問item內容。話句話說,
它消耗內存和時間。
流API容許你犧牲一點點便利,優化性能和較低的內存配置。然而,流API更輕量級,所以易於理解。
FileUpload類用於訪問表單字段和字段順序,它們已經經過客戶端發送。然而,FileItemFactory徹底被忽略。
首先,不要忘記確保,請求實際是一個文件上傳請求。這一般是經過使用相同的靜態方法。
// 檢查咱們有一個文件上傳請求
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
如今咱們已經準備好解析請求:
// 建立新文件上傳處理器ServletFileUpload upload = new ServletFileUpload();// 解析請求FileItemIterator iter = upload.getItemIterator(request);while (iter.hasNext()) { FileItemStream item = iter.next(); String name = item.getFieldName(); InputStream stream = item.openStream(); if (item.isFormField()) { System.out.println("Form field " + name + " with value " + Streams.asString(stream) + " detected."); } else { System.out.println("File field " + name + " with file name " + item.getName() + " detected."); // 處理輸入流 ... }}