how to flush two files from HTTPServeletResponse simultaneously - java

I am in a situation where on calling an API i want to download a excel file along with a zip file.
I have cracked the code for downloading them separately, but when put together only one file gets downloaded and the other one just doesnt gets downloaded.
I guss the problem is I cannot use response.getOutPutStream().flush() or response.flushBuffer() simultaneously.
String absolutePath = context.getRealPath("resources/ZipFolders");
String inputFile = Paths.get(absolutePath + "/Attachments.zip").toAbsolutePath().toString();
File finalFile = new File(inputFile);
ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(finalFile));
String absolutePath2 = context.getRealPath("resources/Spreadsheets");
String inputFile2 = Paths.get(absolutePath2 + "/Validation_Spreadsheet.xlsx").toAbsolutePath().toString();
File file = new File(inputFile2);
byte[] bytes = IOUtils.toByteArray(new FileInputStream(file));
ZipEntry zipEntry = new ZipEntry("Validation_Spreadsheet.xlsx");
zipOut.putNextEntry(zipEntry);
zipOut.write(bytes);
zipOut.closeEntry();
zipOut.close();
response.setContentType("application/zip");
response.setHeader("Content-disposition", "attachment;filename=attachment_trial.zip");
response.getOutputStream().write(IOUtils.toByteArray(new FileInputStream(finalFile)));
System.err.println("above flush>>>>>>>>>>>>>>");
response.getOutputStream().flush();
responsetrial.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
responsetrial.setHeader("Content-disposition", "attachment; filename=TransactionErrors.xlsx");
responsetrial.getOutputStream().write(IOUtils.toByteArray(new FileInputStream(file)));
System.err.println("above flush2>>>>>>>>>>>>>>");
responsetrial.getOutputStream().flush();

It's not possible to download 2 files over a single HTTP request.
You will need to make 2 separately requests for this task.
If you need to download many files in a single "HTML button", you need to write some javascript logic two make this.

Related

Corrupted zip file using ZipOutputStream

I'm trying to create a zip file to be able to send multiple files over http.
My issue is that the Zip file that is generated is "corrupted" before and after the file has been send. The issue is i'm not able to find what i did wrong as i'm getting no errors inside the console.
So does someone has an idea file my generated zip file is corrupted ?
This is my code :
OutputStream responseBody = t.getResponseBody();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipOutputStream zos = new ZipOutputStream(baos);
int counter = 1;
for (PDDocument doc : documents)
{
ZipEntry zipEntry = new ZipEntry("document" + counter);
zos.putNextEntry(zipEntry);
ByteArrayOutputStream docOs = new ByteArrayOutputStream();
doc.save(docOs);
docOs.close();
zos.write(docOs.toByteArray());
zos.closeEntry();
zos.finish();
zos.flush();
counter++;
}
zos.close();
baos.close();
responseBody.write(baos.toByteArray());
responseBody.flush();
Thank you for your help !
You need to remove zos.finish() from inside the loop as it terminates the ZIP entries, as it is handled by zos.close() at end of the stream.
With very large streams you will be better off sending ZIP directly to responseBody bypassing ByteArrayOutputStream memory buffer.
If you are still having problems check the content type of the output is set. It might be easier to debug by temporarily writing the byte[] to file to check the ZIP format you are sending with:
Files.write(Path.of("temp.zip"), baos.toByteArray());
This outline below shows sending a simple ZIP over http (from a servlet, adjust the first 2 lines to appropriate calls for "t"). This may help you check which step of your code causes the corruption if you work back to adding your own document objects inside the loop:
// MUST set response content type:
// resp.setContentType("application/zip");
OutputStream out = resp.getOutputStream(); // or t.getResponseBody();
try(ZipOutputStream zos = new ZipOutputStream(out))
{
while (counter-- > 0)
{
ZipEntry zipEntry = new ZipEntry("document" + counter+".txt");
zos.putNextEntry(zipEntry);
zos.write(("This is ZipEntry: "+zipEntry.getName()+"\r\n").getBytes());
}
}

Java Zip File Created With ByteArrayOutputStream is smaller than with FileOutputStream

The Zip file output from using ByteArrayOuputStream is smaller than that with FileOutputStream.
In Java I am creating a Zip file. Which opens successfully when I use FileOutputStream.
When switching the FileOutputStream to a ByteArrayOutputStream I get a slightly smaller file, about 221 bytes. And this file won't open, but has the same contents as the other but missing 221 bytes.
The logic I use to construct the Zip file is the same in both cases. Therefore I will share the essential logic without the zip creation (let me know if you need this).
ByteArrayOutputStream baos = new ByteArrayOutputStream();
zipOS = new ZipOutputStream(baos);
Then I create the Zip file, and then ...
zipOS.flush();
baos.flush();
baos.writeTo(new FileOutputStream("TEST_AS_BYTESTREAM_1.zip"));
zipOS.close();
baos.close();
I'm using the baos.writeTo() to bypass this to prove it's not the HTTP response but the ByteArrayOutputStream element thats an issue.
When using baos.writeTo(new FileOutputStream("TEST_AS_BYTESTREAM_1.zip"));
I get a smaller file that doesn't open as a Zip.
This logic is in a java controller so that a link clicked on web page will down load the zip file without touching any file systems excepted the users.
Here is the code using FileOutputStream which works...
FileOutputStream baos = new FileOutputStream("TEST_AS_FILE.zip");
zipOS = new ZipOutputStream(baos);
Aside from the the second code snippet isn't relevant as I would need to send the file created on the web server. But the point is, that Fileoutput stream with the same logic and ByteArrayOutputStream has a difference.
The following statement is wrong:
baos.writeTo(new FileOutputStream("TEST_AS_BYTESTREAM_1.zip"));
The javadoc of writeTo(OutputStream out) does not say anything about closing the OutputStream, which means it doesn't, so the last of the data is still sitting buffered in the un-closed, un-flushed FileOutputStream.
The correct way is:
try (OutputStream out = new FileOutputStream("TEST_AS_BYTESTREAM_1.zip")) {
baos.writeTo(out);
}
Also, a ByteArrayOutputStream does not need to be closed or flushed. As the javadoc of close() says it:
Closing a ByteArrayOutputStream has no effect.
You will however need to call finish() on an ZipOutputStream to complete the content. Since closing the zip stream automatically finishes it for you, the correct way to do this is:
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (ZipOutputStream zipOS = new ZipOutputStream(baos)) {
// create the Zip file content here
}
try (OutputStream out = new FileOutputStream("TEST_AS_BYTESTREAM_1.zip")) {
baos.writeTo(out);
}
When writing directly to a file:
try (ZipOutputStream zipOS = new ZipOutputStream(new FileOutputStream("TEST_AS_FILE.zip"))) {
// create the Zip file content here
}
Consider adding a BufferedOutputStream for better performance.
The final answer is as follows. Many thanks to the answer previously given.
Basically I needed to use the .finish() method of the ZipOuputStream. Everything else remained the same.
Pre zip creation code same as before
ByteArrayOutputStream baos = new ByteArrayOutputStream();
zipOS = new ZipOutputStream(baos);
Post zip creation...
zipOS.flush();
zipOS.finish();
baos.flush();
documentBody = baos.toByteArray();
zipOS.close();
baos.close();
Then my HTTP response is
HttpHeaders headers = new HttpHeaders();
headers.setContentType(new MediaType("application", "zip"));
headers.setContentLength(documentBody.length);
headers.add("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
ByteArrayResource resource = new ByteArrayResource(documentBody);
return ResponseEntity.ok()
.headers(headers)
.body(resource);
This way I avoid the creation of a file which is nice.
Many thanks for the assistance.

Unable to open S3 zip file downloaded using spring-boot

My AWS S3 contains some files and I want to download those files through my application as a zip file. I'm able to download the zip file successfully but while the file I'm getting an error Unexpected end of archive and also the CSV file size is 0 inside the zip.
I did dig a lot but not able to understand the exact solutions.
Here's the code:
String zipFileName = "MyZipFile.zip";
String fileName = "test.csv";
filePath = new String(Base64.getDecoder().decode(filePath));
System.out.println("file:" + filePath);
// get file from S3
S3Object s3Obj = awsClient.downloadFile(filePath);
byte[] s3Bytes = s3Obj.getObjectContent().readAllBytes();
// create zip
ByteArrayOutputStream byteArrOutputStream = new ByteArrayOutputStream(s3Bytes.length);
ZipOutputStream zipOutStream = new ZipOutputStream(byteArrOutputStream);
ZipEntry zip = new ZipEntry(fileName);
zipOutStream.putNextEntry(zip);
zipOutStream.write(s3Bytes, 0, s3Bytes.length);
byte[] streamBytes = byteArrOutputStream.toByteArray();
// close streams
zipOutStream.closeEntry();
zipOutStream.close();
closeQuietly(byteArrOutputStream);
// prepare download
System.out.println("streamBytes:" + streamBytes + " len:" + streamBytes.length);
System.out.println("s3Bytes:" + s3Bytes + " len:" + s3Bytes.length);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentLength(streamBytes.length);
headers.setContentDispositionFormData("attachment", zipFileName);
I think you should close your zip output stream to have everything written to the underlying byte array stream before you extract the byte array... Ie reorder the lines to:
// close streams
zipOutStream.closeEntry();
zipOutStream.close();
byte[] streamBytes = byteArrOutputStream.toByteArray();
closeQuietly(byteArrOutputStream);

Zip multiple objects from inputStream in Java

I have a webapp that allows users to select images and then download them. For a single image, I use HTML5's anchor download and it works beautifully. Now I need to allow them to select multiple images, and download them as a .zip file. I'm using an api to get each image as an InputStream and returning a Jersey Response.
I'm new to zipping and I'm a bit confused with how zipping with InputStream should work.
For single images, it works like so:
try {
InputStream imageInputStream = ImageStore.getImage(imageId);
if (imageInputStream == null) {
XLog.warnf("Unable to find image [%s].", imageId);
return Response.status(HttpURLConnection.HTTP_GONE).build();
}
Response.ResponseBuilder response = Response.ok(imageInputStream);
response.header("Content-Type", imageType.mimeType());
response.header("Content-Disposition", "filename=image.jpg");
return response.build();
}
It's not much, but here's the java I have so far for multiple images
public Response zipAndDownload(List<UUID> imageIds) {
try {
// TODO: instantiate zip file?
for (UUID imageId : imageIds) {
InputStream imageInputStream = ImageStore.getImage(imageId);
// TODO: add image to zip file (ZipEntry?)
}
// TODO: return zip file
}
...
}
I just don't know how to deal with multiple InputStreams, and it seems that I shouldn't have multiple, right?
An InputStream per image is ok. To zip the files you need to create a .zip file for them to live in and get a ZipOutputStream to write to it:
File zipFile = new File("/path/to/your/zipFile.zip");
ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile)));
For each image, create a new ZipEntry, add it to the ZipOutputSteam, then copy the bytes from your image's InputStream to the ZipOutputStream:
ZipEntry ze = new ZipEntry("PrettyPicture1.jpg");
zos.putNextEntry(ze);
byte[] bytes = new byte[1024];
int count = imageInputStream.read(bytes);
while (count > -1)
{
zos.write(bytes, 0, count);
count = imageInputStream.read(bytes);
}
imageInputStream.close();
zos.closeEntry();
After you add all the entries, close the ZipOutputStream:
zos.close();
Now your zipFile points to a zip file full of pictures you can do whatever you want with. You can return it like you do with a single image:
BufferedInputStream zipFileInputStream = new BufferedInputStream(new FileInputStream(zipFile));
Response.ResponseBuilder response = Response.ok(zipFileInputStream);
But the content type and disposition are different:
response.header("Content-Type", MediaType.APPLICATION_OCTET_STREAM_TYPE);
response.header("Content-Disposition", "attachment; filename=zipFile.zip");
Note: You can use the copy method from Guava's ByteStreams helper to copy the streams instead of copying the bytes manually. Simply replace the while loop and the 2 lines before it with this line:
ByteStreams.copy(imageInputStream, zos);

zipping binary data in java

I am trying to zip a group of binary data (result set returned from database) into a single file. Which can be downloaded via web application. Following code is used to zip the result set and write the zip file to HttpServletResponse
String outFilename = "outfile.zip";
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment; filename= " + outFilename);
OutputStream os = response.getOutputStream();
ZipOutputStream out = new ZipOutputStream(os);
for (int i = 0; i < cardFileList.size(); i++) {
CardFile cardFile = cardFileList.get(i);
out.putNextEntry(new ZipEntry(cardFile.getBinaryFileName()));
out.write(cardFile.getBinaryFile(), 0, cardFile.getBinaryFile().length);
out.closeEntry();
}
// Complete the ZIP file
out.flush();
out.close();
os.close();
The problem is that while unzipping the downloaded zip file using WinRar I get following error :
File Path: Either multipart or corrupt ZIP archive
Can someone point out where am I making mistake?. Any help would be appreciated.
[EDIT] I tried response.setContentType("application/zip"); but same result.
The following code works for me:
FileOutputStream os = new FileOutputStream("out.zip");
ZipOutputStream zos = new ZipOutputStream(os);
try
{
for (int i = 1; i <= 5; i++)
{
ZipEntry curEntry = new ZipEntry("file" + i + ".txt");
zos.putNextEntry(curEntry);
zos.write(("Good morning " + i).getBytes("UTF-8"));
}
}
finally
{
zos.close();
}
Zip files generated with this code opens up with 7-zip without problem.
Check that the response from the servlet is not actually a 404 or 500 error page. Pick a small response and open it up with a hex editor or even a text editor. Zip files start with a 'PK' magic number which should be visible even in a text editor.
Try saving to file instead of servlet output stream for starters and see if that makes a difference.
Could there be a filter modifying your servlet's output / corrupting the ZIP?
Your code looks correct for producing the file. So the problem is probably in your downloading.
look at the file you have downloaded. What is it's size?
try a tool other than your WinRar and see what you have. 7Zip is decent.

Categories