When I am posting form request, spring is adding charset like application/x-www-form-urlencoded; charset=UTF-8 which causing problem to consume restful service. How can I remove the charset from RestTemplate to so the content-type is exactly application/x-www-form-urlencoded?
FormHttpMessageConverter makes a lot of validations to make sure you are using a MediaType with a valid charset. I would try either subclassing it and registering a new converter (there are a lot of private methods though), or converting your MultiValueMap to String payload manually (StringHttpMessageConverter is a lot less restrictive about Media Types)
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<String> entity = new HttpEntity<>("param1=value1", headers);
String result = restTemplate.postForObject( url, entity, String.class);
I had the same problem. I solved it by removing the charset from the contenttype in the header after it was added.
class MyFormHttpMessageConverter extends FormHttpMessageConverter {
#Override
public void write(final MultiValueMap<String, ?> map, final MediaType contentType, final HttpOutputMessage outputMessage) throws IOException,
HttpMessageNotWritableException {
super.write(map, contentType, outputMessage);
HttpHeaders headers = outputMessage.getHeaders();
MediaType mediaType = headers.getContentType();
if(Objects.nonNull(mediaType) && MapUtils.isNotEmpty(mediaType.getParameters())){
Map<String, String> filteredParams = mediaType.getParameters()
.entrySet()
.stream()
.filter(entry -> !entry.getKey().equals("charset"))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
headers.setContentType(new MediaType(mediaType, filteredParams));
}
}
}
Related
I am new to Java (Spring Boot), and i am trying to send a multipart/form-data POST request to s3 to upload a file.
I managed to do this using spring's RestTemplate like this :
public String uploadFile(byte[] file, Map<String, Object> fields, String url) throws URISyntaxException {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
MultiValueMap<String, Object> formData= new LinkedMultiValueMap<String, Object>();
for (Map.Entry<String, Object> entry : fields.entrySet()) {
formData.add(entry.getKey(), entry.getValue());
}
formData.add("file", file);
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<MultiValueMap<String, Object>>(formData, headers);
String response = restTemplate.postForObject(new URI(url), request, String.class);
return response;
}
Then i tried to do the same using webclient, but i can not and AWS respond with The body of your POST request is not well-formed multipart/form-data.
Here is the code using webclient :
public String uploadFileWebc(byte[] file, Map<String, Object> fields, String url) {
MultipartBodyBuilder builder = new MultipartBodyBuilder();
for (Map.Entry<String, Object> entry : fields.entrySet()) {
builder.part(entry.getKey(), entry.getValue(), MediaType.TEXT_PLAIN);
}
builder.part("file", file).filename("file");
MultiValueMap<String, HttpEntity<?>> parts = builder.build();
Void result = webClient.filter(errorHandlingFilter()).build().post().uri(url)
.contentType(MediaType.MULTIPART_FORM_DATA)
.contentLength(file.length)
.bodyValue(parts)
.retrieve()
.bodyToMono(Void.class)
.block();
return "Done Uploading.";
}
Can anybody please point out what am i missing ?
It turns out that webclient does not add the content-length header due to its streaming nature, and S3 API needs this header to be sent.
I ended up using restTemplate for uploading files to S3.
I have a controller like so that accepts a MultipartFile and json object:
#PostMapping(value = "/v1/submit")
public ResponseEntity submit(
#RequestParam(value="myFile", required = true) MultipartFile myFile
, #Valid #RequestPart(value="fileMeta", required=true) FileMeta fileMeta
){
I need to forward this to a new url using an okhttpclient post with a Multipartbody containing both myFile and fileMeta objects:
OkHttpClient client = new OkHttpClient();
MultipartBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("myFile", myFile.getName(), okhttp3.RequestBody.create(file, MediaType.parse("pdf"))
.addFormDataPart("fileMeta", fileMeta)
.build();
I am getting following error:
Cannot resolve method 'create(org.springframework.web.multipart.MultipartFile, okhttp3.MediaType)'
The method definition of OkHttp's RequestBody create is the following: create(MediaType contentType, byte[] content). It expects the first the MediaType and second the payload (either as byte[], File or other formats).
So you first have to switch the order of the method arguments and second convert the MultipartFile from Spring to a proper format that the create() method accepts, e.g. byte[] or File:
OkHttpClient client = new OkHttpClient();
MultipartBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("myFile", myFile.getName(), RequestBody.create(MediaType.parse("pdf"), file)
.addFormDataPart("fileMeta", fileMeta)
.build();
There are already multiple solutions available on StackOverflow to convert MultipartFile to File: How to convert a multipart file to File?
UPDATE: Example for using RestTemplate
#RestController
public class FileSendingController {
#PostMapping("/files")
public void streamFile(#RequestParam("file") MultipartFile file) {
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("file", file);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
RestTemplate restTemplate = new RestTemplate();
restTemplate.postForEntity("http://upload.to", requestEntity, String.class);
}
}
I want to make a multipart request to some external API (created using Spring Boot) but all I get is Required request part 'file' is not present.
I know the source code of the external API but I can't modify it. It looks like this:
#PostMapping("/upload")
public ResponseEntity handleFileUpload(#RequestParam("file") MultipartFile file){
return ResponseEntity.ok().build();
}
And from my application I create and send requests exactly like on the following snippet:
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
MultiValueMap<String, Object> body
= new LinkedMultiValueMap<>();
body.add("file", "dupa".getBytes());
HttpEntity<MultiValueMap<String, Object>> requestEntity
= new HttpEntity<>(body, headers);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate
.postForEntity("http://api:8080/upload", requestEntity, String.class);
return response.getBody();
What's the reason it doesn't work? The above code rewritten using Apache HttpClient works like charm.
You basically have two options, the solution with byte array:
map.add("file", new ByteArrayResource(byteArrayContent) {
#Override
public String getFilename() {
return "yourFilename";
}
});
I remember having a problem with just adding a byte array, so you need to have a filename too and use ByteArrayResource.
Or adding a File:
map.add("file", new FileSystemResource(file));
I want to write a REST service which does responed with a zipFile and some json data, everything in one multipart/mixed request.
The server part works fine and i am testing it with the REST Client from firefox. My Server sends a multipart like this
--k-dXaXvCFusLVXUsg-ryiHMmkdttadgcBqi4XH
Content-Disposition: form-data; name="form"
Content-type: application/json
{"projectName":"test","signal":"true"}
--k-dXaXvCFusLVXUsg-ryiHMmkdttadgcBqi4XH
Content-Disposition: form-data; name="file2"; filename="file2.txt"
Content-type: application/octet-stream
Content-Length: 10
hallo=Welt
I know that RestTemplate can send multiparts with the help of a MultiValueMap out of the box.
Now I tried to consume multipart/mixed responses and return a MultiValueMap
#Component
public class RestCommand
extends AbstractLoginRestCommand<Form, MultiValueMap<String, Object>>
{
#Override
protected MultiValueMap<String, Object> executeInternal ( Form form )
{
RestTemplate restTemplate = getRestTemplate();
MyMultiValueMap map = restTemplate.postForObject(getUrl(), form, MyMultiValueMap.class);
return new LinkedMultiValueMap<String, Object>(map);
}
}
class MyMultiValueMap extends LinkedMultiValueMap<String, Object>
{}
MyMultiValueMap exist to prevent type erasure (generics).
This gives
org.springframework.web.client.RestClientException: Could not extract
response: no suitable HttpMessageConverter found for response type
[class org.jlot.client.remote.MyMultiValueMap] and content type
[multipart/form-data;boundary=Rjh-fkdsI9OIyPpYwdFY7lsUIewhRSX8kE19I;charset=UTF-8]
at
org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:107)
at
org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:492)
Javadoc of FormHttpMessageConverter says it can write but not read multipart/form-data.
Why is it like this?
Is there a way to read multipart/form-data with RestTemplate out-of-the-box or do I need to write a HttpMessageConverter?
I had the same issue and I think I achieved what you wanted.
You just have to override the canRead method of the form converter. With your example something like below should work.
FormHttpMessageConverter formConverter = new FormHttpMessageConverter() {
#Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
if (clazz == MyMultiValueMap.class) {
return true;
}
return super.canRead(clazz, mediaType);
}
};
And add this converter to your rest template.
I use this solution at the moment:
#ResponseBody
#PostMapping(value = JlotApiUrls.PUSH, produces = "application/json")
public List<PushResultDTO> push (
#PathVariable String projectName,
#PathVariable String versionName,
#RequestPart("file") MultipartFile multipartFile,
#RequestPart("data") #Valid PushForm pushForm
) throws IOException, BindException
{
...
}
https://github.com/kicktipp/jlot/blob/master/jlot-web/src/main/java/org/jlot/web/api/controller/PushController.java
My post method gets called but my Profile is empty. What is wrong with this approach? Must I use #Requestbody to use the RestTemplate?
Profile profile = new Profile();
profile.setEmail(email);
String response = restTemplate.postForObject("http://localhost:8080/user/", profile, String.class);
#RequestMapping(value = "/", method = RequestMethod.POST)
public #ResponseBody
Object postUser(#Valid Profile profile, BindingResult bindingResult, HttpServletResponse response) {
//Profile is null
return profile;
}
You have to build the profile object this way
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<String, Object>();
parts.add("email", email);
Object response = restTemplate.postForObject("http://localhost:8080/user/", parts, String.class);
MultiValueMap was good starting point for me but in my case it still posted empty object to #RestController my solution for entity creation and posting ended up looking like so:
HashedMap requestBody = new HashedMap();
requestBody.put("eventType", "testDeliveryEvent");
requestBody.put("sendType", "SINGLE");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
// Jackson ObjectMapper to convert requestBody to JSON
String json = new ObjectMapper().writeValueAsString(request);
HttpEntity<String> entity = new HttpEntity<>(json, headers);
restTemplate.postForEntity("/generate", entity, String.class);
My current approach:
final Person person = Person.builder().name("antonio").build();
final ResponseEntity response = restTemplate.postForEntity(
new URL("http://localhost:" + port + "/person/aggregate").toString(),
person, Person.class);