1. 程式人生 > >java web 檔案上傳的總結

java web 檔案上傳的總結

前言

1. 關於enctype=”multipart/form-data”:
對於表單enctype屬性,w3school解釋如下:規定在傳送到伺服器之前應該如何對錶單資料進行編碼,預設的,表單資料會編碼為 “application/x-www-form-urlencoded” 就是說,在傳送到伺服器之前,所有字元都會進行編碼(空格轉換為 “+” 加號,特殊符號轉換為 ASCII HEX 值)。
這裡寫圖片描述
另外,application/x-www-form-urlencoded(預設)的POST資料是放在請求體的Form Data中,其他都是放在Request Payload 中。至於在上傳控制元件的表單一定用multipart/form-data,因為不知道使用者上傳的是什麼,如果對其字元編碼後伺服器再解碼是很費時且容易出錯的。

2. 關於表單裡普通文字域request.getParameter()獲取不到值?
原因:由於Tomcat對於Content-Type multipart/form-data(檔案上傳)和application/x-www-form-urlencoded(普通POST請求)做了“特殊處理”。下面來看看相關的處理程式碼。

Tomcat的HttpServletRequest類的實現類為org.apache.catalina.connector.Request(實際上是org.apache.coyote.Request),而它對處理請求引數的方法為protected void parseParameters(),這個方法中對Content-Type multipart/form-data(檔案上傳)和application/x-www-form-urlencoded(POST請求)的處理程式碼如下:

protectedvoid parseParameters() {  
           //省略部分程式碼......  
           parameters.handleQueryParameters();// 這裡是處理url中的引數  
           //省略部分程式碼......  
           if ("multipart/form-data".equals(contentType)) { // 這裡是處理檔案上傳請求  
                parseParts();  
                success = true;  
                return
; } if(!("application/x-www-form-urlencoded".equals(contentType))) {// 這裡如果是非POST請求直接返回,不再進行處理 success = true; return; } //下面的程式碼才是處理普通POST請求引數 //省略部分程式碼...... try { if (readPostBody(formData, len)!= len) { // 讀取請求體資料 return; } } catch (IOException e) { // Client disconnect if(context.getLogger().isDebugEnabled()) { context.getLogger().debug( sm.getString("coyoteRequest.parseParameters"),e); } return; } parameters.processParameters(formData, 0, len); // 處理POST請求引數,把它放到requestparameter map中(即request.getParameterMap獲取到的Map,request.getParameter(name)也是從這個Map中獲取的) // 省略部分程式碼...... } protected int readPostBody(byte body[], int len) throws IOException { int offset = 0; do { int inputLen = getStream().read(body, offset, len - offset); if (inputLen <= 0) { return offset; } offset += inputLen; } while ((len - offset) > 0); return len; }

從上面程式碼可以看出,Content-Type不是application/x-www-form-urlencoded的POST請求是不會讀取請求體資料和進行相應的引數處理的,即不會解析表單資料來放到request parameter map中。所以通過request.getParameter(name)是獲取不到的。

參見部落格:AJAX POST請求中引數以form data和request payload形式在servlet中的獲取方式

但是,request.getParameter()獲取不到引數值是在沒有使用註解@MultipartConfig或者web.xml沒有配置servlet的子元素multipart-config情況下,只要使用了註解或者配置了,就能request.getParameter()獲取到表單中普通的文字域了。

程式碼分析

上傳表單:

<form action="test_10" enctype="multipart/form-data" method="post" >
    會員名稱<input type="text" name="mname" size="30"/></br>
    <input type="file" name="fileName" size="30"/></br>
    <input type="submit" value="提交"/>
    <input type="reset" value="重置"/>
</form>
  1. 用io流來獲取(無@MultipartConfig,用了註解後request.getInputStream()就無法獲取輸入流了,但可以獲取各個Part的輸入流,原因不明,先留個坑)
public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String name=request.getParameter("mname");
        System.out.println("會員名"+name);
        InputStream in=request.getInputStream();
        OutputStream out=response.getOutputStream();
        byte[] b=new byte[1024];
        int len;
        while((len=in.read(b))!=-1){
            out.write(b,0,len);
        }
        in.close();
        out.close();
    }

結果:

------WebKitFormBoundary61NGudaHeAB24p96
Content-Disposition: form-data; name="mname"

1234
------WebKitFormBoundary61NGudaHeAB24p96
Content-Disposition: form-data; name="fileName"; filename="abc.txt"
Content-Type: text/plain

this is a txt
------WebKitFormBoundary61NGudaHeAB24p96--

拿到資料後就是解析、擷取的問題了,解析工作可以參考這篇部落格:用 Servlet 進行上載的原理和實現
後來太麻煩了就有了第三方工具包,比如這篇部落格common-upload的使用
再後來Servlet3.0出來增加了Part介面,上傳檔案就容易多了

2. 通過javax.servlet.http.part類來讀取
Servlet3.0提供了對檔案上傳的支援,通過@MultipartConfig標註和HttpServletRequest的兩個新方法getPart()和getParts(),開發者能夠很容易實現檔案的上傳操作。
@MultipartConfig標註主要是為了輔助Servlet3.0中HttpServletRequest提供的對上傳檔案的支援。該標註寫在Servlet類的宣告之前,一表示該Servlet希望處理的請求時multipart/form-data型別的,二是使用該註解,HttpServletRequest物件才可以得到表單資料的各個部分。另外,該標註還提供了若干屬性用於簡化對上傳檔案的處理。
@MultipartConfig標註屬性
fileSizeThershold int型 是(可選) 描述:當前資料量大於該值時,內容將被寫入檔案。
location String型 是(可選) 描述:存放生成檔案的地址
maxFileSize long型 是(可選) 描述:允許上傳的檔案最大值,預設為-1,表示沒有限制
maxRequestSize long型 是(可選) 描述:針對 multipart/form-data 請求的最大數量,預設為-1,表示沒有限制

 @MultipartConfig
 ....
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
                String name=request.getParameter("mname");
        System.out.println("會員名"+name);
        String path="E:\\demo";

        Part filePart = request.getPart("fileName");
        if(filePart.getSize()>1024*1024){//上傳檔案不能超過1MB大小
            filePart.delete();
        }else{
            path+="\\number\\"+name;
            File f=new File(path);
            if(!f.exists()){ //目錄不存在則建立目錄
                f.mkdirs();
            }
        }
        //
        String h=filePart.getHeader("content-disposition");
        System.out.println(h);
        //servlet 3.1版本才能用 filePart.getSubmittedFileName();  

        //拼接出檔名
        String fname=h.substring(h.lastIndexOf("=")+2,h.length()-1);
        filePart.write(path+"\\"+fname);
    }

以下是Part類常用方法:
public Part getPart(String name):返回用name指定名稱的Part物件
public Collection<Part > getParts():返回所有Part物件的集合
public InputStream getInputStream() throws IOException:返回Part物件的輸入流物件
public String getContentType():返回Part物件的內容型別
public long getSize():返回Part物件的大小
public String getName():返回Part物件的名稱
public String getHeader(String name):返回Part物件指定的MIME頭的值
public Collection< String> getHeaders(String name):返回name指定的頭 值的集合
public Collection< String> getHeaderName():返回Part物件頭名稱的集合
public void delete() throws IOException:刪除臨時檔案
public void write(String fileName) throws IOException:將Part物件寫到指定的檔案中