前言
對於這塊知識點,我一直都是模糊的,不是非常清楚的。在平時的工作中,遇到上傳的問題,也沒有深入的去研究過,也都是直接用別人封裝好的類來完成自己的工作。某一天,看了本書,說到這個知識點,一臉茫然,覺的有必要去深入的學習一下,至少要讓自己明白HTTP檔案上傳的過程,這個原理,以便將來出現問題,也能通過原理進行深入的分析,而不是等問題來了,兩眼一抹黑,然後漫無目的的百度。哦,如果是那樣子,那該是多麼的痛苦,多麼的無助。
所以查缺補漏,以免讓將來的自己感到無助、痛苦,甚至難堪,走起!!!
HTTP上傳原理
我們在開發的時候,當要用到檔案上傳功能時,前端開發人員都會告訴你以下幾條金科律令:
- 提交方式必須為
post
; - 表單中有檔案上傳的表單項必須為
<input type="file"/>
; - 必須指定表單型別
enctype="multipart/form-data"
。
是的,我們必須按照上面這三條鐵令進行設定,否則就無法上傳檔案。比如我們一般會這麼寫:
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<body>
<form action="<%=request.getContextPath() %>/UploadServletDemo" enctype="multipart/form-data" method="post">
上傳使用者:<input type="text" name="username"><br/>
上傳檔案1:<input type="file" name="file1"><br/>
上傳檔案2:<input type="file" name="file2"><br/>
<input type="submit" value="提交">
</form>
</body>
</html>
我這裡寫了一個簡單的Servlet:
@WebServlet("/UploadServletDemo")
public class UploadServletDemo extends HttpServlet{
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("utf-8");
// 獲取表單(POST)資料
ServletInputStream in = request.getInputStream();//此方法得到所有的提交資訊,不僅僅只有內容
// 轉換流
InputStreamReader inReaser = new InputStreamReader(in);
// 緩衝流
BufferedReader reader = new BufferedReader(inReaser);
String str = null;
while ((str=reader.readLine()) != null){
System.out.println(str);
}
}
}
我們把程式跑起來,然後通過Fiddler進行抓包,可以看到我們傳送的Post請求中,請求體中有以下這樣的資料:
POST http://localhost:8080/javawebservlet_war/UploadServletDemo HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 446
Cache-Control: max-age=0
Origin: http://localhost:8080
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryqj67FUBQUHXZj78G
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Referer: http://localhost:8080/javawebservlet_war/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,und;q=0.7,zh-TW;q=0.6
Cookie: JSESSIONID=6BE280EF3CBE213F73430FFDF015DE97
------WebKitFormBoundaryqj67FUBQUHXZj78G
Content-Disposition: form-data; name="username"
abc
------WebKitFormBoundaryqj67FUBQUHXZj78G
Content-Disposition: form-data; name="file1"; filename="檔案1.txt"
Content-Type: text/plain
ABC檔案1
------WebKitFormBoundaryqj67FUBQUHXZj78G
Content-Disposition: form-data; name="file2"; filename="檔案2.txt"
Content-Type: text/plain
BDF檔案2
------WebKitFormBoundaryqj67FUBQUHXZj78G--
到這裡,我們就大概就知道了HTTP上傳檔案的原理了。HTTP把需要上傳的表單的所有資料按照一定的格式存放在請求體中,對於檔案也是同樣的。
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryqj67FUBQUHXZj78G
表示要上傳附件,其中boundary
表示分隔符,如果表單中有多項,就要使用boundary
進行分隔,每個表單項由------FormBoundary
開始,以------FormBoundary
結尾。例如這樣:
------FormBoundary
Content-Disposition: form-data; name="param1"
value1
------FormBoundary
這個boundary
的值是由瀏覽器生成的,由瀏覽器來保證與上傳內容不重複。在每個分隔項裡,需要我們去重點關注Content-Disposition
訊息頭,其中第一個引數總是固定不變的form-data
,name表示表單元素屬性名,回車換行符後面的內容就是元素的值。還有Content-Type
表示我們上傳的檔案的MIME型別,我們在伺服器端需要根據這個進行檔案的區分。
HTTP就是按照這種格式,把表單中的資料封裝成一個請求一股腦的發給伺服器端,伺服器端根據這種規則對接收到的請求進行解析,從而完成檔案上傳功能。
最後一個
boundary
的結尾會多兩個--
FileUpload元件
通過上面的描述,我們可以知道完成檔案上傳功能,重點工作不是在於客戶端,而是在於伺服器端。伺服器端需要根據客戶端傳送過來的請求,根據上面說的規則對請求報文進行解析,從而提取出上傳的檔案內容。可以看到,雖然上面的規則比較簡單,但是用不同的開發語言來一次性寫出沒有Bug的解析程式,也不是那麼簡單的。而且這種實現過一遍,就可以大家共享的東西就非常適合開發成元件供大家一起使用,所以呢,開源社群就開發了這樣的一個元件,這個元件來給我們完成了上面規則的編碼,而我們需要做的就是去學會使用這個元件,就這麼簡單!
下面就通過一個簡單的FileUpload
Demo程式來總結一下如何使用FileUpload
元件。
FileUpload Demo
下面來一段簡單的使用Demo。
@WebServlet("/FileUpload")
public class FileUploadDemo extends HttpServlet {
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
DiskFileItemFactory fac = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(fac);
upload.setFileSizeMax(10 * 1024 * 1024);
upload.setSizeMax(20 * 1024 * 1024);
if (upload.isMultipartContent(request)) {
try {
List<FileItem> list = upload.parseRequest(request);
for (FileItem item : list) {
if (item.isFormField()) {
String fileName = item.getFieldName();
String value = item.getString("UTF-8");
System.out.println(fileName + ":" + value);
} else {
String name = item.getName();
String id = UUID.randomUUID().toString();
name = id + name;
String realPath = getServletContext().getRealPath("/upload");
File file = new File(realPath, name);
item.write(file);
item.delete();
}
}
} catch (Exception e) {
e.printStackTrace();
}
} else {
System.out.println("不處理!");
}
}
}
總結
這篇文章對通過HTTP協議進行檔案上傳原理進行了比較詳細的分析和總結,希望對大家有幫助!
2019年7月31日 於內蒙古呼和浩特。