Requirement:
I need to create a Rest API which can allows to download a file as well as a JSON response.
I already have 2 different APIs to solve the purpose, but now I need to merge these APIs to a single one.
public ResponseEntity<InputStreamResource> downloadFile1(
#RequestParam(defaultValue = DEFAULT_FILE_NAME) String fileName) throws IOException {
MediaType mediaType = MediaTypeUtils.getMediaTypeForFileName(this.servletContext, fileName);
System.out.println("fileName: " + fileName);
System.out.println("mediaType: " + mediaType);
File file = new File(DIRECTORY + "/" + fileName);
InputStreamResource resource = new InputStreamResource(new FileInputStream(file));
return ResponseEntity.ok()
// Content-Disposition
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + file.getName())
// Content-Type
.contentType(mediaType)
// Contet-Length
.contentLength(file.length()) //
.body(resource);
}
Above is the existing code that only return a file to download but I need a json response as well.
You need to return Multipart content. See for example
https://github.com/juazugas/spring-boot-multipart/blob/master/src/main/java/com/example/demo/server/MultiEndpoint.java
The code
#GET
#Produces("multipart/mixed")
public MultipartBody getMulti2(#QueryParam("name") String name) {
List<Attachment> attachments = new LinkedList<>();
attachments.add(new Attachment("root", "application/json", service.getEntity(name)));
attachments.add(new Attachment("image", "application/octet-stream", service.getEntityData(name)));
return new MultipartBody(attachments, true);
}
Related
I used this method to download and get content type
#GetMapping("/downloadFile/")
public ResponseEntity<Resource> downloadFile(#RequestParam String fileName, HttpServletRequest request) {
// Load file as Resource
Resource resource = fileStorageService.loadOneFileAsResource(fileName);
// Try to determine file's content type
String contentType = null;
try {
contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath());
} catch (IOException ex) {
logger.info("Could not determine file type.");
}
// Fallback to the default content type if type could not be determined
if(contentType == null) {
contentType = "application/octet-stream";
}
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
.body(resource);
}
But always detect my content "application/octet-stream", what is problem and what should I do?
thanks in advance.
You can download the file, save it and then use the following:
Path path = Paths.get(resource.getURI());
String contentType = Files.probeContentType(path);
This should give you the content type. Look over here.
How can I get a file from reosurces / folderX / file.txt
#PostMapping(value = "/uploadFile", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public ResponseEntity<Resource> uploadFile(#RequestParam("file") MultipartFile file) {
/*
* FIRST I upload file
* Next, I need to return different file in this request
*/
return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + "file.txt + "\"").body();
}
You can serve a resource like this:
#GetMapping(value = "file")
public ResponseEntity<Resource> file() {
Resource resource = new ClassPathResource("folderX/file.txt");
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"file.txt\"");
return new ResponseEntity<>(resource, headers, HttpStatus.OK);
}
I am getting a resulting file but in the response I am getting gibberish symbols
here is the code I am trying
public ResponseEntity<InputStreamResource> getExcel(String filePath) throws Exception {
try {
Path excelPath = Paths.get(filePath);
byte[] excel = Files.readAllBytes(excelPath);
ByteArrayInputStream excelToByte = new ByteArrayInputStream(excel);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.add("Content-Disposition", "attachment; filename=ABCGeneratedExcel.xls");
return ResponseEntity
.ok()
.headers(headers)
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(new InputStreamResource(excelToByte));
}
catch (NoSuchFileException e) {
System.out.prinln("does not exist");
}
You should use HttpServletResponse instead. And let Spring framework initialize it by declaring as Controller method's parameter. Because you will write the excel file as binary stream, do not define the return type.
Then write the response stream after setting the contentType and header for excel downloading.
public void getExcel(String filePath, HttpServletResponse response) {
byte[] excel = Files.readAllBytes(excelPath);
String fileName = "anyFileName.xlsx"
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
response.getWriter().write(excel); // in fact, you need to surround this by try-catch block
}
Path filePath = pathToFolder.resolve(fileName).normalize();
Resource resource = new UrlResource(filePath.toUri());
if (resource.exists()) {
return resource;
} else {
throw new NotFoundException(String.format("File %s not found", fileName));
}
Where path to File - in your directory, and file name - name of file in your directory.
Next step is:
Resource resource = service.downloadFile(fileName);
String contentType = null;
try {
contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath());
} catch (IOException e) {
log.info("Could not determine file type");
}
if (contentType == null) {
contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
}
return ResponseEntity
.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION, String.format(
"%s; filename=%s", content.name().toLowerCase(), resource.getFilename()
)
)
.body(resource);
Where first %s - attachment - for downloading, and inline - for rendering file in the browser.
Second %s - name of file (note that if you are storing your file in the file system, use file name with extension).
I have a folder in S3 bucket that contains json files. I am using Spring Boot. The user provides folder name and specific file(json file) name.
public ResponseEntity<?> downloading(String folderName, String fileName) throws IOException {
S3Object s3Object = s3client
.getObject(new GetObjectRequest(s3BucketName, folderName + fileName));
if (s3Object.getKey().length() > 0) {
//enables the user to dowload json file
//return the object that can be dowloaded, status code
return new ResponseEntity<>( HttpStatus.OK);
}
else{
//return error message and status code
return new ResponseEntity<>( HttpStatus.NOT_FOUND);
}
}
I have referred the answer from here.
So, I have added just a part of the code that will help you :
public ResponseEntity<byte[]> downloading(String folderName, String fileName) {
S3Object s3Object = s3client
.getObject(new GetObjectRequest(s3BucketName, folderName + fileName));
if (s3Object.getKey().length() > 0) {
S3ObjectInputStream input = s3Object.getObjectContent();
byte[] bytes = IOUtils.toByteArray(input);
String file = URLEncoder.encode(s3Object.getKey(), "UTF-8").replaceAll("\\+", "%20");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentLength(bytes.length);
headers.setContentDispositionFormData("attachment", file);
return new ResponseEntity<byte[]>(bytes, HttpStatus.OK);
}
return new ResponseEntity<byte[]>(null, HttpStatus.NOT_FOUND);
}
Thanks. :)
Server Code :
#POST
#Path("reportDownload")
#Consumes(MediaType.APPLICATION_JSON)
public Response generateReport(QueryData queryData) {
File file = new File("report.xlsx") // large file
StreamingOutput stream = new FileStreamingOutput(file) ;
return Response.ok(stream, MediaType.APPLICATION_OCTET_STREAM)
.header("filename" , file.getName())
.build();
}
Client Code :
Using the following code I'm able to download files upto some limit. Getting out of memory heap error for large files.
final String uri = buildUri("/reportGenerate/reportDownload");
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setReadTimeout(read_timeout);
factory.setConnectTimeout(connection_timeout);
RestTemplate restTemplate = new RestTemplate(factory);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
List<MediaType> mediaTypeList = new ArrayList<>();
mediaTypeList.add(MediaType.APPLICATION_OCTET_STREAM);
headers.setAccept(mediaTypeList);
HttpEntity entity = new HttpEntity(queryData, headers);
ResponseEntity<byte[]> data = restTemplate.exchange(uri, HttpMethod.POST, entity, byte[].class);
HttpHeaders responseHeader = data.getHeaders();
String fileName = (String) responseHeader.get("filename").get(0);
String downloadFolder = ApplicationConfig.REPORT_DOWNLOAD_FOLDER.getValue();
if (data.getStatusCode() == HttpStatus.OK) {
FileOutputStream fos = null;
File toFile = null;
try {
toFile = new File(downloadFolder + File.separator + fileName);
fos = new FileOutputStream(toFile);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
IOUtils.write(data.getBody(), bos);
bos.writeTo(fos);
} catch (Exception e) {
convertReportException(e);
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException ex) {
convertReportException(ex);
}
}
return toFile;
}
}
How to use stream for download larger files.
Here is how I do it with a ResponseExtractor. Based on hints from this Spring Jira issue.
RestTemplate restTemplate // = ...;
// Optional Accept header
RequestCallback requestCallback = request -> request.getHeaders()
.setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL));
// Streams the response instead of loading it all in memory
ResponseExtractor<Void> responseExtractor = response -> {
// Here I write the response to a file but do what you like
Path path = Paths.get("some/path");
Files.copy(response.getBody(), path);
return null;
};
restTemplate.execute(URI.create("www.something.com"), HttpMethod.GET, requestCallback, responseExtractor);
update
Here is what RestTemplate does behind the scenes on postForObject and friends (inline comments from me):
#Override
public <T> T postForObject(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables)
throws RestClientException {
// From RequestCallback's javadoc:
// Callback interface for code that operates on a ClientHttpRequest.
// Allows to manipulate the request headers, and write to the request body.
//
// Used internally by the RestTemplate, but also useful for application code.
RequestCallback requestCallback = httpEntityCallback(request, responseType);
// HttpMessageConverterExtractor checks the response type header and requested
// responseType class to select the proper message converter to handle the response.
// It also implements ResponseExtractor.
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger);
return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
}
/**
* Returns a request callback implementation that writes the given object to the
* request stream.
*/
protected <T> RequestCallback httpEntityCallback(Object requestBody, Type responseType) {
return new HttpEntityRequestCallback(requestBody, responseType);
}
Note: This is essentially a duplicate of my answer at https://stackoverflow.com/a/38664475/1030527 but I can't mark the questions as duplicate since neither this one or that one have upvoted answers.
At the client that you mentioned
don't store the file in memory to download a large via the RestTemplate, it can cause the Java heap exception.
it should be stored on disk.
Here is some code sample to download a large file via the RestTemplate
#GetMapping("largeFile")
public ResponseEntity<InputStreamResource> downloadLargeFile(
#RequestParam("fileName") String fileName
) throws IOException {
RestTemplate restTemplate = new RestTemplate();
// Optional Accept header
RequestCallback requestCallback = request -> request.getHeaders()
.setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL));
// Streams the response instead of loading it all in memory
ResponseExtractor<InputStreamResource> responseExtractor = response -> {
// Here I write the response to a file but do what you like
Path path = Paths.get("tmp/" + fileName);
Files.copy(response.getBody(), path, StandardCopyOption.REPLACE_EXISTING);
return new InputStreamResource(new FileInputStream(String.format("tmp/%s", fileName)));
};
InputStreamResource response = restTemplate.execute(
String.format("http://%s:%s/file/largeFileRestTemplate?fileName=%s", host, "9091", fileName),
HttpMethod.GET,
requestCallback,
responseExtractor
);
return ResponseEntity
.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment; filename=%s", fileName))
.body(response);
}