一、基本原理
上傳檔案的基本流程如下圖所示。瀏覽器端提供了一個表單,在使用者提交請求後,將檔案資料和其他表單資訊 編碼並上傳至伺服器端,伺服器端將上傳的內容進行解碼了,提取出 HTML 表單中的資訊,將檔案資料存入磁碟或資料庫。
二、各過程詳解
瀏覽器編碼
<form enctype="application/x-www-form-urlencoded" action="/upload" method="post" >
<input type="text" name="name"/><br/>
<input type="text" name="age"/><br/>
<input type="submit" value="上傳"/><br/>
</form>複製程式碼
在向伺服器端提交請求時,瀏覽器需要將大量的資料一同提交給 Server 端, 而提交前,瀏覽器需要按照 Server 端可以識別的方式進行編碼,對於普通 的表單資料,這種編碼方式很簡單,編碼後的結果通常是 field1=value2&field2=value2&… 的形式,如 name=ltq&age=18。通常使用的表單也是採用這種方式編碼的,Servlet 的 API 提供了對這種 編碼方式解碼的支援,只需要呼叫 ServletRequest 類中的方法就可以得到 使用者表單中的欄位和資料。
這種編碼方式( application/x-www-form-urlencoded )雖然簡單,但對於 傳輸大塊的二進位制資料顯得力不從心,對於傳輸這類資料,瀏覽器採用 了另一種編碼方式,即 "multipart/form-data" 的編碼方式,採用這種方式, 瀏覽器可以很容易的表單內的資料和檔案一起。這種編碼方式先定義好 一個不可能在資料中出現的字串作為分界符,然後用它將各個資料段 分開,而對於每個資料段都對應著 HTML 頁面表單中的一個 Input 區,包 括一個 content-disposition 屬性,說明了這個資料段的一些資訊,如果這個 資料段的內容是一個檔案,還會有 Content-Type 屬性,然後就是資料本身。 這裡,我們可以編寫一個簡單的 Servlet 來看到瀏覽器到底是怎樣編碼的。
實現流程:
- 過載 HttpServlet 中的 doPost 方法
- 呼叫 request.getContentLength() 得到 Content-Length ,並定義一個與 Content-Length 大小相等的位元組陣列 buffer 。
- 從HttpServletRequest 的例項 request 中得到一個 InputStream, 並把它讀入 buffer 中。
- 列印到控制檯,檢視上傳內容。
程式碼清單
- 前端程式碼
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title></head>
<body>
<form enctype="multipart/form-data" action="${pageContext.request.contextPath }/servlet/uploadServlet2" method="post" >
<input type="text" name="name"/><br/>
<input type="file" name="photo"/><br/>
<input type="submit" value="上傳"/><br/>
</form>
</body>
</html>複製程式碼
- 服務端程式碼
package com.itheima.upload;
import java.io.IOException;
import java.io.InputStream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class UploadServlet1 extends HttpServlet {
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
int len = request.getContentLength();
byte buffer[] = new byte[len];
InputStream in = request.getInputStream();
int total = 0;
int once = 0;
while ((total < len) && (once >=0)) {
once = in.read(buffer,total,len);
total += once;
}
System.out.println(new String(buffer,0,len));
}
}複製程式碼
在使用
這裡 ------WebKitFormBoundaryMsG9gupKbNVAw2Dn
就是瀏覽器指定的分界符,不同的瀏覽器有不同的確定分界符的方法,但都需要保證分界符不會在檔案內容中出現.
------WebKitFormBoundaryMsG9gupKbNVAw2Dn
Content-Disposition: form-data; name="name"
李同錢
------WebKitFormBoundaryMsG9gupKbNVAw2Dn
Content-Disposition: form-data; name="photo"; filename="robots.txt"
Content-Type: text/plain
# www.robotstxt.org/
# Allow crawling of all content
User-agent: *
Disallow:
------WebKitFormBoundaryMsG9gupKbNVAw2Dn--
以上是上傳輸出內容
瀏覽器採用預設的編碼方式是 application/x-www-form-urlencoded , 可以通過指定 form 標籤中的 enctype 屬性使瀏覽器知道此表單是用 multipart/form-data 方式編碼如:
<form enctype="multipart/form-data" action="${pageContext.request.contextPath}/servlet/uploadServlet2" method="post" > |
提交請求
提交請求的過程由瀏覽器完成的,並且遵循 HTTP 協議,每一個從瀏覽 器端到伺服器端的一個請求,都包含了大量與該請求有關的資訊, 在 Servlet 中,HttpServletRequest 類將這些資訊封裝起來,便於我們提取 使用。在檔案上載和表單提交的過程中,有兩個指的關心的問題,一是 上載的資料是是採用的那種方式的編碼,這個問題的可以從 Content-Type 中得到答案,另一個是問題是上載的資料量有多少即 Content-Length , 知道了它,就知道了 HttpServletRequest 的例項中有多少資料可以讀取 出來。這兩個屬性,我們都可以直接從 HttpServletRequest 的一個例項 中獲得,具體呼叫的方法是 getContentType() 和 getContentLength() 。
Content-Type 是一個字串,在上面的例子中,增加
System.out.println(request.getContentType()); |
可以得到這樣的一個輸出字串:
multipart/form-data; boundary=----WebKitFormBoundaryLJzBFw0CbuD1LLFn |
前半段正是編碼方式,而後半段正是分界符,通過 String 類中的方法, 我們可以把這個字串分解,提取出分界符。
String contentType=request.getContentType();
int start=contentType.indexOf("boundary=");
int boundaryLen=new String("boundary=").length();
String boundary=contentType.substring(start+boundaryLen);
boundary="--"+boundary;複製程式碼
解碼
經過以上的流程, 我們可以得到一個包含有所有上載資料的一個位元組陣列和一個分界符, 而我們要得到以下內容:
- 提交的表單中的各個欄位以及對應的值
- 如果表單中有 file 控制元件,並且使用者選擇了上傳檔案, 則需要分析出欄位的名稱、檔案在瀏覽器端的名字、檔案的 Content-Type 和檔案的內容。
位元組陣列的內容可以分解如下:
位元組中上傳的內容可能包括file型別的和普通表單型別,兩種型別DATA存在的欄位存在一些差異
具體解碼過程也可以分為兩個步驟:
將上傳的資料分解成資料段,每個資料段對應著表單中的一個 Input 區。對每個資料段,再進行分解,提出上述要求得到的內容。
通過分界符boundary,提出DATA,
實現將boundary轉換成buffer,與傳輸的Buffer迴圈比較找到相同的塊,切割出DATA,
在遞迴DATA比較其中的欄位,提出對應的欄位,儲存在集合中
https://www.ibm.com/developerworks/cn/java/fileup