Apache HttpClient发送文件时中文文件名变问号

使用Apache HttpClient发送multipart/form-data,包含有中文名的文件,对方收到的文件名中文变成了问号

解决方法:
发送方需要设置mode为HttpMultipartMode.RFC6532

发送端代码如下,其中关键行为builder.setMode(HttpMultipartMode.RFC6532);

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

@SpringBootTest
public class HttpClientTest {

    @Test
    public void test() {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            ContentType contentType = ContentType.create(
                    ContentType.MULTIPART_FORM_DATA.getMimeType(),
                    StandardCharsets.UTF_8);

            MultipartEntityBuilder builder = MultipartEntityBuilder.create();
            // 解决中文变问号的关键代码
            builder.setMode(HttpMultipartMode.RFC6532);
            builder.setCharset(StandardCharsets.UTF_8);
            builder.setContentType(contentType);
            builder.addTextBody("some_name", "中文的名字", contentType);
            builder.addBinaryBody("some_file", new File("H:\\这是一个中文名的文件_ABC_123.jpg"));
            HttpEntity httpEntity = builder.build();
            HttpPost httpPost = new HttpPost("http://localhost:8080/city/test");
            httpPost.setEntity(httpEntity);

            HttpResponse response = httpClient.execute(httpPost);
            System.out.println(response);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

接收端代码如下:

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;

@RestController
@RequestMapping("/city")
public class CityController {

    @PostMapping("/test")
    public String test(HttpServletRequest request) {
        MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
        Map<String, MultipartFile> fileMap = multipartRequest.getFileMap();
        fileMap.forEach((key, multipartFile) -> {
            System.out.println("key : " + key);
            String originalFilename = multipartFile.getOriginalFilename();
            System.out.println("file name : " + originalFilename);
            File file = new File("H:\\out" + File.separator + originalFilename);
            try {
                multipartFile.transferTo(file);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        });
        Map<String, String[]> parameterMap = multipartRequest.getParameterMap();
        parameterMap.forEach((key, value) -> {
            System.out.println("key : " + key);
            System.out.println("value : " + Arrays.toString(value));
        });

        return "this is a test";
    }
}

为什么要这样设置呢?因为默认是STRICT,需要改为RFC6532以处理中文。
源码中有这么一个枚举

package org.apache.http.entity.mime;

/**
 *
 * @since 4.0
 */
public enum HttpMultipartMode {

    /** RFC 822, RFC 2045, RFC 2046 compliant */
    STRICT,
    /** browser-compatible mode, i.e. only write Content-Disposition; use content charset */
    BROWSER_COMPATIBLE,
    /** RFC 6532 compliant */
    RFC6532

}

STRICT: 最严格的模式,遵循 RFC 822、RFC 2045 和 RFC 2046 的规范。在此模式下,通常要求严格按照标准编码和处理内容,不一定会特别处理非ASCII字符,可能会导致中文文件名乱码或转换为问号。

BROWSER_COMPATIBLE: 较为宽松的模式,主要兼容浏览器的行为。它通常仅写入 Content-Disposition 头部,使用当前默认的字符集(通常是 ISO-8859-1 或类似的)来编码内容。对于非ASCII字符的处理可能因浏览器而异,可能导致中文文件名出现问号。

RFC6532: 较新的标准,专门用于处理国际化的多部分内容。在这种模式下,使用 RFC 6532 定义的规则来处理非ASCII字符,包括文件名等内容不会出现乱码或问号。这种模式适合需要确保国际化字符正确传输的场景。

posted @ 2024-07-31 22:44  年迈的魔法师  阅读(14)  评论(0编辑  收藏  举报