簡介
Commons FileUpload能夠輕鬆地爲web應用程序添增強大,高性能的文件上傳功能。Servlet3.0以前的web應用程序須要使用Commons FileUpload組件上傳文件,可是從Servlet3.0開始,文件上傳就成了一個內置的功能。文件上傳時,須要使用POST方法提交HTTP請求,而且內容類型(Content-Type)爲 multipart/form-data。
enctype
表單的enctype屬性表示在發送到服務器以前應該如何對錶單數據進行編碼,默認值是 application/x-www-form-urlencoded。另外一個重要的值就是 multipart/form-data。
application/x-www-form-urlencoded
發送到服務器的HTTP消息的正文本質上是一個大的查詢字符串,字符串中的名稱/值對被 & 分隔,而且名稱與值由 = 分隔。若是值包含非字母數字的字符,那麼該字符將被「%HH」取代,即一個百分比符號和兩個十六進制數字表示的字符串。HH與字符集有關,若是 Content-Type = application/x-www-form-urlencoded;charset=utf-8,那麼該字符將首先被編碼爲UTF-8字節數組,再將每一個字節轉換爲HH。特別要注意,空格會轉換爲 +。
其實用 application/x-www-form-urlencoded 來上傳文件也何嘗不可,只是文件內容必須被序列化爲字符串,服務端再將該字符串反序列化。
例如:
key1=%E7%AD%96&key2=abcd1234。
multipart/form-data
不對字符編碼,在使用包含文件上傳控件的表單時,必須使用該值。
例如:
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding:gzip, deflate
Accept-Language:zh-CN,zh;q=0.8
Cache-Control:max-age=0
Connection:keep-alive
Content-Length:755
Content-Type:multipart/form-data; boundary=-----------------------------65982022822840
Cookie:JSESSIONID=wg5h37bt3rcr1dcpes14s42jz
Host:localhost:9443
Origin:https://localhost:9443
Referer:https://localhost:9443/ice-web1/test/test1
Upgrade-Insecure-Requests:1
User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36
-----------------------------65982022822840
Content-Disposition: form-data; name="userName"
匿名
-----------------------------65982022822840
Content-Disposition: form-data; name="userAge"
20
-----------------------------65982022822840
Content-Disposition: form-data; name="uploadFile"; filename="測試文件"
Content-Type: application/octet-stream
文件內容xxxxx
-----------------------------65982022822840--
發送到服務器的HTTP消息的正文沒有被編碼,而是Content-Type請求頭中的boundary表示的邊界符分隔成幾個部分。Content-Disposition 是 MIME 協議的擴展,能夠用於文件上傳和下載。上傳時能夠表示參數的名稱,如上。下載時表示默認的文件名稱,例如 Content-Disposition: attachment; filename=FileName.txt。
文件上傳頁面
<!DOCTYPE html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<html>
<style>
.progress {
width: 260px;
height: 20px;
border: 1px solid white;
border-radius: 20px;
overflow: hidden;
}
.step {
height: 100%;
width: 0;
background: dodgerblue;
}
</style>
<script>
function upload() {
var file1 = document.getElementById("file1").files[0];
var file2 = document.getElementById("file2").files[0];
var remark = document.getElementById("remark");
if (file1 === undefined || file1 === null) {
alert("請選擇文件1");
return;
}
if (file2 === undefined || file2 === null) {
alert("請選擇文件2");
return;
}
if (remark.value === null || remark.value === "") {
alert("請輸入備註");
return;
}
var data = new FormData(document.getElementById('form'));
var xhr = new XMLHttpRequest();
xhr.upload.onprogress = function (event) {
if (event.lengthComputable) {
var percent = event.loaded / event.total * 100 + '%';
document.querySelector('.step').style.width = percent;
document.getElementById("progressPercent").innerHTML = percent;
}
}
xhr.onload = function (event) {
alert(xhr.responseText);
};
xhr.open("post", "upload");
xhr.send(data);
}
</script>
<body>
<title>index</title>
<div>
<form id="form" enctype="multipart/form-data">
<fieldset>
<legend>上傳文件</legend>
文件1:<input type="file" id="file1" name="file"/> <br/>
文件2:<input type="file" id="file2" name="file"/> <br/>
備註:<input type="text" id="remark" name="remark"><input type="button" onclick="upload()" value="上傳">
</fieldset>
</form>
</div>
<div class='progress'>
<div class="step"></div>
</div>
<div id="progressPercent">0%</div>
</body>
</html>
Apache Commons FileUpload 1.3.3
Apache Commons FileUpload提供了兩組API,傳統API和流式API。傳統API假定文件項必須在用戶實際可訪問以前存儲在某個位置,這種方法很方便,可是它佔用內存且耗時比較長。
傳統API文件在被用戶讀取前,必須等待被保存在內存或者硬盤中(臨時文件),這種方法很是簡單,可是另外一方面卻帶來了內存和時間上的額外開銷。流式API更佳輕量級,它可讓你犧牲一點便利性以換得理想的性能,文件能夠直接從網絡輸入流中獲取。
傳統API
web.xml
<web-app>
<display-name>Archetype Created Web Application</display-name>
<listener>
<!--建立臨時文件清理跟蹤器,用於跟蹤臨時文件,而且在垃圾回收器回收DiskFileItem時刪除-->
<listener-class>
org.apache.commons.fileupload.servlet.FileCleanerCleanup
</listener-class>
</listener>
</web-app>
CommonsFileUploadServlet.java
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.ProgressListener;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.FileCleanerCleanup;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FileCleaningTracker;
import org.apache.commons.io.FileUtils;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.List;
@WebServlet(name = "fileUploadServlet", urlPatterns = {"/upload"})
public class CommonsFileUploadServlet extends GenericServlet {
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest httpServletRequest = (HttpServletRequest) req;
HttpServletResponse httpServletResponse = (HttpServletResponse) res;
httpServletResponse.setContentType("text/html; charset=UTF-8");
try {
//進度監聽器
ProgressListener progressListener = new ProgressListener() {
private long megaBytes = -1;
/**
* @param pBytesRead 已經讀取的字節數
* @param pContentLength 總字節數
* @param pItems 字段編號
*/
@Override
public void update(long pBytesRead, long pContentLength, int pItems) {
//減小通知次數,每接收1M通知一次
long mBytes = pBytesRead / (1024 * 1024);
if (megaBytes == mBytes) {
return;
}
megaBytes = mBytes;
System.out.println("正在讀取item:" + pItems);
if (pContentLength == -1) {
System.out.println("到目前爲止," + pBytesRead + "字節已讀");
} else {
System.out.println("到目前爲止," + pBytesRead + "/" + pContentLength
+ "字節已讀");
}
}
};
//獲取臨時文件清理跟蹤器
FileCleaningTracker fileCleaningTracker = FileCleanerCleanup.getFileCleaningTracker(httpServletRequest.getServletContext());
//爲基於磁盤的文件項建立工廠
DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();
//設置尺寸閾值,單位字節,超過設定值則文件臨時寫入磁盤,不然保存在內存
diskFileItemFactory.setSizeThreshold(1024 * 1024);
//設置文件寫入磁盤時臨時保存的目錄
diskFileItemFactory.setRepository(new File("E:/文件上傳測試"));
//設置臨時文件清理跟蹤器,若是設置爲null,將再也不對臨時文件進行跟蹤
diskFileItemFactory.setFileCleaningTracker(fileCleaningTracker);
ServletFileUpload servletFileUpload = new ServletFileUpload(diskFileItemFactory);
//設置容許上傳的單個文件最大尺寸,單位字節,-1表明不限制,參數類型是long
servletFileUpload.setFileSizeMax(1024 * 1024 * 10L);
//設置整個請求的大小的最大值,單位字節,-1表明不限制,參數類型是long
servletFileUpload.setSizeMax(1024 * 1024 * 100L);
//設置進度監聽器
servletFileUpload.setProgressListener(progressListener);
//設置讀取每一個部分的請求頭的字符集
servletFileUpload.setHeaderEncoding("UTF-8");
boolean isMultipartContent = ServletFileUpload.isMultipartContent(httpServletRequest);
if (isMultipartContent) {
//開始上傳文件並解析請求,按順序獲取各個表單項
List<FileItem> fileItems = servletFileUpload.parseRequest(httpServletRequest);
if (fileItems != null) {
for (FileItem fileItem : fileItems) {
//若是當前表單項是字段
if (fileItem.isFormField()) {
//獲取字段名
String fieldName = fileItem.getFieldName();
//獲取字段值
String value = fileItem.getString("UTF-8");
System.out.println("請求字段:" + fieldName + "=" + value);
}
//若是當前表單項是文件
else {
DiskFileItem diskFileItem = (DiskFileItem) fileItem;
//獲取字段名
String fieldName = diskFileItem.getFieldName();
//獲取瀏覽器提供的原始文件名
String fileName = diskFileItem.getName();
//獲取文件MIME類型
String contentType = diskFileItem.getContentType();
//獲取文件大小,單位字節
long sizeInBytes = diskFileItem.getSize();
//判斷文件是否存儲在內存中
boolean isInMemory = diskFileItem.isInMemory();
File storeLocation = diskFileItem.getStoreLocation();
System.out.println("請求字段:" + fieldName);
System.out.println("原始文件名:" + fileName);
System.out.println("MIME類型:" + contentType);
System.out.println("文件大小(字節):" + sizeInBytes);
System.out.println("文件臨時保存在內存中?:" + isInMemory);
System.out.println("文件臨時保存的路徑:" + storeLocation.getAbsolutePath());
//等同於diskFileItem.write(new File("E:/文件上傳測試/" + fileName))
try (InputStream inputStream = diskFileItem.getInputStream()) {
FileUtils.copyInputStreamToFile(inputStream, new File("E:/文件上傳測試/" + fileName));
}
//手動刪除臨時文件
diskFileItem.delete();
}
}
}
try (PrintWriter printWriter = httpServletResponse.getWriter()) {
printWriter.println("上傳成功");
}
}
} catch (Exception e) {
try (PrintWriter printWriter = httpServletResponse.getWriter()) {
printWriter.println(e.getMessage());
}
e.printStackTrace();
}
}
}
須要注意幾點:
1.臨時文件能夠手動刪除,也建立臨時文件清理跟蹤器自動刪除,或者同時使用這二者方式。
2.進度監聽器可能會下降性能,好比update方法中發送網絡數據包等,因此最好仍是下降通知頻率。
3.若是上傳大文件,須要配置tomcat的Connector的maxSwallowSize屬性爲負數,不然客戶端不會接收到響應。
流式API
import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.ProgressListener;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.util.Streams;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
@WebServlet(name = "fileUploadServlet", urlPatterns = {"/upload"})
public class CommonsFileUploadStreamServlet extends GenericServlet {
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest httpServletRequest = (HttpServletRequest) req;
HttpServletResponse httpServletResponse = (HttpServletResponse) res;
httpServletResponse.setContentType("text/html; charset=UTF-8");
try {
//進度監聽器
ProgressListener progressListener = new ProgressListener() {
private long megaBytes = -1;
/**
* @param pBytesRead 已經讀取的字節數
* @param pContentLength 總字節數
* @param pItems 字段編號
*/
@Override
public void update(long pBytesRead, long pContentLength, int pItems) {
//減小通知次數,每接收1M通知一次
long mBytes = pBytesRead / (1024 * 1024);
if (megaBytes == mBytes) {
return;
}
megaBytes = mBytes;
System.out.println("正在讀取item:" + pItems);
if (pContentLength == -1) {
System.out.println("到目前爲止," + pBytesRead + "字節已讀");
} else {
System.out.println("到目前爲止," + pBytesRead + "/" + pContentLength
+ "字節已讀");
}
}
};
//爲基於磁盤的文件項建立工廠
DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();
ServletFileUpload servletFileUpload = new ServletFileUpload(diskFileItemFactory);
//設置容許上傳的單個文件最大尺寸,單位字節,-1表明不限制,參數類型是long
servletFileUpload.setFileSizeMax(1024 * 1024 * 10L);
//設置整個請求的大小的最大值,單位字節,-1表明不限制,參數類型是long
servletFileUpload.setSizeMax(1024 * 1024 * 100L);
//設置進度監聽器
servletFileUpload.setProgressListener(progressListener);
//設置讀取每一個部分的請求頭的字符集
servletFileUpload.setHeaderEncoding("UTF-8");
boolean isMultipartContent = ServletFileUpload.isMultipartContent(httpServletRequest);
if (isMultipartContent) {
//開始上傳文件並解析請求,按順序獲取各個表單項
FileItemIterator fileItemIterator = servletFileUpload.getItemIterator(httpServletRequest);
while (fileItemIterator.hasNext()) {
FileItemStream fileItemStream = fileItemIterator.next();
String name = fileItemStream.getFieldName();
if (fileItemStream.isFormField()) {
try (InputStream inputStream = fileItemStream.openStream()) {
System.out.println("表單字段:" + name + ",值:"
+ Streams.asString(inputStream, "utf-8"));
}
} else {
System.out.println("請求字段:" + fileItemStream.getFieldName());
System.out.println("原始文件名:" + fileItemStream.getName());
System.out.println("MIME類型:" + fileItemStream.getContentType());
try (
//文件輸出流
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(
new FileOutputStream("E:/文件上傳測試/" + fileItemStream.getName()));
//文件輸入流
InputStream inputStream = new BufferedInputStream(fileItemStream.openStream());) {
byte[] bytes = new byte[1024 * 8];
int n;
while (-1 != (n = inputStream.read(bytes))) {
bufferedOutputStream.write(bytes, 0, n);
}
}
}
}
try (PrintWriter printWriter = httpServletResponse.getWriter()) {
printWriter.println("上傳成功");
}
}
} catch (Exception e) {
try (PrintWriter printWriter = httpServletResponse.getWriter()) {
printWriter.println(e.getMessage());
}
e.printStackTrace();
}
}
}
Servlet
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;
/**
* 必須使用@MultipartConfig註解標註Servlet
* maxFileSize表示容許上傳的文件大小的最大值,單位字節,-1表示無限制
* maxRequestSize表示整個請求大小的最大值,單位字節,-1表示無限制
* fileSizeThreshold表示尺寸閾值,單位字節,超過設定值則文件臨時寫入磁盤,不然保存在內存
* location表示臨時文件的目錄
*/
@WebServlet(name = "servletFileUploadServlet", urlPatterns = {"/upload"})
@MultipartConfig(maxFileSize = 1024 * 1024 * 10, maxRequestSize = 1024 * 1024 * 100, fileSizeThreshold = 1024 * 1024, location = "E:/文件上傳測試/")
public class ServletFileUploadServlet extends GenericServlet {
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest httpServletRequest = (HttpServletRequest) req;
httpServletRequest.setCharacterEncoding("UTF-8");
HttpServletResponse httpServletResponse = (HttpServletResponse) res;
httpServletResponse.setContentType("text/html; charset=UTF-8");
Collection<Part> parts = httpServletRequest.getParts();
try {
parts.forEach(part -> {
//若是part.getContentType() == null,就表明這是個表單字段,不然就是文件
if (part.getContentType() == null) {
//獲取字段名
String fieldName = part.getName();
//獲取字段值
String value = httpServletRequest.getParameter(fieldName);
System.out.println("請求字段:" + fieldName + "=" + value);
} else {
System.out.println("請求字段:" + part.getName());
System.out.println("原始文件名:" + part.getSubmittedFileName());
System.out.println("MIME類型:" + part.getContentType());
System.out.println("文件大小(字節):" + part.getSize());
try {
part.write("E:/文件上傳測試/" + part.getSubmittedFileName());
//手動刪除臨時文件
part.delete();
} catch (IOException e) {
e.printStackTrace();
}
}
});
try (PrintWriter printWriter = httpServletResponse.getWriter()) {
printWriter.println("上傳成功");
}
} catch (Exception e) {
try (PrintWriter printWriter = httpServletResponse.getWriter()) {
printWriter.println(e.getMessage());
}
e.printStackTrace();
}
}
}