Spring Webclient multipart/form-data request - spring-boot

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.

Related

When trying to invoke rest API throws httpmediatypenotsupportedexception content type 'application/x-www-form-urlencoded' not supported

I am trying to invoke rest API below which consumes a multi-part file. First paramter is a MultipartFile and second is s String. and this functions processes some business logic
#PostMapping( value="/uploadFile", consumes = MediaType.MULTIPART_FORM_DATE_VALUE)
public ResponseEntity<String> upload(#RequestPart("file") MultipartFile file,
#RequestParam("path") String path){
//businness logic
}
Invoking above API from code below. But it throws
httpmediatypenotsupportedexception content type
'application/x-www-form-urlencoded' not supported. I have also tried
added header "Content-type", MediaType.MULTIPART_FORM_DATA_VALUE OR
"Content-Type", "multipart/form-data" OR "Accept",
"multipart/form-data" in the headers below, but that has not helped
either
public void uploadFile() {
Path path = Paths.get("C:/ABC.txt");
byte[] content = null;
try{
content = Files.readAllBytes(path); // All file is read in content variable
} catch(final IOException e){
}
MultipartFile file = new MockMultipartFile("ABC.txt",content);
UriComponentsBuilder urlBuilder = UriComponentsBuilder.fromHttpUrl(oauthURL);
urlBuilder.queryParam("file", file);
urlBuilder.queryParam("path", "/temp);
HttpHeaders headers = new HttpHeaders();
HttpEntity<String> response = null;
HttpEntity<?> entity = new HttpEntity<>(headers);
try{
response = restTemplate.exchange(urlBuilder.build().encode().toUri(), HttpMethod.POST, entity. String.class);
}
catch (Exception e){
}
}
}
Your server accepts (consumes) "multipart/form-data" however you are sending the file and path in the URL. This will always result in a "application/x-www-form-urlencoded".
So you need to change the server to accept it as you send them or send the file and path as the body (within the entity)
EDIT (some code to show how):
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("file", file);
body.add("path","/temp");
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
response = restTemplate.postForEntity(serverUrl, requestEntity, String.class);

How to add file as Form data in Http request in spring boot

I'm writing test cases to a controller in spring boot which takes MultipartFile as RequestParam. In the test case method I'm using TestRestTemplate.exchange() to send the request to the controller. I'm not sure how to make the Headers correctly so that I can send the request.
The Postman curl looks like this:
curl --location --request POST 'localhost:9091/response/upload'
--form 'file=#"/home/adityak/Downloads/ClientLog_NEW.txt"'
For file uploading to any service or endpoint
private String testExchange(File file) {
//add file
LinkedMultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
params.add("file", new FileSystemResource(file));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<LinkedMultiValueMap<String, Object>> requestEntity =
new HttpEntity<>(params, headers);
ResponseEntity<String> responseEntity = restTemplate.exchange(
"/upload-file",
HttpMethod.POST,
requestEntity,
String.class);
HttpStatus statusCode = responseEntity.getStatusCode();
if (statusCode == HttpStatus.ACCEPTED) {
result = responseEntity.getBody();
}
return result;
}

Okhttp create MultipartBody with spring MultipartFile and json object

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);
}
}

Sending a multipart request using RestTemplate

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));

How to send body content as raw JSON and not form-data in Spring Boot RestTemplate

I have a spring boot application and trying to invoke a rest service of another company by using RestTemplate.
The remote Rest Service required multiple header and body content as Raw JSON.
Here is the sample required body request :
{
"amount": "10000",
"destinationNumber": "365412"
}
But my request body generate like this :
{
amount= [10000],
destinationNumber= [365412]
}
I've done like this :
String BASE_URI = "http://server.com/sericeX";
RestTemplate template = new RestTemplate();
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
headers.add("Authorization","Some token");
headers.add("Content-Type", "application/json");
MultiValueMap<String, String> bodyParam = new LinkedMultiValueMap<>();
bodyParam.add("amount", request.getAmount());
bodyParam.add("destinationNumber",request.getDestinationNumber());
HttpEntity entity = new HttpEntity(bodyParam,headers);
ResponseEntity<TransferEntity> responseEntity = template.exchange(BASE_URI, HttpMethod.POST, entity,TransferEntity.class);
TransferEntity transferEntity = responseEntity.getBody();
Could you please tell me how can i generate body request as JSON ?
Thanks to #Alex Salauyou based on his comment using HashMap instead of MultiValueMap solved the problem. Here is the changes need to be done:
HashMap<String, String> bodyParam = new HashMap<>();
bodyParam.put("amount", request.getAmount());
bodyParam.put("destinationNumber",request.getDestinationNumber());

Resources