Upload-上傳 隨着3.0版本的發佈,文件上傳終於成爲Servlet規範的一項內置特性,再也不依賴於像Commons FileUpload之類組件,所以在服務端進行文件上傳編程變得不費吹灰之力.html
客戶端 要上傳文件, 必須利用multipart/form-data設置HTML表單的enctype屬性,且method必須爲POST:java
服務端 服務端Servlet主要圍繞着@MultipartConfig註解和Part接口:處理上傳文件的Servlet必須用@MultipartConfig註解標註:web
@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:spring
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中處理上傳文件時, 須要:mvc
- 經過查看是否存在Content-Type標頭, 檢驗一個Part是封裝的普通表單域,仍是文件域. - 如有Content-Type存在, 但文件名爲空, 則表示沒有選擇要上傳的文件. - 若是有文件存在, 則能夠調用write()方法來寫入磁盤, 調用同時傳遞一個絕對路徑, 或是相對於@MultipartConfig註解的location屬性的相對路徑.
SimpleFileUploadServletapp
/**
@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("
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個目錄,且分佈較爲均勻.
示例-簡易存儲圖片服務器 需求: 提供上傳圖片功能, 爲其生成外鏈, 並提供下載功能(見下)
客戶端