Spring REST – crea un archivo .zip y lo envía al cliente

Quiero crear un archivo .zip que contenga mis archivos comprimidos que recibo del backend y luego enviar este archivo al usuario. Durante 2 días he estado buscando la respuesta y no puedo encontrar la solución adecuada, quizás puedas ayudarme 🙂

Por ahora, el código es así: (Sé que no debería hacerlo todo en el controlador de spring, pero no me importa eso, es solo para propósitos de prueba, para encontrar la manera de hacerlo funcionar)

@RequestMapping(value = "/zip") public byte[] zipFiles(HttpServletResponse response) throws IOException{ //setting headers response.setContentType("application/zip"); response.setStatus(HttpServletResponse.SC_OK); response.addHeader("Content-Disposition", "attachment; filename=\"test.zip\""); //creating byteArray stream, make it bufforable and passing this buffor to ZipOutputStream ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(byteArrayOutputStream); ZipOutputStream zipOutputStream = new ZipOutputStream(bufferedOutputStream); //simple file list, just for tests ArrayList files = new ArrayList(2); files.add(new File("README.md")); //packing files for (File file : files) { //new zip entry and copying inputstream with file to zipOutputStream, after all closing streams zipOutputStream.putNextEntry(new ZipEntry(file.getName())); FileInputStream fileInputStream = new FileInputStream(file); IOUtils.copy(fileInputStream, zipOutputStream); fileInputStream.close(); zipOutputStream.closeEntry(); } if (zipOutputStream != null) { zipOutputStream.finish(); zipOutputStream.flush(); IOUtils.closeQuietly(zipOutputStream); } IOUtils.closeQuietly(bufferedOutputStream); IOUtils.closeQuietly(byteArrayOutputStream); return byteArrayOutputStream.toByteArray(); } 

Pero el problema es que al usar el código, cuando ingreso la URL: localhost: 8080 / zip, obtengo el archivo: test.zip.html en lugar del archivo .zip

Cuando elimino la extensión .html y dejo solo el archivo test.zip, ¿se abre correctamente? ¿Cómo evitar devolver esta extensión .html? ¿Por qué se agrega?

No tengo idea de qué más puedo hacer. También estaba intentando reemplazar ByteArrayOuputStream con algo como:

 OutputStream outputStream = response.getOutputStream(); 

y configura el método para que se anule, de modo que no devuelva nada, pero creó el archivo .zip que estaba … dañado?

En mi macbook después de desempaquetar el test.zip estaba obteniendo test.zip.cpgz que me estaba dando de nuevo el archivo test.zip y así sucesivamente …

En Windows, el archivo .zip se dañó como dije y ni siquiera se pudo abrir.

También supongo que eliminar la extensión .html automáticamente será la mejor opción, pero ¿cómo? Espero que no sea tan difícil como parece 🙂 gracias

Parece estar resuelto. Yo reemplacé

 response.setContentType("application/zip"); 

con:

 @RequestMapping(value = "/zip", produces="application/zip") 

Y ahora tengo claro, hermoso archivo .zip 🙂

Si alguno de ustedes tiene una propuesta mejor o más rápida, o simplemente quiere dar un consejo, entonces adelante, tengo curiosidad.

 @RequestMapping(value="/zip", produces="application/zip") public void zipFiles(HttpServletResponse response) throws IOException { //setting headers response.setStatus(HttpServletResponse.SC_OK); response.addHeader("Content-Disposition", "attachment; filename=\"test.zip\""); ZipOutputStream zipOutputStream = new ZipOutputStream(response.getOutputStream()); // create a list to add files to be zipped ArrayList files = new ArrayList<>(2); files.add(new File("README.md")); // package files for (File file : files) { //new zip entry and copying inputstream with file to zipOutputStream, after all closing streams zipOutputStream.putNextEntry(new ZipEntry(file.getName())); FileInputStream fileInputStream = new FileInputStream(file); IOUtils.copy(fileInputStream, zipOutputStream); fileInputStream.close(); zipOutputStream.closeEntry(); } zipOutputStream.close(); } 

Estoy utilizando el REST Web Service de Spring Boot y he diseñado los puntos finales para devolver siempre ResponseEntity ya sea JSON o PDF o ZIP y se me ocurrió la siguiente solución que está parcialmente inspirada en denov's answer en esta pregunta, así como en otra pregunta donde aprendí cómo convertir ZipOutputStream en byte[] para alimentarlo a ResponseEntity como salida del punto final.

De todos modos, creé una clase de utilidad simple con dos métodos para descargar archivos pdf y zip

 @Component public class FileUtil { public BinaryOutputWrapper prepDownloadAsPDF(String filename) throws IOException { Path fileLocation = Paths.get(filename); byte[] data = Files.readAllBytes(fileLocation); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.parseMediaType("application/pdf")); String outputFilename = "output.pdf"; headers.setContentDispositionFormData(outputFilename, outputFilename); headers.setCacheControl("must-revalidate, post-check=0, pre-check=0"); return new BinaryOutputWrapper(data, headers); } public BinaryOutputWrapper prepDownloadAsZIP(List filenames) throws IOException { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.parseMediaType("application/zip")); String outputFilename = "output.zip"; headers.setContentDispositionFormData(outputFilename, outputFilename); headers.setCacheControl("must-revalidate, post-check=0, pre-check=0"); ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream(); ZipOutputStream zipOutputStream = new ZipOutputStream(byteOutputStream); for(String filename: filenames) { File file = new File(filename); zipOutputStream.putNextEntry(new ZipEntry(filename)); FileInputStream fileInputStream = new FileInputStream(file); IOUtils.copy(fileInputStream, zipOutputStream); fileInputStream.close(); zipOutputStream.closeEntry(); } zipOutputStream.close(); return new BinaryOutputWrapper(byteOutputStream.toByteArray(), headers); } } 

Y ahora, el punto final puede devolver ResponseEntity Como se muestra a continuación utilizando los datos de byte[] y los encabezados personalizados que se adaptan específicamente para pdf o zip .

 @GetMapping("/somepath/pdf") public ResponseEntity generatePDF() { BinaryOutputWrapper output = new BinaryOutputWrapper(); try { String inputFile = "sample.pdf"; output = fileUtil.prepDownloadAsPDF(inputFile); //or invoke prepDownloadAsZIP(...) with a list of filenames } catch (IOException e) { e.printStackTrace(); //Do something when exception is thrown } return new ResponseEntity<>(output.getData(), output.getHeaders(), HttpStatus.OK); } 

El BinaryOutputWrapper es una clase de POJO inmutable simple que creé con private byte[] data; y org.springframework.http.HttpHeaders headers; como campos para devolver data y headers desde el método de utilidad.