Spring 5: 使用RestTemplate 将POST 多个文件与POJO; MediaType.MULTIPART_FORM_DATA 与 LinkedMultiValueMap
Spring 5: use RestTemplate to POST multiple files with POJO; MediaType.MULTIPART_FORM_DATA with LinkedMultiValueMap
版本:
Java 11
Spring 5.3.9
Jackson 2.13
org.apache.httpcomponents:httpclient: 4.5.13
我认为这里的问题是开箱即用,RestTemplate
无法处理 HttpEntity
其值本身就是 MultiValueMap
with (String
, Resource
) 对。如何解决?我想规范的用例是支持通过 HTML 形式同时上传多个文件以及元数据。详情如下。
消息转换器如下:
private List<HttpMessageConverter<?>> getMessageConverters()
{
List<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(MediaType.TEXT_HTML);
mediaTypes.add(MediaType.APPLICATION_JSON);
mediaTypes.add(MediaType.TEXT_PLAIN);
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(mediaTypes);
List<MediaType> formMediaTypes = new ArrayList<>();
formMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
formMediaTypes.add(MediaType.MULTIPART_FORM_DATA);
FormHttpMessageConverter formConverter = new FormHttpMessageConverter();
formConverter.setSupportedMediaTypes(formMediaTypes);
formConverter.addPartConverter(new MappingJackson2HttpMessageConverter());
formConverter.addPartConverter(new ResourceHttpMessageConverter());
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
stringConverter.setSupportedMediaTypes(formMediaTypes);
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
messageConverters.add(converter);
messageConverters.add(formConverter);
messageConverters.add(stringConverter);
return messageConverters;
}
并且:
RestTemplate restTemplate = new RestTemplate(requestFactory);
restTemplate.setMessageConverters(getMessageConverters());
然后我用这些文件创建一个 HttpEntity
(在这个例子中,我只 POST 创建一个文件):
ByteArrayResource bas = new ByteArrayResource(labxReport.getPDFFile().getBytes()) {
@Override public String getFilename() { return reportFilename; }
};
MultiValueMap<String, Object> reportFiles = new LinkedMultiValueMap<String, Object>();
reportFiles.add(reportFilename, bas);
HttpHeaders reportFilesReqHeaders = new HttpHeaders();
reportFilesReqHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
HttpEntity<MultiValueMap<String, Object>> reportFilesEntity = new HttpEntity<>(reportFiles, reportFilesReqHeaders);
对于 POJO(这里是 ReportInfo
的一个实例),我创建了一个单独的 HttpEntity
,如下所示:
HttpHeaders reportInfoReqHeaders = new HttpHeaders();
reportInfoReqHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<ReportInfo> reportInfoEntity = new HttpEntity<>(reportInfo, reportInfoReqHeaders);
然后我为我的主要 POST 请求拼凑 HttpEntity
,如下所示:
MultiValueMap<String, Object> postParams = new LinkedMultiValueMap<String, Object>();
postParams.set("files", reportFilesEntity);
postParams.set("data", reportInfoEntity);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
httpHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
HttpEntity<MultiValueMap<String, Object>> requestPOST = new HttpEntity<>(postParams, httpHeaders);
最后,我提出 POST 请求:
ResponseEntity<String> response = restTemplate.exchange(PORTAL_URL, HttpMethod.POST, requestPOST, String.class);
这导致以下堆栈跟踪:
org.springframework.http.converter.HttpMessageNotWritableException: Could not write request: no suitable HttpMessageConverter found for request type [org.springframework.util.LinkedMultiValueMap]
at org.springframework.http.converter.FormHttpMessageConverter.writePart(FormHttpMessageConverter.java:532) ~[spring-web-5.2.16.RELEASE.jar:5.2.16.RELEASE]
at org.springframework.http.converter.FormHttpMessageConverter.writeParts(FormHttpMessageConverter.java:503) ~[spring-web-5.2.16.RELEASE.jar:5.2.16.RELEASE]
at org.springframework.http.converter.FormHttpMessageConverter.writeMultipart(FormHttpMessageConverter.java:483) ~[spring-web-5.2.16.RELEASE.jar:5.2.16.RELEASE]
at org.springframework.http.converter.FormHttpMessageConverter.write(FormHttpMessageConverter.java:360) ~[spring-web-5.2.16.RELEASE.jar:5.2.16.RELEASE]
at org.springframework.http.converter.FormHttpMessageConverter.write(FormHttpMessageConverter.java:156) ~[spring-web-5.2.16.RELEASE.jar:5.2.16.RELEASE]
at org.springframework.web.client.RestTemplate$HttpEntityRequestCallback.doWithRequest(RestTemplate.java:950) ~[spring-web-5.2.16.RELEASE.jar:5.2.16.RELEASE]
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:735) ~[spring-web-5.2.16.RELEASE.jar:5.2.16.RELEASE]
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:672) ~[spring-web-5.2.16.RELEASE.jar:5.2.16.RELEASE]
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:581) ~[spring-web-5.2.16.RELEASE.jar:5.2.16.RELEASE]
...
谢谢。
我解决这个问题的方法如下getMessageConverters
所示。当时的想法是 files
表单参数本身就是一种 form-based 消息(即具有 key/value 对),因此需要 FormHttpMessageConverter
.[=14 的另一个实例=]
这是对我有用的逻辑:
private List<HttpMessageConverter<?>> getMessageConverters()
{
List<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(MediaType.TEXT_HTML);
mediaTypes.add(MediaType.APPLICATION_JSON);
mediaTypes.add(MediaType.TEXT_PLAIN);
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(mediaTypes);
List<MediaType> formMediaTypes = new ArrayList<>();
formMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
formMediaTypes.add(MediaType.MULTIPART_FORM_DATA);
FormHttpMessageConverter formConverter = new FormHttpMessageConverter();
formConverter.setSupportedMediaTypes(formMediaTypes);
formConverter.addPartConverter(new MappingJackson2HttpMessageConverter());
FormHttpMessageConverter multifileConverter = new FormHttpMessageConverter();
multifileConverter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_OCTET_STREAM));
multifileConverter.addPartConverter(new ResourceHttpMessageConverter());
formConverter.addPartConverter(multifileConverter);
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
stringConverter.setSupportedMediaTypes(formMediaTypes);
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
messageConverters.add(converter);
messageConverters.add(formConverter);
messageConverters.add(stringConverter);
return messageConverters;
}
版本:
Java 11
Spring 5.3.9
Jackson 2.13
org.apache.httpcomponents:httpclient: 4.5.13
我认为这里的问题是开箱即用,RestTemplate
无法处理 HttpEntity
其值本身就是 MultiValueMap
with (String
, Resource
) 对。如何解决?我想规范的用例是支持通过 HTML 形式同时上传多个文件以及元数据。详情如下。
消息转换器如下:
private List<HttpMessageConverter<?>> getMessageConverters()
{
List<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(MediaType.TEXT_HTML);
mediaTypes.add(MediaType.APPLICATION_JSON);
mediaTypes.add(MediaType.TEXT_PLAIN);
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(mediaTypes);
List<MediaType> formMediaTypes = new ArrayList<>();
formMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
formMediaTypes.add(MediaType.MULTIPART_FORM_DATA);
FormHttpMessageConverter formConverter = new FormHttpMessageConverter();
formConverter.setSupportedMediaTypes(formMediaTypes);
formConverter.addPartConverter(new MappingJackson2HttpMessageConverter());
formConverter.addPartConverter(new ResourceHttpMessageConverter());
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
stringConverter.setSupportedMediaTypes(formMediaTypes);
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
messageConverters.add(converter);
messageConverters.add(formConverter);
messageConverters.add(stringConverter);
return messageConverters;
}
并且:
RestTemplate restTemplate = new RestTemplate(requestFactory);
restTemplate.setMessageConverters(getMessageConverters());
然后我用这些文件创建一个 HttpEntity
(在这个例子中,我只 POST 创建一个文件):
ByteArrayResource bas = new ByteArrayResource(labxReport.getPDFFile().getBytes()) {
@Override public String getFilename() { return reportFilename; }
};
MultiValueMap<String, Object> reportFiles = new LinkedMultiValueMap<String, Object>();
reportFiles.add(reportFilename, bas);
HttpHeaders reportFilesReqHeaders = new HttpHeaders();
reportFilesReqHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
HttpEntity<MultiValueMap<String, Object>> reportFilesEntity = new HttpEntity<>(reportFiles, reportFilesReqHeaders);
对于 POJO(这里是 ReportInfo
的一个实例),我创建了一个单独的 HttpEntity
,如下所示:
HttpHeaders reportInfoReqHeaders = new HttpHeaders();
reportInfoReqHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<ReportInfo> reportInfoEntity = new HttpEntity<>(reportInfo, reportInfoReqHeaders);
然后我为我的主要 POST 请求拼凑 HttpEntity
,如下所示:
MultiValueMap<String, Object> postParams = new LinkedMultiValueMap<String, Object>();
postParams.set("files", reportFilesEntity);
postParams.set("data", reportInfoEntity);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
httpHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
HttpEntity<MultiValueMap<String, Object>> requestPOST = new HttpEntity<>(postParams, httpHeaders);
最后,我提出 POST 请求:
ResponseEntity<String> response = restTemplate.exchange(PORTAL_URL, HttpMethod.POST, requestPOST, String.class);
这导致以下堆栈跟踪:
org.springframework.http.converter.HttpMessageNotWritableException: Could not write request: no suitable HttpMessageConverter found for request type [org.springframework.util.LinkedMultiValueMap]
at org.springframework.http.converter.FormHttpMessageConverter.writePart(FormHttpMessageConverter.java:532) ~[spring-web-5.2.16.RELEASE.jar:5.2.16.RELEASE]
at org.springframework.http.converter.FormHttpMessageConverter.writeParts(FormHttpMessageConverter.java:503) ~[spring-web-5.2.16.RELEASE.jar:5.2.16.RELEASE]
at org.springframework.http.converter.FormHttpMessageConverter.writeMultipart(FormHttpMessageConverter.java:483) ~[spring-web-5.2.16.RELEASE.jar:5.2.16.RELEASE]
at org.springframework.http.converter.FormHttpMessageConverter.write(FormHttpMessageConverter.java:360) ~[spring-web-5.2.16.RELEASE.jar:5.2.16.RELEASE]
at org.springframework.http.converter.FormHttpMessageConverter.write(FormHttpMessageConverter.java:156) ~[spring-web-5.2.16.RELEASE.jar:5.2.16.RELEASE]
at org.springframework.web.client.RestTemplate$HttpEntityRequestCallback.doWithRequest(RestTemplate.java:950) ~[spring-web-5.2.16.RELEASE.jar:5.2.16.RELEASE]
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:735) ~[spring-web-5.2.16.RELEASE.jar:5.2.16.RELEASE]
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:672) ~[spring-web-5.2.16.RELEASE.jar:5.2.16.RELEASE]
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:581) ~[spring-web-5.2.16.RELEASE.jar:5.2.16.RELEASE]
...
谢谢。
我解决这个问题的方法如下getMessageConverters
所示。当时的想法是 files
表单参数本身就是一种 form-based 消息(即具有 key/value 对),因此需要 FormHttpMessageConverter
.[=14 的另一个实例=]
这是对我有用的逻辑:
private List<HttpMessageConverter<?>> getMessageConverters()
{
List<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(MediaType.TEXT_HTML);
mediaTypes.add(MediaType.APPLICATION_JSON);
mediaTypes.add(MediaType.TEXT_PLAIN);
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(mediaTypes);
List<MediaType> formMediaTypes = new ArrayList<>();
formMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
formMediaTypes.add(MediaType.MULTIPART_FORM_DATA);
FormHttpMessageConverter formConverter = new FormHttpMessageConverter();
formConverter.setSupportedMediaTypes(formMediaTypes);
formConverter.addPartConverter(new MappingJackson2HttpMessageConverter());
FormHttpMessageConverter multifileConverter = new FormHttpMessageConverter();
multifileConverter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_OCTET_STREAM));
multifileConverter.addPartConverter(new ResourceHttpMessageConverter());
formConverter.addPartConverter(multifileConverter);
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
stringConverter.setSupportedMediaTypes(formMediaTypes);
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
messageConverters.add(converter);
messageConverters.add(formConverter);
messageConverters.add(stringConverter);
return messageConverters;
}