I want to create a ZIP file that contains my archived files that I received from the backend, and then send this file to a user. For 2 days I have been looking for the answer and can't find proper solution, maybe you can help me :)
For now, the code is like this (I know I shouldn't do it all in the Spring controller, but don't care about that, it is just for testing purposes, to find the way to make it works):
@RequestMapping(value = "/zip")
public byte[] zipFiles(HttpServletResponse response) throws IOException {
// Setting HTTP headers
response.setContentType("application/zip");
response.setStatus(HttpServletResponse.SC_OK);
response.addHeader("Content-Disposition", "attachment; filename=\"test.zip\"");
// Creating byteArray stream, make it bufferable and passing this buffer to ZipOutputStream
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(byteArrayOutputStream);
ZipOutputStream zipOutputStream = new ZipOutputStream(bufferedOutputStream);
// Simple file list, just for tests
ArrayList<File> 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();
}
But the problem is, that using the code, when I enter the URL localhost:8080/zip
, I get a file test.zip.html
instead of .zip
file.
When I remove .html
extension and leave just test.zip
it opens correctly. So my questions are:
.html
extension?I have no idea what else can I do. I was also trying replace ByteArrayOuputStream
with something like:
OutputStream outputStream = response.getOutputStream();
and set the method to be void so it returns nothing, but It created .zip
file which was damaged?
On my MacBook after unpacking the test.zip
I was getting test.zip.cpgz
which was again giving me test.zip
file and so on.
On Windows the .zip file was damaged as I said and couldn't even open it.
I also suppose, that removing .html
extension automatically will be the best option, but how?
Hope it is no as hard as It seems to be :)
Thanks
While downloading multiple files, we can create a zip file in spring boot and download that zip file alone rather then downloading multiple files individually. For this purpose, we first need to create a zip file in spring boot and then set the content type as application/zip to download the zip file.
Press and hold (or right-click) the file or folder, select (or point to) Send to, and then select Compressed (zipped) folder. A new zipped folder with the same name is created in the same location.
Steps to Compress a File in Java Open a ZipOutputStream that wraps an OutputStream like FileOutputStream. The ZipOutputStream class implements an output stream filter for writing in the ZIP file format. Put a ZipEntry object by calling the putNextEntry(ZipEntry) method on the ZipOutputStream.
seems to be solved. I replaced:
response.setContentType("application/zip");
with:
@RequestMapping(value = "/zip", produces="application/zip")
And now I get clear, beautiful .zip file :)
If any of you have either better or faster proposition, or just want to give some advice, then go ahead, I am curious.
@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<File> 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();
}
@RequestMapping(value="/zip", produces="application/zip")
public ResponseEntity<StreamingResponseBody> zipFiles() {
return ResponseEntity
.ok()
.header("Content-Disposition", "attachment; filename=\"test.zip\"")
.body(out -> {
var zipOutputStream = new ZipOutputStream(out);
// create a list to add files to be zipped
ArrayList<File> 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();
});
}
I am using REST Web Service
of Spring Boot
and I have designed the endpoints to always return ResponseEntity
whether it is JSON
or PDF
or ZIP
and I came up with the following solution which is partially inspired by denov's answer
in this question as well as another question where I learned how to convert ZipOutputStream
into byte[]
in order to feed it to ResponseEntity
as output of the endpoint.
Anyway, I created a simple utility class with two methods for pdf
and zip
file download
@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<String> 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);
}
}
And now the endpoint can easily return ResponseEntity<?>
as shown below using the byte[]
data and custom headers that is specifically tailored for pdf
or 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);
}
The BinaryOutputWrapper
is a simple immutable POJO
class I created with private byte[] data;
and org.springframework.http.HttpHeaders headers;
as fields in order to return both data
and headers
from utility method.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With