I'm trying to create a rest service to download files from a repository, using Springboot.
I'm trying to return a ResponseEntity with StreamingResponseBody, to return the file that i get from the repository, as an InputStream.
This is the current code i have:
#GetMapping(path = "/downloadFile")
public ResponseEntity<StreamingResponseBody> downloadFile(#RequestParam(value = "documentId") String documentId,
HttpServletRequest request, HttpServletResponse response) throws InterruptedException, IOException {
InputStream is = downloadService.getDocument(documentId);
StreamingResponseBody out = outputStream -> {
outputStream.write(IOUtils.toByteArray(is));
};
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "text/csv");
headers.add("Content-Disposition", "attachment; filename=" + documentId);
headers.add("Pragma", "no-cache");
headers.add("Cache-Control", "no-cache");
return (new ResponseEntity<>(out, headers, HttpStatus.OK));
}
When I consume this endpoint, using directly the browser, or postman, the file that is downloaded comes empty.
I understand that the OutputStream is written to asynchronously (Async is enabled in the config class).
How can I consume this service and get the file completely written, the way it comes from the repository I'm using ? ( if possible using Postman, just for testing purposes)
Am i building the service correctly?
I have modified the code bit little, in my documentId is the name of the file to be downloaded. I have tested, it is working fine. Check below the code.
#GetMapping(path = "/downloadFile")
public ResponseEntity<StreamingResponseBody> downloadFile(
#RequestParam(value = "documentId") String documentId,
HttpServletRequest request,
HttpServletResponse response)
throws InterruptedException, IOException {
String dirPath = "E:/sure-delete/"; //Directory having the files
InputStream inputStream = new FileInputStream(new File(dirPath + documentId));
final StreamingResponseBody out =
outputStream -> {
int nRead;
byte[] data = new byte[1024];
while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
System.out.println("Writing some bytes of file...");
outputStream.write(data, 0, nRead);
}
};
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "text/csv");
headers.add("Content-Disposition", "attachment; filename=" + documentId);
headers.add("Pragma", "no-cache");
headers.add("Cache-Control", "no-cache");
return ResponseEntity.ok().headers(headers).body(out);
}
Related
In my spring-boot application, I have a GET end-point. When we call this GET endpoint, the application is sending a GET request to another service with RestTemplate and returns the same response file as the response of the GET request. With the below code I'm able to receive the response file. But I need to set the same headers that I have received to RestTempate request.
How to do that.
#GetMapping(value = URL_CONTENT_ID, produces = {MediaType.APPLICATION_OCTET_STREAM_VALUE, APPLICATION_ZIP_VALUE,
MediaType.TEXT_HTML_VALUE})
#ControllerLogging
public ResponseEntity<StreamingResponseBody> getContentFile(#PathVariable String contentId) {
StreamingResponseBody responseBody = outputStream -> {
getContentFile(outputStream, contentId);
outputStream.close();
};
return ResponseEntity.ok()
.body(responseBody);
}
public void getContentFile(OutputStream outputStream, String nodeId) {
RequestCallback requestCallBack = request -> {
HttpHeaders headers = new HttpHeaders();
authenticationHelper.apply(headers::set);
request.getHeaders().addAll(headers);
};
ResponseExtractor<OutputStream> responseExtractor = clientHttpResponse -> {
InputStream inputStream = clientHttpResponse.getBody();
StreamUtils.copy(inputStream, outputStream);
return null;
};
restTemplate.execute(dcmUrl + nodeId, HttpMethod.GET, requestCallBack, responseExtractor);
}
Please find the solution to this issue, the change I did was adding "HttpServletResponse" to the parameters of the controller method.
#GetMapping(value = URL_CONTENT_ID, produces = {MediaType.APPLICATION_OCTET_STREAM_VALUE, APPLICATION_ZIP_VALUE,
MediaType.TEXT_HTML_VALUE})
#ControllerLogging
public ResponseEntity<StreamingResponseBody> getContentFile(#PathVariable String contentId, HttpServletResponse response) {
StreamingResponseBody responseBody = outputStream -> {
getContentFile(outputStream, response, contentId);
outputStream.close();
};
return ResponseEntity.ok(responseBody);
}
public void getContentFile(OutputStream outputStream, HttpServletResponse response, String nodeId) {
RequestCallback requestCallBack = request -> {
HttpHeaders headers = new HttpHeaders();
authenticationHelper.apply(headers::set);
request.getHeaders().addAll(headers);
};
ResponseExtractor<OutputStream> responseExtractor = clientHttpResponse -> {
InputStream inputStream = clientHttpResponse.getBody();
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, modifyContentDispositionHeader(clientHttpResponse));
response.setHeader(HttpHeaders.CONTENT_TYPE, modifyContentTypeHeader(clientHttpResponse).toString());
StreamUtils.copy(inputStream, outputStream);
return null;
};
restTemplate.execute(dcmUrl + nodeId, HttpMethod.GET, requestCallBack, responseExtractor);
}
I tried to use spring boot to pass files to vue. The files can be opened, but there's noting in it.
I checked the res.data, and it turned out that it is undefined.
Here's the spring boot's code:
The contorller:
#GetMapping("/download/{path}")
public ResponseEntity<InputStreamResource> downloadFiles(#PathVariable String path, HttpServletResponse response) throws IOException {
//fileUtils.getFile(path,response);
return fileUtils.downloadFile(path);
}
The FileUtils:
public ResponseEntity<InputStreamResource> downloadFile(String path)
throws IOException {
String filePath = System.getProperty("user.dir") + "\\files\\" + path;
FileSystemResource file = new FileSystemResource(filePath);
HttpHeaders headers = new HttpHeaders();
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
headers.add("Content-Disposition", String.format("attachment; filename=\"%s\"", file.getFilename()));
headers.add("Pragma", "no-cache");
headers.add("Expires", "0");
return ResponseEntity
.ok()
.headers(headers)
.contentLength(file.contentLength())
.contentType(MediaType.parseMediaType("application/octet-stream"))
.body(new InputStreamResource(file.getInputStream()));
}
The Vue's code:
handleDownload(path,fileName){
request.get('/files/download/'+path+fileName, {responseType: 'blob'}).then(res => {
console.log(res.data)
fileDownload(res.data, fileName);
}).catch((res)=>{
console.log('download error');
}
)
}
I guess the reason there is only'undefined' in the file is that res.data is'undefined'. But I don't understand why res.data is undefined.
fileDownload(res, fileName); works. But I don't know why.
I'm developing a Rest API, will be responsible to return a csv file as response.
This is my Api interface:
#Api(value = Constantes.REPORTS)
public interface ExtractFileApi {
#RequestMapping(value = Constantes.REPORTS_URL, produces = "application/csv", method = RequestMethod.GET)
public ResponseEntity<InputStreamResource> getExtractFile() throws IOException;
}
And this is my interface implementation:
#RestController
public class ExtractFileApiController implements ExtractFileApi {
#Override
public ResponseEntity<InputStreamResource> getExtractFile() throws IOException {
ClassPathResource pdfFile = new ClassPathResource("pdf-sample.csv");
HttpHeaders headers = new HttpHeaders();
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
headers.add("Pragma", "no-cache");
headers.add("Expires", "0");
return ResponseEntity.ok().headers(headers).contentLength(pdfFile.contentLength())
.contentType(MediaType.parseMediaType("application/octet-stream"))
.body(new InputStreamResource(pdfFile.getInputStream()));
}
For now, my API return a link to download the file, but I don't know how to force the response to be exactly a CSV file (file.csv).
Can any one help me ?
You need to change return type to void and then use following code at end
Path path = Paths.get(pdfFile.getPath());
response.setHeader("content-disposition", "attachment; filename="
+ path.getFileName().toString().replace(" ", "_"));
try {
response.setContentType(Files.probeContentType(path));
response.setContentLength((int) Files.size(path));
// Copy bytes from source to destination, closes both streams.
FileCopyUtils.copy(Files.newInputStream(path),
response.getOutputStream());
} catch (IOException e) {
LOGGER.error("fetching file failed", e);
response.setStatus(500);
}
Try changing the production output MIME type to text/csv instead.
I'm trying to implement an ajax download. This is my code for the ajax request:
$('#download').click(function(){
$.ajax({
url: '${downloadPath}',
type: 'GET',
data: {${_csrf.parameterName}:'${_csrf.token}'},
success: function (res) {
}
});
});
And this is my controller's method:
#Secured("IS_AUTHENTICATED")
#RequestMapping(value="download/{id}", method=RequestMethod.GET, produces="application/pdf")
#ResponseBody
public void download(#PathVariable(value="id") final Long id, HttpServletResponse response) throws IOException {
CheckList checkList = checkListService.findById(id);
// byte[] byteItem = checkListService.getFileByIdDocument(id);
File f = new File(VariabiliGlobali.PATH_CHECKLIST+checkList.getPratica().getId()+"/"+id);
ServletOutputStream out = response.getOutputStream();
response.setContentType("application/pdf");
response.setContentLength((int)f.length());
response.setHeader("Content-Disposition", "attachment; filename=\"" + f.getName() + "\"");
FileInputStream in = new FileInputStream(f);
byte[] buffer = new byte[4096];
int length;
while( (length = in.read(buffer) ) > 0) {
out.write(buffer, 0, length);
}
in.close();
out.flush();
}
I can see the pdf inside the response:
But my browser (Chrome) doens't do anything.
Where am I wrong? How can I dowload it?
You don't need ajax and you are using as content type application/octet-stream as we can see in your code here:
response.setContentType("application/octet-stream");
If you want to display the pdf inside the browser (if the browser has the proper plugin to read pdf) you should use:
the right pdf content type
set the proper header
In my code i did the following:
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "inline; filename=pdfFileName.pdf;");
In any case I'd suggest to yuo to use the "spring" way like this:
#Secured("IS_AUTHENTICATED")
#RequestMapping(value="download/{id}", method=RequestMethod.GET)
public ResponseEntity<InputStreamResource> download(#PathVariable(value="id") final Long id) throws IOException {
HttpHeaders respHeaders = new HttpHeaders();
MediaType mediaType = new MediaType("application","pdf");
respHeaders.setContentType(mediaType);
respHeaders.setContentDispositionFormData("inline", "pdfFileName.pdf");
//Here you have to take the InputStream of the file you want to download
InputStreamResource isr = new InputStreamResource(new FileInputStream(file));
return new ResponseEntity<InputStreamResource>(isr, respHeaders, HttpStatus.OK);
}
I hope it's useful
Angelo
Set your content type in header. So browsers handles the pdf.
headers.setContentType(MediaType.parseMediaType("application/pdf"));
Below is the sample code.
#Secured("IS_AUTHENTICATED")
#RequestMapping(value="download/{id}", method=RequestMethod.GET, produces="application/pdf")
public ResponseEntity<?> download(#PathVariable(value="id") final Long id, HttpServletResponse response) throws IOException {
List<SampleDto> reportData = new ArrayList<SampleDto>();
HttpHeaders headers = new HttpHeaders();
if (null == reportData || reportData.size() == 0) {
return new ResponseEntity<byte[]>(null, headers, HttpStatus.NO_CONTENT);
}
byte[] contents = writePdfContentToBytes();//Here you should your code to get content in bytes.
headers.setContentType(MediaType.parseMediaType("application/pdf"));
headers.setContentDispositionFormData("inline", "Report.pdf");
return new ResponseEntity<byte[]>(contents, headers, HttpStatus.OK);
}
What is the most appropriate, and standard, way to set the Content-Disposition=attachment and filename=xyz.zip using Spring 3 FileSystemResource?
The action looks like :
#ResponseBody
#RequestMapping(value = "/action/{abcd}/{efgh}", method = RequestMethod.GET, produces = "application/zip")
#PreAuthorize("#authorizationService.authorizeMethod()")
public FileSystemResource doAction(#PathVariable String abcd, #PathVariable String efgh) {
File zipFile = service.getFile(abcd, efgh);
return new FileSystemResource(zipFile);
}
Although the file is a zip file so the browser always downloads the file, but I would like to explicitly mention the file as attachment, and also provide a filename that has nothing to do with the files actual name.
There might be workarounds for this problem, but I would like to know the proper Spring and FileSystemResource way to achieve this goal.
P.S. The file that is being used here is a temporary file, marked for deletion when the JVM exists.
In addition to the accepted answer, Spring has the class ContentDisposition specific for this purpose. I believe it deals with the file name sanitization.
ContentDisposition contentDisposition = ContentDisposition.builder("inline")
.filename("Filename")
.build();
HttpHeaders headers = new HttpHeaders();
headers.setContentDisposition(contentDisposition);
#RequestMapping(value = "/action/{abcd}/{efgh}", method = RequestMethod.GET)
#PreAuthorize("#authorizationService.authorizeMethod(#id)")
public HttpEntity<byte[]> doAction(#PathVariable ObjectType obj, #PathVariable Date date, HttpServletResponse response) throws IOException {
ZipFileType zipFile = service.getFile(obj1.getId(), date);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
response.setHeader("Content-Disposition", "attachment; filename=" + zipFile.getFileName());
return new HttpEntity<byte[]>(zipFile.getByteArray(), headers);
}
#RequestMapping(value = "/files/{file_name}", method = RequestMethod.GET)
#ResponseBody
public FileSystemResource getFile(#PathVariable("file_name") String fileName,HttpServletResponse response) {
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment; filename=somefile.pdf");
return new FileSystemResource(new File("file full path"));
}
Here is an alternative approach for Spring 4. Note that this example clearly does not use good practices regarding filesystem access, this is just to demonstrate how some properties can be set declaratively.
#RequestMapping(value = "/{resourceIdentifier}", method = RequestMethod.GET, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
// #ResponseBody // Needed for #Controller but not for #RestController.
public ResponseEntity<InputStreamResource> download(#PathVariable(name = "resourceIdentifier") final String filename) throws Exception
{
final String resourceName = filename + ".dat";
final File iFile = new File("/some/folder", resourceName);
final long resourceLength = iFile.length();
final long lastModified = iFile.lastModified();
final InputStream resource = new FileInputStream(iFile);
return ResponseEntity.ok()
.header("Content-Disposition", "attachment; filename=" + resourceName)
.contentLength(resourceLength)
.lastModified(lastModified)
.contentType(MediaType.APPLICATION_OCTET_STREAM_VALUE)
.body(resource);
}
Made few changes to both given answers and I ended up with the best of both in my project where I needed to extract an image from the database as a blob and then serve it to the clients :
#GetMapping("/images/{imageId:.+}")
#ResponseBody
public ResponseEntity<FileSystemResource> serveFile(#PathVariable #Valid String imageId,HttpServletResponse response)
{
ImageEntity singleImageInfo=db.storage.StorageService.getImage(imageId);
if(singleImageInfo==null)
{
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
}
Blob image=singleImageInfo.getImage();
try {
String filename= UsersExtra.GenerateSession()+"xxyy"+singleImageInfo.getImage1Ext().trim();
byte [] array = image.getBytes( 1, ( int ) image.length() );
File file = File.createTempFile(UsersExtra.GenerateSession()+"xxyy", singleImageInfo.getImage1Ext().trim(), new File("."));
FileOutputStream out = new FileOutputStream( file );
out.write( array );
out.close();
FileSystemResource testing=new FileSystemResource(file);
String mimeType = "image/"+singleImageInfo.getImage1Ext().trim().toLowerCase().replace(".", "");
response.setContentType(mimeType);
String headerKey = "Content-Disposition";
String headerValue = String.format("attachment; filename=\"%s\"", filename);
response.setHeader(headerKey, headerValue);
// return new FileSystemResource(file);
return ResponseEntity.status(HttpStatus.OK).body( new FileSystemResource(file));
}catch(Exception e)
{
System.out.println(e.getMessage());
}
return null;
}
Using a ResponseEntity in Kumar's code will help you respond with the correct Response code.
Note: converting from a blob to a file is quoted from this link:
Snippet to create a file from the contents of a blob in Java