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字符,包括文件名等内容不会出现乱码或问号。这种模式适合需要确保国际化字符正确传输的场景。