Spring boot rest service to download a zip file which contains multiple file
Asked Answered
B

4

14

I am able to download a single file but how I can download a zip file which contain multiple files.

Below is the code to download a single file but I have multiples files to download. Any help would greatly appreciated as I am stuck on this for last 2 days.

@GET
@Path("/download/{fname}/{ext}")
    @Produces(MediaType.APPLICATION_OCTET_STREAM)
public Response  downloadFile(@PathParam("fname") String fileName,@PathParam("ext") String fileExt){
    File file = new File("C:/temp/"+fileName+"."+fileExt);
    ResponseBuilder rb = Response.ok(file);
    rb.header("Content-Disposition", "attachment; filename=" + file.getName());
    Response response = rb.build();
    return response;
}
Bubal answered 14/7, 2018 at 19:47 Comment(5)
have you tried something with response.setContentType("application/zip");?Petronel
I think you will have to use rb.header("Content-Type", "application/zip");Petronel
So zip file is already there or you wish to create it on the fly? Also, one concern if full file needs to be in memory or needs to be streamed in chunks while downloading ?Plusch
I have to create the zip file on fly and to answer your second question is that full file needs to be in memory but we are restricting the download if it goes above 100 MB.Bubal
@abhishekchauhan: Its not guaranteed that you will have a single concurrent client so as per memory foot print , 100MB might have to get multiplied by number of concurrent allowed connections. Just a thought.Plusch
P
5

Use these Spring MVC provided abstractions to avoid loading of whole file in memory. org.springframework.core.io.Resource & org.springframework.core.io.InputStreamSource

This way, your underlying implementation can change without changing controller interface & also your downloads would be streamed byte by byte.

See accepted answer here which is basically using org.springframework.core.io.FileSystemResource to create a Resource and there is a logic to create zip file on the fly too.

That above answer has return type as void, while you should directly return a Resource or ResponseEntity<Resource> .

As demonstrated in this answer, loop around your actual files and put in zip stream. Have a look at produces and content-type headers.

Combine these two answers to get what you are trying to achieve.

Plusch answered 17/7, 2018 at 12:53 Comment(3)
thanks for your support. Now I am able to create zip on fly and download it as well.Bubal
Your welcome !! Its better that you update your question with your final working code so it helps others too.Plusch
I didn't quite understand your answer. After all, if we create a zip around response.getOutputStream(), that is, on the fly, the client will gradually receive a response from the server. How can we (and why) return the Resource if the response to the client is already gradually being filled in due to what we put in response.getOutputStream() files?Clairclairaudience
B
14

Here is my working code I have used response.getOuptStream()

@RestController
public class DownloadFileController {

    @Autowired
    DownloadService service;

    @GetMapping("/downloadZip")
    public void downloadFile(HttpServletResponse response) {

        response.setContentType("application/octet-stream");
        response.setHeader("Content-Disposition", "attachment;filename=download.zip");
        response.setStatus(HttpServletResponse.SC_OK);

        List<String> fileNames = service.getFileName();

        System.out.println("############# file size ###########" + fileNames.size());

        try (ZipOutputStream zippedOut = new ZipOutputStream(response.getOutputStream())) {
            for (String file : fileNames) {
                FileSystemResource resource = new FileSystemResource(file);

                ZipEntry e = new ZipEntry(resource.getFilename());
                // Configure the zip entry, the properties of the file
                e.setSize(resource.contentLength());
                e.setTime(System.currentTimeMillis());
                // etc.
                zippedOut.putNextEntry(e);
                // And the content of the resource:
                StreamUtils.copy(resource.getInputStream(), zippedOut);
                zippedOut.closeEntry();
            }
            zippedOut.finish();
        } catch (Exception e) {
            // Exception handling goes here
        }
    }
}

Service Class:-

public class DownloadServiceImpl implements DownloadService {

    @Autowired
    DownloadServiceDao repo;

    @Override
    public List<String> getFileName() {

        String[] fileName = { "C:\\neon\\FileTest\\File1.xlsx", "C:\\neon\\FileTest\\File2.xlsx", "C:\\neon\\FileTest\\File3.xlsx" };

        List<String> fileList = new ArrayList<>(Arrays.asList(fileName));       
        return fileList;
    }
}
Bubal answered 19/7, 2018 at 17:38 Comment(1)
How can we give the location to the zip file to be downloaded?Saltus
P
5

Use these Spring MVC provided abstractions to avoid loading of whole file in memory. org.springframework.core.io.Resource & org.springframework.core.io.InputStreamSource

This way, your underlying implementation can change without changing controller interface & also your downloads would be streamed byte by byte.

See accepted answer here which is basically using org.springframework.core.io.FileSystemResource to create a Resource and there is a logic to create zip file on the fly too.

That above answer has return type as void, while you should directly return a Resource or ResponseEntity<Resource> .

As demonstrated in this answer, loop around your actual files and put in zip stream. Have a look at produces and content-type headers.

Combine these two answers to get what you are trying to achieve.

Plusch answered 17/7, 2018 at 12:53 Comment(3)
thanks for your support. Now I am able to create zip on fly and download it as well.Bubal
Your welcome !! Its better that you update your question with your final working code so it helps others too.Plusch
I didn't quite understand your answer. After all, if we create a zip around response.getOutputStream(), that is, on the fly, the client will gradually receive a response from the server. How can we (and why) return the Resource if the response to the client is already gradually being filled in due to what we put in response.getOutputStream() files?Clairclairaudience
S
0
public void downloadSupportBundle(HttpServletResponse response){

      File file = new File("supportbundle.tar.gz");
      Path path = Paths.get(file.getAbsolutePath());
      logger.debug("__path {} - absolute Path{}", path.getFileName(),
            path.getRoot().toAbsolutePath());

      response.setContentType("application/octet-stream");
      response.setHeader("Content-Disposition", "attachment;filename=supportbundle.tar.gz");
      response.setStatus(HttpServletResponse.SC_OK);

      System.out.println("############# file name ###########" + file.getName());

      try (ZipOutputStream zippedOut = new ZipOutputStream(response.getOutputStream())) {

         FileSystemResource resource = new FileSystemResource(file);

         ZipEntry e = new ZipEntry(resource.getFilename());
         e.setSize(resource.contentLength());
         e.setTime(System.currentTimeMillis());
         zippedOut.putNextEntry(e);
         StreamUtils.copy(resource.getInputStream(), zippedOut);
         zippedOut.closeEntry();

         zippedOut.finish();
      } catch (Exception e) {
         
      }
}
Sievert answered 31/10, 2020 at 14:12 Comment(0)
C
-1
public ResponseEntity<byte[]> downloadFolderAsZip(@RequestParam("id") Long id) throws IOException, InvalidKeyException,
        InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException,
        InternalException, XmlParserException, ErrorResponseException {
    {
        // Set the response headers for the ZIP file
       

        List<File> files = folderRepository.findAllByParentFolderById(id);
        List<Folder> folders = folderRepository.findAllByParentFolder(id);
        Folder folderById = folderRepository.findById(id).get();

        // Create ByteArrayOutputStream to hold ZIP file contents
        ByteArrayOutputStream zipBytes = new ByteArrayOutputStream();
        ZipOutputStream zipOut = new ZipOutputStream(zipBytes);


        // Iterate over each file and add to ZIP
        for (com.numeryx.minioaccess.model.File file : files) {
            String fileName = file.getName();
            String filePath = file.getPath();

            // Get file from Minio
            InputStream inputStream = ConnectedUserMinioSession.minioClient.getObject(
                    GetObjectArgs.builder()
                            .bucket("reports")
                            .object(file.getPathUuid())
                            .build());

            // Add file to ZIP
            zipOut.putNextEntry(new ZipEntry(fileName));
            IOUtils.copy(inputStream, zipOut);
            zipOut.closeEntry();
            inputStream.close();



            }
        if (!folders.isEmpty()) {
            for (Folder folder : folders) {
                addFolderToZip(zipOut, folder.getName() ,folder.getId(),"");
            }

        }

            // Close ZIP stream
            zipOut.finish();
            zipOut.close();

            // Set HTTP response headers
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            headers.setContentDispositionFormData("attachment", "folder.zip");

            // Return ZIP file as byte array
            return new ResponseEntity<>(zipBytes.toByteArray(), headers, HttpStatus.OK);
        }


}

private void addFolderToZip(ZipOutputStream zipOut, String folderName,Long id,String folderSupName) throws IOException,
        InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException,
        ServerException, InternalException, XmlParserException, ErrorResponseException {
    if (folderSupName != "") {
        String[] value = folderSupName.split("/", 4);
        // Create a new ZIP entry for the folder

        zipOut.putNextEntry(new ZipEntry(value[3]  + folderName + "/"));
        zipOut.closeEntry();


        // Get all files and subfolders in the folder
        //List<File> files = fileDBRepository.findAllByParentFolder(folderPathUuid);
        List<File> files = folderRepository.findAllByParentFolderById(id);
        List<Folder> folders = folderRepository.findAllByParentFolder(id);
        Folder folderById = folderRepository.findById(id).get();

        // Recursively add all files and subfolders to the ZIP
        for (com.numeryx.minioaccess.model.File file : files) {
            String fileName = file.getName();
            String filePathUuid = file.getPathUuid();
            // If the file is not a folder, add it to the ZIP
            InputStream inputStream = ConnectedUserMinioSession.minioClient.getObject(
                    GetObjectArgs.builder()
                            .bucket("reports")
                            .object(filePathUuid)
                            .build());

            // Add file to ZIP
            zipOut.putNextEntry(new ZipEntry(value[3] +  folderName + "/" + fileName));
            IOUtils.copy(inputStream, zipOut);
            zipOut.closeEntry();
            inputStream.close();
        }

        if (!folders.isEmpty()) {
            for (Folder folder : folders) {
                addFolderToZip(zipOut, folder.getName(), folder.getId(), folderById.getPath());
            }
        }
    } else {
        zipOut.putNextEntry(new ZipEntry(folderSupName + folderName + "/"));
        zipOut.closeEntry();


        // Get all files and subfolders in the folder

        List<File> files = folderRepository.findAllByParentFolderById(id);
        List<Folder> folders = folderRepository.findAllByParentFolder(id);
        Folder folderById = folderRepository.findById(id).get();

        // Recursively add all files and subfolders to the ZIP
        for (com.numeryx.minioaccess.model.File file : files) {
            String fileName = file.getName();
            String filePathUuid = file.getPathUuid();
            // If the file is not a folder, add it to the ZIP
            InputStream inputStream = ConnectedUserMinioSession.minioClient.getObject(
                    GetObjectArgs.builder()
                            .bucket("reports")
                            .object(filePathUuid)
                            .build());

            // Add file to ZIP
            zipOut.putNextEntry(new ZipEntry(folderSupName + folderName + "/" + fileName));
            IOUtils.copy(inputStream, zipOut);
            zipOut.closeEntry();
            inputStream.close();
        }

        if (!folders.isEmpty()) {
            for (Folder folder : folders) {
                addFolderToZip(zipOut, folder.getName(), folder.getId(), folderById.getPath());
            }
        }
    }
}
Checker answered 5/10, 2023 at 14:21 Comment(3)
@GetMapping("/zip")Checker
In my code I worked with minio but you can change itChecker
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Henricks

© 2022 - 2025 — McMap. All rights reserved.