首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

JavaBean 實現檔案上傳完整解析:HTTP Multipart 請求手工解析與核心邏輯構建

     編輯:Java 程式設計入門

檔案上傳是 Web 開發的高頻需求。在 Servlet 3.0 引入 @MultipartConfig 之前,開發者需手工解析 multipart/form-data 請求體。本文透過從零構建簡易上傳 Bean,深度剖析 HTTP 檔案上傳請求的原始結構與解析邏輯。

前端表單構建

<form action="jsp1.jsp" enctype="multipart/form-data" method="post">
    作者: <input type="text" name="author" /><br/>
    公司: <input type="text" name="company" /><br/>
    選擇檔案: <input type="file" name="filename" /><br/>
    <input type="submit" value="上傳" />
</form>

關鍵點:enctype="multipart/form-data" 告知瀏覽器採用多部分編碼,而非預設的 application/x-www-form-urlencoded。

JSP 呼叫上傳 Bean

<jsp:useBean id="uploadBean" class="SimpleBean" scope="page" />
<%
    uploadBean.doUpload(request);
%>

核心 Bean 實作:原始請求流捕獲

import java.io.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.ServletInputStream;

public class SimpleBean {
    public void doUpload(HttpServletRequest request) throws IOException {
        // 將原始請求體完整寫入檔案供分析
        try (PrintWriter pw = new PrintWriter(
                new BufferedWriter(new FileWriter("Demo.out"));
             ServletInputStream in = request.getInputStream()) {
            
            int b;
            while ((b = in.read()) != -1) {
                pw.print((char) b);
            }
        }
    }
}

實際請求體結構分析 (Demo.out)

-----------------------------7d15340138
Content-Disposition: form-data; name="Author"

A. Christie
-----------------------------7d15340138
Content-Disposition: form-data; name="Company"

Abisco
-----------------------------7d15340138
Content-Disposition: form-data; name="Filename"; filename="C:\123\data\abisco.html"
Content-Type: text/html

<html>
<head>
<title>Abisco</title>
</head>
</html>

-----------------------------7d15340138--

Multipart 請求結構深度解析

邊界分隔符

  • 格式:----------------------------- + 隨機數字
  • 每個部分以 --{boundary} 開頭
  • 結束標記:--{boundary}-- (尾部雙連字號)

一般表單欄位部分

--{boundary}
Content-Disposition: form-data; name="{fieldName}"

{fieldValue}
  • 標頭結束以 雙 CRLF (\r\n\r\n) 標識
  • 緊接欄位值,無額外編碼

檔案上傳部分

--{boundary}
Content-Disposition: form-data; name="{fieldName}"; filename="{clientPath}"
Content-Type: {mimeType}

{fileBinaryContent}
  • filename:Windows 瀏覽器傳送完整路徑;Linux/Mac 僅傳檔名
  • Content-Type:瀏覽器依副檔名推斷 MIME 類型
  • 雙 CRLF 後為檔案二進位內容

生產級解析邏輯設計要點

核心解析流程

  1. 從 Content-Type 標頭提取 boundary 參數
  2. 讀取請求輸入流,按 --{boundary} 分割各部分
  3. 解析各部分標頭區(至雙 CRLF)提取 Content-Disposition、Content-Type
  4. 根據 filename 存在與否判斷:一般欄位 vs 檔案
  5. 一般欄位:讀取剩餘內容為字串值
  6. 檔案欄位:流式寫入目標檔案(避免大檔案 OOM)

關鍵技術難點

挑戰解決方案
大檔案記憶體溢出臨界值超過時落地磁碟,使用 FileItemFactory 機制
邊界匹配效能Boyce-Moore/KMP 算法優化邊界搜尋
字元編碼處理標頭用 ISO-8859-1,欄位值用請求編碼 (UTF-8)
檔名安全性提取純檔名 new File(filename).getName(),防止路徑遍歷
惡意請求防護限制總大小、單檔大小、檔案類型白名單

現代替代方案

  • Servlet 3.0+:@MultipartConfig + request.getPart() / request.getParts()
  • Apache Commons FileUpload:成熟穩健,支援進度監聽、磁碟臨時檔
  • Spring MultipartResolver:整合 Commons FileUpload,提供 MultipartFile 介面

完整上傳 Bean 骨架 (生產就緒版)

public class FileUploadBean {
    private static final int MEMORY_THRESHOLD = 1024 * 1024; // 1MB
    private static final long MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB
    private static final long MAX_REQUEST_SIZE = 100 * 1024 * 1024; // 100MB
    
    public Map<String, Object> parseRequest(HttpServletRequest request) 
            throws IOException, ServletException {
        
        // 1. 驗證 Content-Type
        String contentType = request.getContentType();
        if (contentType == null || !contentType.startsWith("multipart/")) {
            throw new ServletException("非 multipart 請求");
        }
        
        // 2. 使用 Commons FileUpload (推薦)
        DiskFileItemFactory factory = new DiskFileItemFactory();
        factory.setSizeThreshold(MEMORY_THRESHOLD);
        factory.setRepository(new File(System.getProperty("java.io.tmpdir")));
        
        ServletFileUpload upload = new ServletFileUpload(factory);
        upload.setFileSizeMax(MAX_FILE_SIZE);
        upload.setSizeMax(MAX_REQUEST_SIZE);
        upload.setHeaderEncoding("UTF-8");
        
        // 3. 解析並分類
        Map<String, Object> result = new HashMap<>();
        List<FileItem> items = upload.parseRequest(request);
        
        for (FileItem item : items) {
            if (item.isFormField()) {
                // 一般欄位
                result.put(item.getFieldName(), item.getString("UTF-8"));
            } else {
                // 檔案欄位
                String fileName = new File(item.getName()).getName();
                File savedFile = new File(UPLOAD_DIR, generateUniqueName(fileName));
                item.write(savedFile);
                result.put(item.getFieldName(), savedFile);
            }
        }
        return result;
    }
}

從手工解析原始請求流到採用成熟框架,理解 Multipart 協議細節是排查上傳異常、實現自定義需求(如斷點續傳、流式處理、病毒掃描整合)的必經之路。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved