Upload-上傳 隨著3.0版本的釋出,檔案上傳終於成為Servlet規範的一項內建特性,不再依賴於像Commons FileUpload之類元件,因此在服務端進行檔案上傳程式設計變得不費吹灰之力.
客戶端 要上傳檔案, 必須利用multipart/form-data設定HTML表單的enctype屬性,且method必須為POST:
服務端 服務端Servlet主要圍繞著@MultipartConfig註解和Part介面:處理上傳檔案的Servlet必須用@MultipartConfig註解標註:
@MultipartConfig屬性 描述 fileSizeThreshold The size threshold after which the file will be written to disk location The directory location where files will be stored maxFileSize The maximum size allowed for uploaded files. maxRequestSize The maximum size allowed for multipart/form-data requests 在一個由多部件組成的請求中, 每一個表單域(包括非檔案域), 都會被封裝成一個Part,HttpServletRequest中提供如下兩個方法獲取封裝好的Part:
HttpServletRequest 描述 Part getPart(String name) Gets the Part with the given name. Collection getParts() Gets all the Part components of this request, provided that it is of type multipart/form-data. Part中提供瞭如下常用方法來獲取/操作上傳的檔案/資料:
Part 描述 InputStream getInputStream() Gets the content of this part as an InputStream void write(String fileName) A convenience method to write this uploaded item to disk. String getSubmittedFileName() Gets the file name specified by the client(需要有Tomcat 8.x 及以上版本支援) long getSize() Returns the size of this fille. void delete() Deletes the underlying storage for a file item, including deleting any associated temporary disk file. String getName() Gets the name of this part String getContentType() Gets the content type of this part. Collection getHeaderNames() Gets the header names of this Part. String getHeader(String name) Returns the value of the specified mime header as a String. 檔案流解析 通過抓包獲取到客戶端上傳檔案的資料格式:
------WebKitFormBoundaryXJ6TxfJ9PX5hJHGh Content-Disposition: form-data; name="author"
feiqing ------WebKitFormBoundaryXJ6TxfJ9PX5hJHGh Content-Disposition: form-data; name="file"; filename="memcached.txt" Content-Type: text/plain
------WebKitFormBoundaryXJ6TxfJ9PX5hJHGh-- 可以看到: A. 如果HTML表單輸入項為文字(),將只包含一個請求頭Content-Disposition. B. 如果HTML表單輸入項為檔案(), 則包含兩個頭: Content-Disposition與Content-Type. 在Servlet中處理上傳檔案時, 需要:
- 通過檢視是否存在Content-Type標頭, 檢驗一個Part是封裝的普通表單域,還是檔案域. - 若有Content-Type存在, 但檔名為空, 則表示沒有選擇要上傳的檔案. - 如果有檔案存在, 則可以呼叫write()方法來寫入磁碟, 呼叫同時傳遞一個絕對路徑, 或是相對於@MultipartConfig註解的location屬性的相對路徑.
SimpleFileUploadServlet
/**
@author jifang. @since 2016/5/8 16:27.br/>*/ @MultipartConfig @WebServlet(name = "SimpleFileUploadServlet", urlPatterns = "/simple_file_upload_servlet.do") public class SimpleFileUploadServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); PrintWriter writer = response.getWriter(); Part file = request.getPart("file"); if (!isFileValid(file)) { writer.print("
請確認上傳檔案是否正確!"); } else { String fileName = file.getSubmittedFileName(); String saveDir = getServletContext().getRealPath("/WEB-INF/files/"); mkdirs(saveDir); file.write(saveDir + fileName);
writer.print("<h3>Uploaded file name: " + fileName);
writer.print("<h3>Size: " + file.getSize());
writer.print("<h3>Author: " + request.getParameter("author"));
複製程式碼
} }
private void mkdirs(String saveDir) { File dir = new File(saveDir); if (!dir.exists()) { dir.mkdirs(); } }
private boolean isFileValid(Part file) { // 上傳的並非檔案 if (file.getContentType() == null) { return false; } // 沒有選擇任何檔案 else if (Strings.isNullOrEmpty(file.getSubmittedFileName())) { return false; } return true; } } 優化 善用WEB-INF 存放在/WEB-INF/目錄下的資源無法在瀏覽器位址列直接訪問, 利用這一特點可將某些受保護資源存放在WEB-INF目錄下, 禁止使用者直接訪問(如使用者上傳的可執行檔案,如JSP等),以防被惡意執行, 造成伺服器資訊洩露等危險.
getServletContext().getRealPath("/WEB-INF/") 檔名亂碼 當檔名包含中文時,可能會出現亂碼,其解決方案與POST相同: 1 request.setCharacterEncoding("UTF-8"); 避免檔案同名 如果上傳同名檔案,會造成檔案覆蓋.因此可以為每份檔案生成一個唯一ID,然後連線原始檔名:
private String generateUUID() { return UUID.randomUUID().toString().replace("-", "_"); } 目錄打散 如果一個目錄下存放的檔案過多, 會導致檔案檢索速度下降,因此需要將檔案打散存放到不同目錄中, 在此我們採用Hash打散法(根據檔名生成Hash值, 取Hash值的前兩個字元作為二級目錄名), 將檔案分佈到一個二級目錄中: 1 2 3 4 private String generateTwoLevelDir(String destFileName) { String hash = Integer.toHexString(destFileName.hashCode()); return String.format("%s/%s", hash.charAt(0), hash.charAt(1)); } 採用Hash打散的好處是:在根目錄下最多生成16個目錄,而每個子目錄下最多再生成16個子子目錄,即一共256個目錄,且分佈較為均勻.
示例-簡易儲存圖片伺服器 需求: 提供上傳圖片功能, 為其生成外鏈, 並提供下載功能(見下)
客戶端