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

Jersey 處理 HTTP Multipart 協議完整實戰:客戶端與服務端雙向解析

     編輯:Java 相關知識

前言

HTTP Multipart(multipart/form-data)是文件上傳、混合數據提交的標準協議。本文基於 Jersey 框架,通過一個「XML Head + XML Body」雙部件業務場景,完整演示客戶端構建請求、服務端解析響應的最佳實踐。

一、Multipart 協議本質


POST /resource HTTP/1.1
Content-Type: multipart/form-data; boundary=Boundary_1_30911772_1367997277472

--Boundary_1_30911772_1367997277472
Content-Disposition: form-data; name="xmlhead"
Content-Type: multipart/form-data


...head xml...
--Boundary_1_30911772_1367997277472
Content-Disposition: form-data; name="xmlbody"
Content-Type: multipart/form-data



--Boundary_1_30911772_1367997277472--

關鍵點:

  • boundary 隨機生成,分隔各部件
  • 每部件含 Content-Disposition: form-data; name="fieldName"
  • 部件內可有獨立 Content-Type
  • 最後以 --boundary-- 結束

二、Maven 依賴


<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-multipart</artifactId>
    <version>2.35</version> <!-- 對應 Jersey 版本 -->
</dependency>
<dependency>
    <groupId>org.glassfish.jersey.inject</groupId>
    <artifactId>jersey-hk2</artifactId>
    <version>2.35</version>
</dependency>

註冊特性:register(MultiPartFeature.class)(客戶端/服務端均需)

三、客戶端構建請求


import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;

public class MultipartClient {
    private static final String HEAD_FIELD = "xmlhead";
    private static final String BODY_FIELD = "xmlbody";
    private final WebTarget target;

    public MultipartClient(String baseUrl) {
        ClientConfig config = new ClientConfig().register(MultiPartFeature.class);
        Client client = ClientBuilder.newClient(config);
        this.target = client.target(baseUrl);
    }

    public String post(String headXml, String bodyXml) {
        FormDataMultiPart multiPart = new FormDataMultiPart()
            .field(HEAD_FIELD, headXml, MediaType.MULTIPART_FORM_DATA_TYPE)
            .field(BODY_FIELD, bodyXml, MediaType.MULTIPART_FORM_DATA_TYPE);

        return target.request()
            .post(Entity.entity(multiPart, MediaType.MULTIPART_FORM_DATA), String.class);
    }
}

四、服務端解析響應


import org.glassfish.jersey.media.multipart.FormDataMultiPart;
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import java.util.ArrayList;
import java.util.List;

@Path("/boss")
public class BossResource {
    @POST
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Produces(MediaType.APPLICATION_XML)
    public String service(FormDataMultiPart multiPart) {
        if (multiPart == null) {
            throw new BadRequestException("Request multipart is null");
        }

        List fields = new ArrayList<>();
        for (FormDataBodyPart part : multiPart.getBodyParts()) {
            String name = part.getName().trim();
            String value = part.getEntityAs(String.class);

            if (HEAD_FIELD.equalsIgnoreCase(name) || BODY_FIELD.equalsIgnoreCase(name)) {
                fields.add(new RequestField(name, value));
            } else {
                log.warn("Ignored unknown field: {}", name);
            }
        }

        // 業務處理...
        return buildResponse(fields);
    }
}

五、關鍵技術點詳解

1. FormDataMultiPart vs MultiPart

類用途
FormDataMultiPart專為 application/x-www-form-urlencoded 和 multipart/form-data 設計,.field(name, value) 便捷添加表單字段
MultiPart通用 multipart 容器,需手動構建 BodyPart,適用於非表單場景(如 multipart/related、multipart/mixed)

2. 部件實體讀取方式


// 字符串
String xml = part.getEntityAs(String.class);

// 輸入流(大文件流式處理)
InputStream is = part.getEntityAs(InputStream.class);

// 字節數組
byte[] bytes = part.getEntityAs(byte[].class);

// 文件上傳場景
FormDataContentDisposition cd = part.getFormDataContentDisposition();
String fileName = cd.getFileName();
InputStream fileIs = part.getEntityAs(InputStream.class);
// 流式寫入磁盤...

3. 邊界自動生成

Jersey 內部使用 MultiPartWriter 自動生成唯一 boundary,無需手動干預。每次請求 boundary 不同,符合 RFC 2046。

4. 大文件/內存控制

  • 默認內存緩衝,大文件易 OOM
  • 配置 MultiPartFeature 參數或使用 FileDataBodyPart 臨時文件存儲
  • 生產環境建議:jersey.config.multipart.maxBodySize 限制總大小,maxFileSize 限制單文件

六、常見坑點與避坑指南

問題原因解決
服務端收到 null未註冊 MultiPartFeatureClientConfig/ResourceConfig 均 .register(MultiPartFeature.class)
字段名大小寫不匹配HTTP 規範不區分大小寫,但代碼比較區分使用 equalsIgnoreCase 或統一規範
中文亂碼部件未指定 charset,默認 ISO-8859-1客戶端 .field(name, value, MediaType.APPLICATION_XML_TYPE.withCharset("UTF-8"))
大文件 OOM全量讀入內存使用 InputStream 流式處理、臨時文件、限制大小
邊界解析失敗Content-Type 缺少 boundary 參數確保發送 multipart/form-data 而非 application/json

七、現代化替代:Spring Boot + WebFlux

若項目已遷移 Spring 生態,推薦使用 WebClient + MultipartBodyBuilder:


WebClient client = WebClient.create(baseUrl);
MultipartBodyBuilder builder = new MultipartBodyBuilder();
builder.part("xmlhead", headXml);
builder.part("xmlbody", bodyXml);

Mono response = client.post()
    .contentType(MediaType.MULTIPART_FORM_DATA)
    .body(BodyInserters.fromMultipartData(builder.build()))
    .retrieve()
    .bodyToMono(String.class);

服務端:@RequestPart("xmlhead") String head, @RequestPart("xmlbody") String body,自動綁定,零依賴 Jersey。

八、總結

Jersey 處理 Multipart 核心在於:

  1. 註冊 MultiPartFeature 啟用支持
  2. 客戶端用 FormDataMultiPart.field() 鏈式構建
  3. 服務端接收 FormDataMultiPart 參數,遍歷 getBodyParts() 解析
  4. 注意字符編碼、大文件流式、邊界自動管理

理解 RFC 2046 協議結構,配合 Jersey 高級 API,即可輕鬆應對混合數據、文件上傳、複雜表單等企業級集成場景。

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