Spring RestTemplate: post 同时是图像和对象
Spring RestTemplate: post both an image and an object at the same time
我的用户可以 post 食物的照片和 post 食物即将发送到我的服务器的照片。
比如说,有人看到好吃的东西,拍了张照片,然后在照片下面写上"Tasty!"。照片被发送到服务器,包含用户名、日期、位置等的消息 "Tasty!" 通过一个 api 调用在一个名为 "Post" 的对象中发送到我的服务器。
我在android这边写了下面的代码:
final String url = Constants.POST_PICS;
RestTemplate restTemplate = RestClientConfig.getRestTemplate(context, true);
//adding StringHttpMessageConverter, formHttpMessageConverter and MappingJackson2HttpMessageConverter to restTemplate
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
FormHttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter();
restTemplate.getMessageConverters().add(formHttpMessageConverter);
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
//putting both objects into a map
MultiValueMap<String, Object> map = new LinkedMultiValueMap<String, Object>();
map.add("image", new FileSystemResource(file));
map.add("post", post);
HttpHeaders imageHeaders = new HttpHeaders();
//setting content type to multipart as the image is a multipart file
imageHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> imageEntity = new HttpEntity<MultiValueMap<String, Object>>(map, imageHeaders);
ResponseEntity<Post> response = restTemplate.exchange(url, HttpMethod.POST, imageEntity, Post.class);
return response.getBody();
这是Spring端的代码:
@RequestMapping(value = "/uploadpostpic", method = RequestMethod.POST)
public Post uploadPostWithPic(@RequestParam("image") MultipartFile srcFile,
@RequestParam("post") Post post) {
return serviceGateway.uploadPostWithPic(srcFile, post);
}
我收到一个错误:
An exception occurred during request network execution :Could not
write request: no suitable HttpMessageConverter found for request type
[Model.Post]
org.springframework.http.converter.HttpMessageNotWritableException:
Could not write request: no suitable HttpMessageConverter found for
request type [Model.Post]
我怀疑这与内容类型设置为 MULTIPART_FORM_DATA 有关,但我需要将其设置为此,因为我需要将图片传输到服务器。
是否可以同时使用 restTemplate 向上游传输一个多部分文件和另一个对象?
编辑:
我看过这些 post:
Sending Multipart File as POST parameters with RestTemplate requests
并根据他们的指导尝试了这段代码:
final String url = Constants.POST_PIC;
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
restTemplate.getMessageConverters().add(new ByteArrayHttpMessageConverter());
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
restTemplate.getMessageConverters().add(new ResourceHttpMessageConverter());
FormHttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter();
formHttpMessageConverter.addPartConverter(new MappingJackson2HttpMessageConverter());
formHttpMessageConverter.addPartConverter(new ResourceHttpMessageConverter()); // This is hope driven programming
formHttpMessageConverter.addPartConverter(new ByteArrayHttpMessageConverter());
restTemplate.getMessageConverters().add(formHttpMessageConverter);
MultiValueMap<String, Object> multipartRequest = new LinkedMultiValueMap<>();
byte[] bFile = new byte[(int) imageFile.length()];
FileInputStream fileInputStream;
//convert file into array of bytes
fileInputStream = new FileInputStream(imageFile);
fileInputStream.read(bFile);
fileInputStream.close();
ByteArrayResource bytes = new ByteArrayResource(bFile) {
@Override
public String getFilename() {
return "file.jpg";
}
};
//post portion of the multipartRequest
HttpHeaders xHeader = new HttpHeaders();
xHeader.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Post> xPart = new HttpEntity<>(post, xHeader);
multipartRequest.add("post", xPart);
//picture portion of the multipartRequest
HttpHeaders pictureHeader = new HttpHeaders();
pictureHeader.setContentType(MediaType.IMAGE_JPEG);
HttpEntity<ByteArrayResource> picturePart = new HttpEntity<>(bytes, pictureHeader);
multipartRequest.add("srcFile", picturePart);
//adding both the post and picture portion to one httpentity for transmitting to server
HttpHeaders header = new HttpHeaders();
header.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity(multipartRequest, header);
return restTemplate.postForObject(url, requestEntity, Post.class);
另一方面,post = null,我不确定为什么它是 null。
这就是我在服务器端尝试做的所有事情:
public Post uploadPostPic(MultipartFile srcFile, Post post) {
Post savedPost = repo.save(post);
}
我正在将它保存到我的存储库中,错误是:
java.lang.IllegalArgumentException: Entity must not be null!
我没有看到您的 Post class 的已注册 HttpMessageConverter。您可能必须为 MultiValueMap 注册一个 HttpMessageConverter。
好吧,几周前我遇到了同样的问题。首先要明确multipart/form-data
content-type是什么意思:
A "multipart/form-data" message contains a series of parts, each
representing a successful control.
A successful control is "valid" for submission. Every successful
control has its control name paired with its current value as part of
the submitted form data set
简单来说,使用 multipart form-data 您可以向服务器发送不同的 content-type 数据。这是一个示例:
POST / HTTP/1.1
Host: localhost:8000
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:29.0) Gecko/20100101 Firefox/29.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Cookie: __atuvc=34%7C7; permanent=0; _gitlab_session=226ad8a0be43681acf38c2fab9497240; __profilin=p%3Dt; request_method=GET
Connection: keep-alive
Content-Type: multipart/form-data; boundary=---------------------------9051914041544843365972754266
Content-Length: 554
-----------------------------9051914041544843365972754266
Content-Disposition: form-data; name="text"
text default
-----------------------------9051914041544843365972754266
Content-Disposition: form-data; name="file1"; filename="a.txt"
Content-Type: text/plain
Content of a.txt.
-----------------------------9051914041544843365972754266
Content-Disposition: form-data; name="file2"; filename="a.html"
Content-Type: text/html
<!DOCTYPE html><title>Content of a.html.</title>
-----------------------------9051914041544843365972754266--
这里是发送三个不同集合的示例 - 第一个是 binary data
,第二个是 plain text
,第三个是 html
,它们由边界分隔。
现在工作得如何 Spring's RestTemplate
?
当您将请求 header 设置为 multipart/form-data
时,resttemplate 将从已注册的消息转换器中获取适当的 HttpMessageConverter
,multipart/form-data
将是 FormHttpMessageConverter
请参阅文档 here。
但是 FormHttpMessageConverter
有 partConverters
的 属性,它们是为 FormHttpMessageConverter
注册的转换器,默认情况下它们是(字符串、字节数组和资源)。这是构造函数的源代码 ;)
public FormHttpMessageConverter() {
this.supportedMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
this.supportedMediaTypes.add(MediaType.MULTIPART_FORM_DATA);
this.partConverters.add(new ByteArrayHttpMessageConverter());
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setWriteAcceptCharset(false);
this.partConverters.add(stringHttpMessageConverter);
this.partConverters.add(new ResourceHttpMessageConverter());
}
简单地说,FormHttpMessageConverter
找不到正确的消息转换器来编写 object Post
。如果你想 Post
写成 JSON 你应该把 MappingJackson2HttpMessageConverter
添加到 partConverters
.
@Produces
public RestTemplate getRestTemplate() {
RestTemplate template = new RestTemplate();
template.getMessageConverters().add(0,createFormHttpConverter());
return template;
}
private static HttpMessageConverter<?> createFormHttpConverter(){
FormHttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter();
formHttpMessageConverter.setPartConverters(getPartConverters());
return formHttpMessageConverter;
}
private static List<HttpMessageConverter<?>> getPartConverters(){
RestTemplate template = new RestTemplate();
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
List<HttpMessageConverter<?>> messageConverters = template.getMessageConverters();
messageConverters.add(0,converter);
return messageConverters;
}
您需要告诉 Spring 如何将请求参数映射到您的对象。您可以像 Alexander 建议的那样通过实现自定义 HttpMessageConterter
来做到这一点,但我有一个更简单的方法:使用命令对象(有时称为表单支持对象):
public class PostWithPicCommand() {
public PostWithPic() {}; //Default constructor is required
//name the variables like the request parameters!
private Post post;
private MultipartFile image;
Getter and Setter!
}
@RequestMapping(value = "/uploadpostpic", method = RequestMethod.POST)
public Post uploadPostWithPic(PostWithPicCommand postWithPicCommand
/*no @Param attribte for postWithPicCommand*/) {
....
}
并且您需要configure/register spring Multipart Resolver,并且需要将请求作为多部分请求发送。
我用这个做了类似的东西:
HttpHeaders headers = new HttpHeaders();
headers.add("Accept","application/json");
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
MultiValueMap<String, Object> map = new LinkedMultiValueMap<String, Object>();
map.add("image", new FileSystemResource(file));
map.add("post", post);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<MultiValueMap<String, Object>>(map, headers);
RestTemplate restTemplate = RestClientConfig.getRestTemplate(context, true);
restTemplate.getMessageConverters().add(new FormHttpMessageConverter());
ResponseEntity<Post> response = restTemplate.postForObject(url, requestEntity, Post.class);
尝试这样的事情:
在此处发送 jsonString,稍后使用 objectwriter.Let 将其转换为对象 我知道您是否需要更多解释。
@RequestMapping(value = "/uploadMultipleFile", method = RequestMethod.POST)
public @ResponseBody
String uploadMultipleFileHandler(@RequestParam("name") String[] names,
@RequestParam("file") MultipartFile[] files) {
if (files.length != names.length)
return "Mandatory information missing";
String message = "";
for (int i = 0; i < files.length; i++) {
MultipartFile file = files[i];
String name = names[i];
try {
byte[] bytes = file.getBytes();
// Creating the directory to store file
String rootPath = System.getProperty("catalina.home");
File dir = new File(rootPath + File.separator + "tmpFiles");
if (!dir.exists())
dir.mkdirs();
// Create the file on server
File serverFile = new File(dir.getAbsolutePath()
+ File.separator + name);
BufferedOutputStream stream = new BufferedOutputStream(
new FileOutputStream(serverFile));
stream.write(bytes);
stream.close();
logger.info("Server File Location="
+ serverFile.getAbsolutePath());
message = message + "You successfully uploaded file=" + name
+ "<br />";
} catch (Exception e) {
return "You failed to upload " + name + " => " + e.getMessage();
}
}
return message;
}
}
已编辑:
最终,我不得不使用 jsonString 来解决我的问题。这并不理想,因为 url 最终会变得很长,但这是解决我的问题的最快方法:
请查看 mykong 关于如何将对象转换为 jsonString 并将它们重新转换回对象的建议:
ObjectMapper mapper = new ObjectMapper();
Staff obj = new Staff();
//Object to JSON in String
String jsonInString = mapper.writeValueAsString(obj);
//JSON from String to Object
Staff obj = mapper.readValue(jsonInString, Staff.class);
http://www.mkyong.com/java/jackson-2-convert-java-object-to-from-json/
我的用户可以 post 食物的照片和 post 食物即将发送到我的服务器的照片。
比如说,有人看到好吃的东西,拍了张照片,然后在照片下面写上"Tasty!"。照片被发送到服务器,包含用户名、日期、位置等的消息 "Tasty!" 通过一个 api 调用在一个名为 "Post" 的对象中发送到我的服务器。
我在android这边写了下面的代码:
final String url = Constants.POST_PICS;
RestTemplate restTemplate = RestClientConfig.getRestTemplate(context, true);
//adding StringHttpMessageConverter, formHttpMessageConverter and MappingJackson2HttpMessageConverter to restTemplate
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
FormHttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter();
restTemplate.getMessageConverters().add(formHttpMessageConverter);
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
//putting both objects into a map
MultiValueMap<String, Object> map = new LinkedMultiValueMap<String, Object>();
map.add("image", new FileSystemResource(file));
map.add("post", post);
HttpHeaders imageHeaders = new HttpHeaders();
//setting content type to multipart as the image is a multipart file
imageHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> imageEntity = new HttpEntity<MultiValueMap<String, Object>>(map, imageHeaders);
ResponseEntity<Post> response = restTemplate.exchange(url, HttpMethod.POST, imageEntity, Post.class);
return response.getBody();
这是Spring端的代码:
@RequestMapping(value = "/uploadpostpic", method = RequestMethod.POST)
public Post uploadPostWithPic(@RequestParam("image") MultipartFile srcFile,
@RequestParam("post") Post post) {
return serviceGateway.uploadPostWithPic(srcFile, post);
}
我收到一个错误:
An exception occurred during request network execution :Could not write request: no suitable HttpMessageConverter found for request type [Model.Post]
org.springframework.http.converter.HttpMessageNotWritableException: Could not write request: no suitable HttpMessageConverter found for request type [Model.Post]
我怀疑这与内容类型设置为 MULTIPART_FORM_DATA 有关,但我需要将其设置为此,因为我需要将图片传输到服务器。
是否可以同时使用 restTemplate 向上游传输一个多部分文件和另一个对象?
编辑:
我看过这些 post:
Sending Multipart File as POST parameters with RestTemplate requests
并根据他们的指导尝试了这段代码:
final String url = Constants.POST_PIC;
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
restTemplate.getMessageConverters().add(new ByteArrayHttpMessageConverter());
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
restTemplate.getMessageConverters().add(new ResourceHttpMessageConverter());
FormHttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter();
formHttpMessageConverter.addPartConverter(new MappingJackson2HttpMessageConverter());
formHttpMessageConverter.addPartConverter(new ResourceHttpMessageConverter()); // This is hope driven programming
formHttpMessageConverter.addPartConverter(new ByteArrayHttpMessageConverter());
restTemplate.getMessageConverters().add(formHttpMessageConverter);
MultiValueMap<String, Object> multipartRequest = new LinkedMultiValueMap<>();
byte[] bFile = new byte[(int) imageFile.length()];
FileInputStream fileInputStream;
//convert file into array of bytes
fileInputStream = new FileInputStream(imageFile);
fileInputStream.read(bFile);
fileInputStream.close();
ByteArrayResource bytes = new ByteArrayResource(bFile) {
@Override
public String getFilename() {
return "file.jpg";
}
};
//post portion of the multipartRequest
HttpHeaders xHeader = new HttpHeaders();
xHeader.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Post> xPart = new HttpEntity<>(post, xHeader);
multipartRequest.add("post", xPart);
//picture portion of the multipartRequest
HttpHeaders pictureHeader = new HttpHeaders();
pictureHeader.setContentType(MediaType.IMAGE_JPEG);
HttpEntity<ByteArrayResource> picturePart = new HttpEntity<>(bytes, pictureHeader);
multipartRequest.add("srcFile", picturePart);
//adding both the post and picture portion to one httpentity for transmitting to server
HttpHeaders header = new HttpHeaders();
header.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity(multipartRequest, header);
return restTemplate.postForObject(url, requestEntity, Post.class);
另一方面,post = null,我不确定为什么它是 null。
这就是我在服务器端尝试做的所有事情:
public Post uploadPostPic(MultipartFile srcFile, Post post) {
Post savedPost = repo.save(post);
}
我正在将它保存到我的存储库中,错误是:
java.lang.IllegalArgumentException: Entity must not be null!
我没有看到您的 Post class 的已注册 HttpMessageConverter。您可能必须为 MultiValueMap 注册一个 HttpMessageConverter。
好吧,几周前我遇到了同样的问题。首先要明确multipart/form-data
content-type是什么意思:
A "multipart/form-data" message contains a series of parts, each representing a successful control.
A successful control is "valid" for submission. Every successful control has its control name paired with its current value as part of the submitted form data set
简单来说,使用 multipart form-data 您可以向服务器发送不同的 content-type 数据。这是一个示例:
POST / HTTP/1.1
Host: localhost:8000
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:29.0) Gecko/20100101 Firefox/29.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Cookie: __atuvc=34%7C7; permanent=0; _gitlab_session=226ad8a0be43681acf38c2fab9497240; __profilin=p%3Dt; request_method=GET
Connection: keep-alive
Content-Type: multipart/form-data; boundary=---------------------------9051914041544843365972754266
Content-Length: 554
-----------------------------9051914041544843365972754266
Content-Disposition: form-data; name="text"
text default
-----------------------------9051914041544843365972754266
Content-Disposition: form-data; name="file1"; filename="a.txt"
Content-Type: text/plain
Content of a.txt.
-----------------------------9051914041544843365972754266
Content-Disposition: form-data; name="file2"; filename="a.html"
Content-Type: text/html
<!DOCTYPE html><title>Content of a.html.</title>
-----------------------------9051914041544843365972754266--
这里是发送三个不同集合的示例 - 第一个是 binary data
,第二个是 plain text
,第三个是 html
,它们由边界分隔。
现在工作得如何 Spring's RestTemplate
?
当您将请求 header 设置为 multipart/form-data
时,resttemplate 将从已注册的消息转换器中获取适当的 HttpMessageConverter
,multipart/form-data
将是 FormHttpMessageConverter
请参阅文档 here。
但是 FormHttpMessageConverter
有 partConverters
的 属性,它们是为 FormHttpMessageConverter
注册的转换器,默认情况下它们是(字符串、字节数组和资源)。这是构造函数的源代码 ;)
public FormHttpMessageConverter() {
this.supportedMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
this.supportedMediaTypes.add(MediaType.MULTIPART_FORM_DATA);
this.partConverters.add(new ByteArrayHttpMessageConverter());
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setWriteAcceptCharset(false);
this.partConverters.add(stringHttpMessageConverter);
this.partConverters.add(new ResourceHttpMessageConverter());
}
简单地说,FormHttpMessageConverter
找不到正确的消息转换器来编写 object Post
。如果你想 Post
写成 JSON 你应该把 MappingJackson2HttpMessageConverter
添加到 partConverters
.
@Produces
public RestTemplate getRestTemplate() {
RestTemplate template = new RestTemplate();
template.getMessageConverters().add(0,createFormHttpConverter());
return template;
}
private static HttpMessageConverter<?> createFormHttpConverter(){
FormHttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter();
formHttpMessageConverter.setPartConverters(getPartConverters());
return formHttpMessageConverter;
}
private static List<HttpMessageConverter<?>> getPartConverters(){
RestTemplate template = new RestTemplate();
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
List<HttpMessageConverter<?>> messageConverters = template.getMessageConverters();
messageConverters.add(0,converter);
return messageConverters;
}
您需要告诉 Spring 如何将请求参数映射到您的对象。您可以像 Alexander 建议的那样通过实现自定义 HttpMessageConterter
来做到这一点,但我有一个更简单的方法:使用命令对象(有时称为表单支持对象):
public class PostWithPicCommand() {
public PostWithPic() {}; //Default constructor is required
//name the variables like the request parameters!
private Post post;
private MultipartFile image;
Getter and Setter!
}
@RequestMapping(value = "/uploadpostpic", method = RequestMethod.POST)
public Post uploadPostWithPic(PostWithPicCommand postWithPicCommand
/*no @Param attribte for postWithPicCommand*/) {
....
}
并且您需要configure/register spring Multipart Resolver,并且需要将请求作为多部分请求发送。
我用这个做了类似的东西:
HttpHeaders headers = new HttpHeaders();
headers.add("Accept","application/json");
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
MultiValueMap<String, Object> map = new LinkedMultiValueMap<String, Object>();
map.add("image", new FileSystemResource(file));
map.add("post", post);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<MultiValueMap<String, Object>>(map, headers);
RestTemplate restTemplate = RestClientConfig.getRestTemplate(context, true);
restTemplate.getMessageConverters().add(new FormHttpMessageConverter());
ResponseEntity<Post> response = restTemplate.postForObject(url, requestEntity, Post.class);
尝试这样的事情: 在此处发送 jsonString,稍后使用 objectwriter.Let 将其转换为对象 我知道您是否需要更多解释。
@RequestMapping(value = "/uploadMultipleFile", method = RequestMethod.POST)
public @ResponseBody
String uploadMultipleFileHandler(@RequestParam("name") String[] names,
@RequestParam("file") MultipartFile[] files) {
if (files.length != names.length)
return "Mandatory information missing";
String message = "";
for (int i = 0; i < files.length; i++) {
MultipartFile file = files[i];
String name = names[i];
try {
byte[] bytes = file.getBytes();
// Creating the directory to store file
String rootPath = System.getProperty("catalina.home");
File dir = new File(rootPath + File.separator + "tmpFiles");
if (!dir.exists())
dir.mkdirs();
// Create the file on server
File serverFile = new File(dir.getAbsolutePath()
+ File.separator + name);
BufferedOutputStream stream = new BufferedOutputStream(
new FileOutputStream(serverFile));
stream.write(bytes);
stream.close();
logger.info("Server File Location="
+ serverFile.getAbsolutePath());
message = message + "You successfully uploaded file=" + name
+ "<br />";
} catch (Exception e) {
return "You failed to upload " + name + " => " + e.getMessage();
}
}
return message;
}
}
已编辑:
最终,我不得不使用 jsonString 来解决我的问题。这并不理想,因为 url 最终会变得很长,但这是解决我的问题的最快方法:
请查看 mykong 关于如何将对象转换为 jsonString 并将它们重新转换回对象的建议:
ObjectMapper mapper = new ObjectMapper();
Staff obj = new Staff();
//Object to JSON in String
String jsonInString = mapper.writeValueAsString(obj);
//JSON from String to Object
Staff obj = mapper.readValue(jsonInString, Staff.class);
http://www.mkyong.com/java/jackson-2-convert-java-object-to-from-json/