I cannot find out why the mp3 file is different after download from my server than original one saved previously there.
This is my controller method. The content of file (byte[] content) is identical with original file on this stage - the original file is the same as file retrieved from database (checked in debugger).
#ResponseBody
#RequestMapping(method = RequestMethod.GET, value = "/{sampleId}/file")
public HttpEntity<byte[]> getFile(#PathVariable Long sampleId) {
ResourceEntity resourceEntity = testSampleRepository.getFile(sampleId);
byte[] content = resourceEntity.getContent();
String fileName = resourceEntity.getFileName();
HttpHeaders header = new HttpHeaders();
header.setContentType(new MediaType("audio", "mpeg"));
header.set(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=" + fileName.replace(" ", "_"));
header.setContentLength(content.length);
return new HttpEntity<byte[]>(content, header);
}
This is how files differ (the left is original one):
Why passing using HTTP distors my file? Should mediaTypes enforce certain encoding? (there was no difference with "audio/mpeg" mediaType and without it).
It should work, if you set the produces = "application/octet-stream" attribute (MediaType.APPLICATION_OCTET_STREAM). Otherwise, you are trapped by Spring's converter framework.
You may want to have a look here, seems your problem is very similar: Spring MVC: How to return image in #ResponseBody? .
Related
I am trying to save a PDF using Angular and Spring Boot.
When I make an API call, my Java code is fetching the data from the database and transforming it to a byte-stream. This stream is sent as response.
if(format.equals(Constant.PDF_FORMAT)) {
ByteArrayInputStream stream = reportPDF.generateReportDocument(dtos);
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Disposition", "inline; filename=report.pdf");
return ResponseEntity.ok()
.headers(headers)
.contentType(MediaType.APPLICATION_PDF)
.body(new InputStreamResource(stream));
}
I have to use this response and save the data into a PDF.
Component.ts
public getReports(type?: string): void {
this.params['expected-format'] = type;
if (type === 'json') {
this.Service.getPilotReports(this.params).subscribe((res) => {
this.reportsData = res;
this.pilotBankSpinnerService.closeSpinner();
});
} else {
this.Service.customGetForDownload(this.params).subscribe(
(data: Blob) => {
var file = new Blob([data], { type: 'application/pdf' });
var fileURL = URL.createObjectURL(file);
window.open(fileURL);
var a = document.createElement('a');
a.href = fileURL;
a.target = '_blank';
a.download = 'reports.pdf';
document.body.appendChild(a);
a.click();
},
(error) => {
console.log('getPDF error: ', error);
}
);
}
}
Service.ts
public customGetForDownload<blob, T>(url: string, params: any): any {
const headers = new HttpHeaders({ 'Content-Type': 'application/json', responseType: 'blob' });
const httpParams = this.http.constructParams(params);
const absoluteUrl = this.getAbsoluteUrl(url);
return this.httpClient.get(absoluteUrl, {
headers: headers,
params: httpParams,
responseType: 'blob' as 'json',
observe: 'response',
});
}
Though the file is getting saved. When I try to open the file, it says "Failed to load pdf document".
Syntax Issues
First I see a syntax error:
missing argument in method-call: ByteArrayInputStream stream = reportPDF.generateReportDocument(dtos, ); (after the comma)
With this syntax error you most likely receive a compilation-error on console.
Assume this is a lapse and you can fix it to something like ByteArrayInputStream stream = reportPDF.generateReportDocument(dtos); then it should compile.
Boots without errors? Then test the endpoint!
Assume further your server application boots and runs without errors, then you could test the HTTP-endpoint with a HTTP-call.
You can test using a HTTP-client like CURL, postman or maybe even a browser.
Then you should receive a response with HTTP status code 200 and the body containing the PDF-file as binary with MIME-type application/pdf and specified header Content-Dispositon.
The browser is expected to prompt you with a download-dialogue.
Responding with a binary in Spring
Your InputStreamResource is a valid way, but you should be confident when using it.
In a Spring controller method, you can return the binary data in different types:
ResponseEntity<byte[]> as byte-array
ResponseEntity<ByteArrayOutputStream> as stream (not input-stream for reading input)
ResponseEntity<Resource> as abstract binary content, see Spring's Resource
ResponseEntity<File> as entire file
See also
Spring boot Angular2 file download not working
PDF Blob is not showing content, Angular 2
Return generated pdf using spring MVC
There are also some response-directed ways especially in Spring:
return a InputStreamResource as you did
return a StreamingResponseBody is very convenient
write to a HttpServletResponse, probably the oldest way
See: How To Download A File Directly From URL In Spring Boot
From input to output
Remember: Input is for reading (e.g. from a request), output is for writing (e.g. to a response). So you need an output type, like byte[] or ByteArrayOutputStream etc for your response-body.
When reading input into ByteArrayInputStream stream you could copy from that input to an output-stream with e.g. Apache-Commons IOUtils: IOUtils.copy(in, out);.
Or simply return the byte-array: byte[] data = stream.readAllBytes();
See: Java InputStream to Byte Array and ByteBuffer | Baeldung
My code was initially breaking if I try and push a large file (anything above 1MB size). It is working fine now and able to accommodate the file sizes I want by adding the following in the properties file.
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB
But how can I write a proper unit/integration test on this to ensure it allows file size up to 10MB?
The following has a good test example (the accepted answer) but it is using a mock file setup to test.
Using Spring MVC Test to unit test multipart POST request
Is there a way I could mock and specify file size?
Or actually pass in a real large file for testing (preferably not)?
Or a better way to do this, test I can accept a large file up to 10MB?
This is the method to be tested
#PostMapping(path = "/example", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<SomeResponse> upload(#PathVariable(#RequestPart("file") MultipartFile file) {
//we won't even get inside thi method and would fail if the file size is over 1MB previously.
// It works currently when I add files with size above 1MB
// cos I added the above 2 lines (spring.servlet.... in the properties file)
// some logic which works fine.
SomeResponse obj = //
return new ResponseEntity<>(obj, HttpStatus.OK);
}
This is current test (and there are other tests to test negative scenarios)
#Test
public void testValidUpload() throws Exception {
String fileContents = "12345";
String expectedFileContents = "12345\nSomeData";
mockServer.expect(requestTo("http://localhost:8080/example"))
.andExpect(method(HttpMethod.POST))
.andExpect(expectFile("file", "test.csv", expectedFileContents))
.andRespond(withStatus(HttpStatus.OK)
.contentType(MediaType.TEXT_PLAIN)
.body("done")
);
String response = this.mvc.perform(multipart("/example")
.file(new MockMultipartFile("file", "filename.csv", MediaType.TEXT_PLAIN_VALUE, fileContents.getBytes())))
.andExpect(status().isOk())
.andExpect(content().contentType(APPLICATION_JSON))
.andExpect(jsonPath("responseStatusCode", Matchers.equalTo("200")))
.andExpect(jsonPath("httpStatus", Matchers.equalTo("OK")))
.andReturn().getResponse().getContentAsString();
Response response = objectMapper.readValue(response, Response.class);
assertEquals(HttpStatus.OK, response.getHttpStatus());
assertEquals(5, response.id());
}
You can try something like this:
byte[] bytes = new byte[1024 * 1024 * 10];
MockMultipartFile firstFile = new MockMultipartFile("data", "file1.txt", "text/plain", bytes);
See documentation.
You can also refer to this article.
I am trying to download a file from rest API, I am writing code in Java and react. but when i call that rest api it is not downloading that file instead gives me some garbage
#POST
#Path("/{loginId}")
#Produces(MULTIPART_FORM_DATA)
#Consumes(APPLICATION_JSON)
public Response downloadExportedFile(#PathParam("loginId") String loginId, ExportFileDTO fileDetails) {
File exportFolder = new File("C://directory");
File[] listOfFiles = exportFolder.listFiles();
for (File listOfFile : listOfFiles) {
if (listOfFile.getName().equals(fileDetails.getFileName())) {
InputStream is = new FileInputStream(listOfFile.getAbsolutePath());
byte[] buffer = IOUtils.toByteArray(is);
return Response.ok(listOfFile)
.header("content-disposition", "attachment; filename=" + new File(listOfFile.getName()).getName())
.type(MediaType.APPLICATION_OCTET_STREAM_TYPE).build();
}
}
It should download the file instead it is giving me output as
PK!b�h^�[Content_Types].xml �(����N�0E�H�C�-Jܲ#5��Q>�ēƪc[�ii����B�j7���{2��h�nm���ƻR����U^7/���%��rZY�#1__�f��q��R4D�AJ�h>����V�ƹ�Z�9����NV�8ʩ����ji){^��-I�"{�v^�P!XS)bR�r��K�s(�3�`c�0��������7M4�����ZƐk+�|\|z�(���P��6h_-[�#�!���Pk���2n�}�?�L��� ��%���d����dN"m,�ǞDO97�~��ɸ8�O�c|n���E������B��!$}�����;{���[����2���PK!�U0#�L_rels/.rels �(���MO�0��H�����ݐBKwAH�!T~�I����$ݿ'T�G�~����<���!��4��;#�w����qu*&r�Fq���v�����GJy(v��*����K��#F��D��.W ��=��Z�MY�b���BS�����7��ϛז��
?�9L�ҙ�sbgٮ|�l!��USh9i�b�r:"y_dl��D���|-N��R"4�2�G�%��Z�4�˝y�7 ë��ɂ�����PK!
You have to change the associated mimetype by changing the the parameter of the #Produces annotation which basically describes what type of data you transmit in your response.
It should become:
#Produces("application/vnd.ms-excel")
According to this other stackoverflow question you should change the #Produces annotation to #Produces(MediaType.APPLICATION_OCTET_STREAM).
According to this second stackoverflow question you are asking an impossible question.
Out of curiosity I reproduced your problem here : see the full gist
If you change #POST to #GET it starts working
If you keep #POST, it has to be posted from a real form and can't post application/json
Finally, posting application/json means React is doing a programmatic XmlHTTPRequest. The above gist shall convince you there is no user prompt in that case
When you say it 'is giving me output', you're not telling where and how the post was requested . You will have to adapt that part.
actually It is APPLICATION_OCTET_STREAM response for a file. we have to handle download functionality at client side AS per Nate's answer here, the response of Ajax request is not recognized by a browser as a file. It will behave in the same way for all Ajax responses. You need to trigger the download popup manually.
downloadFile(fileDetails) {
let username = getUserName();
return fetch(`/files/${username}`, {
method: 'POST',
body: JSON.stringify(fileDetails)
}).then(response => {
return response.blob();
}).then(response => {
let blob = new Blob([response], {type: 'application/octet-stream'});
let fileUrl = window.URL.createObjectURL(blob);
Files.triggerDownload(fileUrl, fileDetails.fileName);
}).catch((error) => {
//myerror
});
}
static triggerDownload(url, fileName) {
let a = document.createElement('a');
a.setAttribute('href', url);
a.setAttribute('download', fileName);
a.click();
}
This will download the file at client machine
I'm wondering if there a real difference when a Sprin MVC controller method returns byte array byte[] to represent a downloaded file or when I copy InputStream object to the ServletOutputStream object?
The reason I'm asking is that I have to make sure that there won't be any OutOfMemory errors when downloading large files. Will the passing file through the ServletOutputStream help to avoid it?
Passing byte array:
byte[] download() {
return getUrlContentAsByteArray();
}
Passing in the ServletOutputStream:
void download(HttpServletResponse response) {
InputStream content = getUrlContentAsStream();
ServletOutputStream outputStream = response.getOutputStream();
response.reset();response.setContentType(ContentType.APPLICATION_OCTET_STREAM.getMimeType());
IOUtils.copyLarge(inputStream, outputStream);
}
In your first example, you have to read the entire response into memory and store it in a byte array. That will require at least as much memory as the size of the response.
In your second example, you do not keep the entire response in memory at once, but use IOUtils.copy to copy the content in multiple, small chunks from the source and into the servlet response. IOUtils is by default using a 4kB large buffer. You are however using a strange mix of both Spring and Servlet APIs.
Using Spring MVC alone, you can leave out the servlet API, return the InputStream wrapped as a Resource and let Spring do the copy job for you:
#RequestMapping(value = "/download", method = RequestMethod.GET)
#ResponseBody
public ResponseEntity<Resource> download() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
InputStream is = null; // get your input stream here
Resource resource = new InputStreamResource(is);
return new ResponseEntity<>(resource, headers, HttpStatus.OK);
}
My HTML file has the following command
<img src="/5/background" alt="" width="192" height="192">
Which I have bound to the following inside a #RestController in a Spring Boot application:
#RequestMapping(value = "/{connector}/background", method = RequestMethod.GET)
public File getConnectorBackgroundImage(#PathVariable("connector") int connector)
{
File image = new File("folder" + connector + "/myPicture");
return image;
}
I have done my debugging and I know the method is being entered, and I know the path is correct, but all that shows is the icon in the browser when there is a problem loading a picture.
What else am I missing?
Spring does not know how to handle the file that way. If you return a File, the controller will simply show you the path to the file if you call the REST API.
The proper way to do it is to read the file as a byte[]. Using commons-io you could come up with something like this:
File image = new File("folder" + connector + "/myPicture");
FileInputStream input = null;
try {
input = new FileInputStream(image);
return IOUtils.toByteArray(input);
} finally {
IOUtils.closeQuietly(input);
}
Another thing you shouldn't forget is to provide the mimetype. To do that you tweak the #RequestMapping annotation a bit by providing the produces property:
#RequestMapping(value = "/{connector}/background", method = RequestMethod.GET, produces = MediaType.IMAGE_PNG_VALUE)
This should do the trick.
EDIT: Didn't notice the comments, you already fixed it by yourself.