Spring RestTemplate POST upload multiple files - spring

Let's assume I have an endpoint looking like the one below:
#PostMapping(
value = "/something",
consumes = MULTIPART_FORM_DATA_VALUE,
produces = APPLICATION_JSON_VALUE)
public SomeDTO post2Files(
#RequestPart("file1") MultipartFile file1,
#RequestPart("file2") MultipartFile file2 {
In another service I want to read one file from the file system and just resend it, while the file2 is actually a string that I wanna pass as a file through RestTemplate.
I tried something like this:
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("file1", new FileSystemResource(somePath));
body.add("file2", new ByteArrayResource(someString.getBytes()));
restTemplate.postForObject("/something", new HttpEntity<>(body, headers), SomeDTO.class)
It doesn't work and I have no clue why. I get 400. What should I do to make the request pass through?

Figured it out.
This is the solution:
body.add("dataSchema", new ByteArrayResource(someString.getBytes()) {
#Override
public String getFilename() {
return "file2";
}
});
It didn't work because the filename did not match with the #RequestPart.

Related

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

POST byte array in multipart using Spring RestTemplate

I'm trying to POST a multipart/form-data using Spring RestTemplate with a byte array as the file to upload and it keeps failing (Server rejects with different kinds of errors).
I'm using a MultiValueMap with ByteArrayResource. Is there something I'm missing?
Yes there is something missing.
I have found this article:
https://medium.com/#voziv/posting-a-byte-array-instead-of-a-file-using-spring-s-resttemplate-56268b45140b
The author mentions that in order to POST a byte array using Spring RestTemplate one needs to override getFileName() of the ByteArrayResource.
Here is the code example from the article:
private static void uploadWordDocument(byte[] fileContents, final String filename) {
RestTemplate restTemplate = new RestTemplate();
String fooResourceUrl = "http://localhost:8080/spring-rest/foos"; // Dummy URL.
MultiValueMap<String, Object> map = new LinkedMultiValueMap<String, Object>();
map.add("name", filename);
map.add("filename", filename);
// Here we
ByteArrayResource contentsAsResource = new ByteArrayResource(fileContents) {
#Override
public String getFilename() {
return filename; // Filename has to be returned in order to be able to post.
}
};
map.add("file", contentsAsResource);
// Now you can send your file along.
String result = restTemplate.postForObject(fooResourceUrl, map, String.class);
// Proceed as normal with your results.
}
I tried it and it works!
I added an issue to send a request from java client to Python service in FastApi and sending a ByteArrayResource instaead of simple byte[] fixed the issue.
FastAPI server returned: "Expected UploadFile, received: <class 'str'>","type":"value_error""

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

Angular 4 and Spring Rest: How to post FormData containing File and model object in a single request

I would like to send a File object along with custom model object in a single request.
let formData:FormData = new FormData();
let file = this.fileList[0];
formData.append('file', file, file.name);
formData.append('address', JSON.stringify(customObj));
...
this.http.post(fileServeUrl, formData)
My backend is in Spring Rest as below
#RequestMapping(value = "/fileServe",
produces = {"application/json"},
consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE},
method = RequestMethod.POST)
ResponseEntity<Image> uploadFile(#RequestPart("file") MultipartFile imageData, #RequestPart("address") Address address) throws IOException {...}
I was able to receive the data if I pass simple String along with File though.
formData.append('file', file, file.name);
formData.append('address', addressText);
Backend
#RequestMapping(value = "/fileServe",
produces = {"application/json"},
consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE},
method = RequestMethod.POST)
ResponseEntity<Image> uploadFile(#RequestPart("file") MultipartFile imageData, #RequestPart("address") String addressText) throws IOException {...}
I tried #RequestBody for my custom object but even that didn't work. Any advise please.
The problem with #Requestbody and #RequestPart annotation is that spring use the HttpMessageConverter to take convert the incoming json message into the your object. As you send form data with a file and a text value spring can not convert it into your object. I am afraid you have to pass the value of address seperatetly.
#RequestMapping(value = "/fileupload", headers = ("content-type=multipart/*"), method = RequestMethod.POST)
public ResponseEntity<AjaxResponseBody> upload(#RequestParam("file") MultipartFile file, #RequestParam String name, #RequestParam String postCode) {
AjaxResponseBody result = new AjaxResponseBody();
HttpHeaders headers = new HttpHeaders();
if (!file.isEmpty()) {
try {
Address address = new Address();
address.setName(name);
result.setMsg("ok");
return new ResponseEntity<AjaxResponseBody>(result, headers, HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<AjaxResponseBody>(HttpStatus.BAD_REQUEST);
}
} else {
return new ResponseEntity<AjaxResponseBody>(HttpStatus.BAD_REQUEST);
}
}
Expept if you find a way your client app send a file with MimeType of image/jpg and and an address of application/json which allow spring to parse the json and map to your Address object which i couldn't do it.

Encoding for downloaded files in Spring

I want to create a controller which will sent to client a CSV file, and I created the next controller:
#ResponseStatus(value = HttpStatus.OK)
#RequestMapping(value = "/csv", method = RequestMethod.GET)
public ResponseEntity downloadCsvAllInvoiceTransactionsByFilter(
#PageableDefault(direction = DESC, sort = "invoiceDate", size = 30) Pageable pageRequest) throws IOException {
String someVariable = "Some text";
byte[] out = someVariable.getBytes(Charset.forName("UTF-8"));
HttpHeaders responseHeaders = new HttpHeaders();
LOGGER.info(new String(out));
responseHeaders.add("content-disposition", "attachment; filename=transactions.csv" );
responseHeaders.add("Content-Type","text/csv; charset=utf-8");
return new ResponseEntity<>(out,responseHeaders,HttpStatus.OK);
}
Logger is displaying the correct string:
Some text
but in downloaded file there is another one
U29tZSB0ZXh0
How can I fix this?
Body of ResponseEntity goes through a message converter before it gets sent. The choice of the particular converter depends on class of the body and response and request headers.
I tried to reproduce the issue with your code snippet and got expected text in csv file. So I assume that you got a message converter registered that converts byte arrays the way you observe.
You can debug AbstractMessageConverterMethodProcessor#writeWithMessageConverters and see which converter is chosen and why.

Resources