Spring REST - create ZIP file and send it to the client - java

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:
How to avoid returning this .html extension?
Why is it added?
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

The problem is solved.
I replaced:
response.setContentType("application/zip");
with:
#RequestMapping(value = "/zip", produces="application/zip")
And now I get a 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.

Related

Generated zip file is always corrupted after download on Mac

Lets say that I have a collection of objects which I would like to wrap with zip. My zip conversion class looks like:
public class ZipFile {
public byte[] createZipByteArray(String fileName, byte[] content) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try (byteArrayOutputStream; ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {
zipOutputStream.setLevel(Deflater.NO_COMPRESSION);
ZipEntry zipEntry = new ZipEntry(fileName);
zipOutputStream.putNextEntry(zipEntry);
zipOutputStream.write(content);
zipOutputStream.closeEntry();
zipOutputStream.flush();
}
return byteArrayOutputStream.toByteArray();
}
}
My controller looks like:
#GetMapping(value = "objectWithDetails/zip", produces = "application/zip")
#RolesAllowed("QUERY_DATA")
public ResponseEntity<byte[]> getObjectWithDetails(String limit, HttpServletResponse response) throws IOException {
response.setContentType("application/zip");
List<ObjectSpecification> objectRepresentation = recipeService.getObjectRepresentation(limit);
byte[] objectsBytes = objectMapper.writeValueAsBytes(objectRepresentation);
ZipFile zipFile = new ZipFile();
return ResponseEntity
.ok()
.contentType(MediaType.valueOf("application/zip"))
.header("Content-Encoding", "UTF-8")
.header("Content-Disposition", String.format("attachment; filename=\"details_%s.zip\"", dateTimeProvider.nowDate()))
.body(zipFile.createZipByteArray(String.format("details_%s.zip", dateTimeProvider.nowDate()), objectsBytes));
}
In swagger, I have a download option for the generated zip files. After all, when I try to open the downloaded file I receive the following:
Unable to expand filename.zip. It is in an unsupported format.
I tried closing ZipOutputStream and ByteArrayOutputStream in a different configuration. Applied a different option for the content type in the controller but still, I cannot find out why all zip files are corrupted. I will be grateful for suggestions on what I miss. Cheers!
I managed to find out the root cause with #k314159
, namely, I had a zip file wrapped with a zip file. New version which works as expected.
public static byte[] createZipByteArray(String fileName, byte[] content) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try (byteArrayOutputStream; ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream)) {
zipOutputStream.setLevel(Deflater.DEFLATED);
ZipEntry zipEntry = new ZipEntry(fileName);
zipOutputStream.putNextEntry(zipEntry);
zipOutputStream.write(content);
zipOutputStream.closeEntry();
zipOutputStream.flush();
}
return byteArrayOutputStream.toByteArray();
}
and controller
#GetMapping(value = "objectWithDetails", produces = "application/zip")
#RolesAllowed("QUERY_DATA")
public ResponseEntity<byte[]> getObjectWithDetails(String limit, HttpServletResponse response) throws IOException {
response.setContentType("application/zip");
List<ObjectSpecification > objectRepresentation = objectService.getObjectRepresentation(limit);
byte[] objectsBytes = objectMapper.writeValueAsBytes(objectRepresentation);
return ResponseEntity
.ok()
.contentType(MediaType.valueOf("application/zip"))
.header("Content-Encoding", "UTF-8")
.header("Content-Disposition", getObjectFileName("attachment; filename=objects_"))
.body(createZipByteArray(getObjectFileName("objects_"), objectsBytes));
}

MultipartFile issue, unable to convert to File

I am trying to to upload above 1 GB file, I am using Spring Boot.
I've tried with below code, but I am getting as Out of Memory error.
public void uploadFile(MultipartFile file) throws IOException {
try {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);
restTemplate.setRequestFactory(requestFactory);
String uploadFile= restTemplate.exchange(url, HttpMethod.POST,
new HttpEntity<>(new FileSystemResource(convert(file)), headers), String.class).getBody();
} catch (Exception e) {
throw new RuntimeException("Exception Occured", e);
}
}
private static File convert(MultipartFile file) throws IOException {
File convFile = new File(file.getOriginalFilename());
convFile.createNewFile();
FileOutputStream fos = new FileOutputStream(convFile);
fos.write(file.getBytes());
fos.close();
return convFile;
}
The Main problem I am facing is, I am unable to convert MultipartFile to java.io.File.
I've even tried replacing FileSystemResource with ByteArrayResource, but still getting OOM error.
I've even tried using below code too:
private static File convert(MultipartFile file) throws IOException {
CommonsMultipartFile commonsMultipartFile = (CommonsMultipartFile) multipartFile;
FileItem fileItem = commonsMultipartFile.getFileItem();
DiskFileItem diskFileItem = (DiskFileItem) fileItem;
String absPath = diskFileItem.getStoreLocation().getAbsolutePath();
File file = new File(absPath);
}
But I am getting below exception for above snippet:
org.springframework.web.multipart.commons.CommonsMultipartFile cannot
be cast to org.springframework.web.multipart.MultipartFile
Could anyone please tell me on how to convert MultipartFile to java.io.File?
And also is there any other approach better than FileSystemResource bcoz I will have to create new file in server everytime before uploading. If file is more than 1GB, another 1 GB new file has to be created on server side, and has to manually delete that file again, which I personally didn't like this approach.
getBytes() tries to load the whole byte array into memory which is causing your OOM what you need to do is stream the file and write it out.
Try the following:
private static Path convert(MultipartFile file) throws IOException {
Path newFile = Paths.get(file.getOriginalFilename());
try(InputStream is = file.getInputStream();
OutputStream os = Files.newOutputStream(newFile))) {
byte[] buffer = new byte[4096];
int read = 0;
while((read = is.read(buffer)) > 0) {
os.write(buffer,0,read);
}
}
return newFile;
}
I changed your method to return a Path instead of File which is part of the java.nio package. The package is preferred over java.io as its been optimized more.
If you do need a File object you can call newFile.toFile()
Since it returns a Path object you can use the java.nio.file.Files class to relocate the file to your preferred directory once it has been written out
private static void relocateFile(Path original, Path newLoc) throws IOException {
if(Files.isDirectory(newLoc)) {
newLoc = newLoc.resolve(original.getFileName());
}
Files.move(original, newLoc);
}

Compressing(zip) List of files with ZipOutPutStream Java

I`m trying to compress a list of Xml converted on Strings, save them in only one zip file and returning as a body of a POST on restful. But everytime I save the file I get the error "The archive is either in unknown format or damaged".
protected ByteArrayOutputStream zip(Map<String, String> mapConvertedXml) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipOutputStream zos = new ZipOutputStream(baos);
try {
for(Map.Entry<String, String> current : mapConvertedXml.entrySet()){
ZipEntry entry = new ZipEntry(current.getKey() + ".xml");
entry.setSize(current.getValue().length());
zos.putNextEntry(entry);
zos.write(current.getValue().getBytes());
zos.flush();
}
zos.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
return baos;
}
Can anyone help me with that?
To test your zip file please save it temporarily in your filesystem and open it manualy.
FileOutputStream fos = new FileOutputStream(pathToZipFile);
ZipOutputStream zos = new ZipOutputStream(fos);
Then, you can use something like that to construct your Rest Server and return your file.
public static Response getFileLast(#Context UriInfo ui, #Context HttpHeaders hh) {
Response response = null;
byte[] sucess = null;
ByteArrayOutputStream baos = getYourFile();
sucess = baos.toByteArray();
if(sucess!=null) {
response = Response.ok(sucess, MediaType.APPLICATION_OCTET_STREAM).header("content-disposition","attachment; filename = YourFileName").build();
} else {
response = Response.status(Status.NOT_FOUND).type(MediaType.APPLICATION_JSON).entity(new Error("Error downloading file.")).build();
}
return response;
}
You can test this Advanced Rest Client for example.

Compress dynamic content to ServletOutputStream

I want to compress the dynamic created content and write to ServletOutputStream directly, without saving it as a file on the server before compressing.
For example, I created an Excel Workbook and a StringBuffer that includes strings with the SQL template. I don't want to save the dynamic content to .xlsx and .sql file on the server before zipping the files and writing to ServletOutputStream for downloading.
Sample Code:
ServletOutputStream out = response.getOutputStream();
workbook.write(byteArrayOutputStream);
zipIt(byteArrayOutputStream,out);
public static boolean zipIt(ByteArrayOutputStream input, ServletOutputStream output) {
try {
ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(output));
ZipEntry zipEntry = new ZipEntry("test.xlsx");
zos.putNextEntry(zipEntry);
if (input != null) {
zipEntry.setSize(input.size());
zos.write(input.toByteArray());
zos.closeEntry();
}
} catch (IOException e) {
logger.error("error {}", e);
return false;
}
return true;
}
Create an HttpServlet and in the doGet() or doPost() method create a ZipOutputStream initialized with the ServletOutputStream and write directly to it:
resp.setContentType("application/zip");
// Indicate that a file is being sent back:
resp.setHeader("Content-Disposition", "attachment;filename=test.zip");
// workbook.write() closes the stream, so we first have to
// write it to a "buffer", a ByteArrayOutputStream
ByteArrayOutputStream baos = new ByteArrayOutputStream();
workbook.write(baos);
byte[] data = baos.toByteArray();
try (ZipOutputStream out = new ZipOutputStream(resp.getOutputStream())) {
// Here you can add your content to the zip
ZipEntry e = new ZipEntry("test.xlsx");
// Configure the zip entry, the properties of the file
e.setSize(data.length);
e.setTime(System.currentTimeMillis());
// etc.
out.putNextEntry(e);
// And the content of the XLSX:
out.write(data);
out.closeEntry();
// You may add other files here if you want to
out.finish();
} catch (Exception e) {
// Handle the exception
}
}

Zip file created on server and download that zip, using java

I have the below code got from mkyong, to zip files on local. But, my requirement is to zip files on server and need to download that. Could any one help.
code wrote to zipFiles:
public void zipFiles(File contentFile, File navFile)
{
byte[] buffer = new byte[1024];
try{
// i dont have idea on what to give here in fileoutputstream
FileOutputStream fos = new FileOutputStream("C:\\MyFile.zip");
ZipOutputStream zos = new ZipOutputStream(fos);
ZipEntry ze= new ZipEntry(contentFile.toString());
zos.putNextEntry(ze);
FileInputStream in = new FileInputStream(contentFile.toString());
int len;
while ((len = in.read(buffer)) > 0) {
zos.write(buffer, 0, len);
}
in.close();
zos.closeEntry();
//remember close it
zos.close();
System.out.println("Done");
}catch(IOException ex){
ex.printStackTrace();
}
}
what could i provide in fileoutputstream here? contentfile and navigationfile are files i created from code.
If your server is a servlet container, just write an HttpServlet which does the zipping and serving the file.
You can pass the output stream of the servlet response to the constructor of ZipOutputStream and the zip file will be sent as the servlet response:
ZipOutputStream zos = new ZipOutputStream(response.getOutputStream());
Don't forget to set the response mime type before zipping, e.g.:
response.setContentType("application/zip");
The whole picture:
public class DownloadServlet extends HttpServlet {
#Override
public void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException {
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment; filename=data.zip");
// You might also wanna disable caching the response
// here by setting other headers...
try ( ZipOutputStream zos = new ZipOutputStream(response.getOutputStream()) ) {
// Add zip entries you want to include in the zip file
}
}
}
Try this:
#RequestMapping(value="download", method=RequestMethod.GET)
public void getDownload(HttpServletResponse response) {
// Get your file stream from wherever.
InputStream myStream = someClass.returnFile();
// Set the content type and attachment header.
response.addHeader("Content-disposition", "attachment;filename=myfilename.txt");
response.setContentType("txt/plain");
// Copy the stream to the response's output stream.
IOUtils.copy(myStream, response.getOutputStream());
response.flushBuffer();
}
Reference

Categories

Resources