Encoding for downloaded files in Spring - 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.

Related

Spring RestTemplate POST upload multiple files

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.

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""

Spring Controller return static HTML site from any directory

I would like to return in my #Controller static HTML website that was generated by other process. Let's say that generated .html files are in /tmp/generated. I'm trying to read file and pass its content to ResponseEntity:
#GetMapping(value = "test")
ResponseEntity<String> test(#RequestParam("filename") String filename) throws IOException {
String content = new String(Files.readAllBytes(Paths.get("/tmp/generated/" + filename)), "UTF-8");
final HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_HTML);
return new ResponseEntity<String>(content, headers, HttpStatus.OK);
}
But when I open url in browser I get badly encoded html content (stating and ending with '"'):
"\u003chtml\u003e\n\u003chead\u003e\n \u003cmeta charset\u003d\"utf-8\" /\u003e\n \u003cmeta http-equiv\u003d\"X-UA-Compatible\" content\u003d\"IE\u003dedge\" /\u003e\n \u003cmeta name\u003d\"viewport\" content\u003d\"width\u003ddevice-width, initial-scale\u003d1\" /\u003e [.....]
If I add produces = MediaType.TEXT_HTML_VALUE to my #GetMapping annotation then I get 406 Not Acceptable error response (but no exception in my spring app)...
How to fix it?
I'm not sure why you are facing problems when using produces in your mapping.
I gave a quick try and it worked for me.
#GetMapping(value = "test", produces=MediaType.TEXT_HTML_VALUE)
public ResponseEntity<String> test(#RequestParam("filename") String filename) throws IOException {
String content = new String(Files.readAllBytes(Paths.get("/tmp/generated/" + filename)), "UTF-8");
return new ResponseEntity<String>(content, HttpStatus.OK);
}
Tested in Chrome browser:
File
NOTE: I tested this controller using SpringBoot v2.0.5.RELEASE
Cheers!
I have successfully build application with Spring Boot 1.5.2.RELEASE and it will return static HTML site from any directory
you can checkout here

How to set the produces value dynamically in spring rest controller?

I need to implement API which can either send response or download a file:
#GetMapping(value = "/download")
public ResponseEntity<?> downloadFile(
#RequestParam(value = "apiResponseType", required = true) String apiResponseType) throws IOException {
ValidationResponse response = null;
if (apiResponseType.equals("FILE")) {
String FILE_HEADER = "id,firstName,lastName,gender,age";
byte[] json = FILE_HEADER.getBytes();
Resource resource = new ByteArrayResource(json);
HttpHeaders headers = new HttpHeaders();
headers.setContentLength(resource.contentLength());
headers.setContentDispositionFormData("attachment", "test.csv");
return ResponseEntity.ok().headers(headers).contentType(MediaType.APPLICATION_OCTET_STREAM).body(resource);
} else {
response = new ValidationResponse();
response.setSuccess(true);
response.setMessage("TESTING");
return ResponseEntity.ok(response);
}
}
Above code is working for "ELSE" case. i.e., can able to send response.
But if I add "produces" to #GetMapping like below, I am able to download the file but not working for response (else case in above code) (Got status: 406 Not Acceptable):
#GetMapping(value = "/downloadFile", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
Can anyone help me with this ?
Did you try return ResponseEntity.ok(response).contentType(MediaType.TEXT_PLAIN); on the else branch (and remove the produces)?
Your question has invalid assumption 'which can either send response or download a file'. Download file is nothing else that sending response! For API client it is no difference. For browser it is just implementation details if browser proposes to save the response as file or shows response in browser window.
Setting the MediaType via a call to contentType on the ResponeEntitiy no longer works. By now (SpringBoot 2.7.1) you have to set the MediaType to the headers.
I wrote this method to dynamically create headers with a given MediaType provided as a string.
final HttpHeaders httpHeaders = new HttpHeaders();
final MediaType mediaType;
switch (responseType) {
case "json":
mediaType = MediaType.APPLICATION_JSON;
break;
case "plain":
case "text":
mediaType = MediaType.TEXT_PLAIN;
break;
default:
final var parts = responseType.split("/");
if (parts.length < 2)
throw new IllegalArgumentException(String.format("Unrecognizable MediaType '%s'", responseType));
mediaType = new MediaType(parts[0], parts[1]);
break;
}
LOGGER.debug("Using mediaType {}", mediaType);
httpHeaders.setContentType(mediaType);

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.

Resources