如何使用RestTemplate发送含Atom条目与文件的multipart/related请求
针对你的两个疑问,我来一步步帮你解决:
一、如何修改各请求部件的头部信息
当然可以自定义每个multipart部件的头部!问题出在你之前直接把JAXB对象和文件资源放进LinkedMultiValueMap,这时候RestTemplate会使用默认的头部规则。要自定义每个部件的Content-Type、Content-ID等,你需要把每个部件包装成带有独立HttpHeaders的HttpEntity,这样就能精准控制每个部分的头部信息。
二、正确构建multipart/related请求的完整实现
下面是调整后的完整代码,同时解决你提到的Atom部件Content-Type错误和文件部件添加Content-ID的问题:
1. 配置RestTemplate的消息转换器
首先要确保FormHttpMessageConverter支持multipart/related类型,并且JAXB消息转换器能处理application/atom+xml:
RestTemplate restTemplate = new RestTemplate(); // 让FormHttpMessageConverter支持multipart/related restTemplate.getMessageConverters().stream() .filter(FormHttpMessageConverter.class::isInstance) .map(FormHttpMessageConverter.class::cast) .findFirst() .ifPresent(converter -> { List<MediaType> supportedTypes = new ArrayList<>(converter.getSupportedMediaTypes()); supportedTypes.add(MediaType.MULTIPART_RELATED); converter.setSupportedMediaTypes(supportedTypes); }); // 确保JAXB转换器支持application/atom+xml(用于正确序列化你的Atom对象) restTemplate.getMessageConverters().stream() .filter(Jaxb2RootElementHttpMessageConverter.class::isInstance) .map(Jaxb2RootElementHttpMessageConverter.class::cast) .findFirst() .ifPresent(converter -> { List<MediaType> supportedTypes = new ArrayList<>(converter.getSupportedMediaTypes()); supportedTypes.add(new MediaType("application", "atom+xml")); converter.setSupportedMediaTypes(supportedTypes); });
2. 构建带自定义头部的请求部件
分别为Atom XML和文件创建带独立头部的HttpEntity:
// 构建Atom XML部件:设置Content-Type为application/atom+xml HttpHeaders atomHeaders = new HttpHeaders(); atomHeaders.setContentType(new MediaType("application", "atom+xml")); HttpEntity<YourAtomEntryClass> atomPart = new HttpEntity<>(e, atomHeaders); // e是你的JAXB Atom对象 // 构建文件部件:设置Content-Type和Content-ID HttpHeaders fileHeaders = new HttpHeaders(); fileHeaders.setContentType(MediaType.parseMediaType("video/mp2t")); fileHeaders.setContentId("<prod@example.com>"); // 注意Content-ID的格式 HttpEntity<FileSystemResource> filePart = new HttpEntity<>(new FileSystemResource(file), fileHeaders);
3. 组装并发送请求
把部件放进LinkedMultiValueMap,设置请求的Content-Type为multipart/related并指定类型参数:
LinkedMultiValueMap<String, Object> multipartMap = new LinkedMultiValueMap<>(); multipartMap.add("atom", atomPart); multipartMap.add("file", filePart); // 设置请求头:指定multipart/related类型和主类型(type=application/atom+xml) HttpHeaders requestHeaders = new HttpHeaders(); // 如果你需要手动指定boundary,可以这样写 requestHeaders.setContentType(MediaType.parseMediaType("multipart/related;boundary===9B752C681081408==;type=application/atom+xml")); // 不需要手动指定boundary的话,用下面的方式,RestTemplate会自动生成合适的boundary // requestHeaders.setContentType(new MediaType("multipart", "related", Collections.singletonMap("type", "application/atom+xml"))); HttpEntity<LinkedMultiValueMap<String, Object>> requestEntity = new HttpEntity<>(multipartMap, requestHeaders); ResponseEntity<String> response = restTemplate.postForEntity(url, requestEntity, String.class);
备选方案:使用WebClient实现(更简洁)
如果你可以切换到WebClient,代码会更直观,不需要手动配置太多转换器:
WebClient webClient = WebClient.create(); Mono<String> responseMono = webClient.post() .uri(url) .contentType(MediaType.parseMediaType("multipart/related;type=application/atom+xml")) .body(BodyInserters.fromMultipartData( MultipartBodyBuilder.create() .part("atom", e) .contentType(new MediaType("application", "atom+xml")) .part("file", new FileSystemResource(file)) .contentType(MediaType.parseMediaType("video/mp2t")) .header("Content-ID", "<prod@example.com>") .build() )) .retrieve() .bodyToMono(String.class); // 同步获取结果(如果需要非异步的话) String responseBody = responseMono.block();
关键要点总结
- 每个multipart部件必须包装成
HttpEntity(RestTemplate)或通过MultipartBodyBuilder配置(WebClient),才能自定义头部。 - 确保消息转换器支持对应的媒体类型,否则会出现序列化/反序列化错误或头部不匹配的问题。
- 请求的Content-Type必须明确设置为
multipart/related,并通过type参数指定主部件的媒体类型(这里是application/atom+xml)。
内容的提问来源于stack exchange,提问作者Dawid Sobierajski
相关产品推荐
相关产品推荐

