Java Zip File Created With ByteArrayOutputStream is smaller than with FileOutputStream - java

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.

Related

How to download the folder from minio as zip file?

The steps I followed are.
get all objects from recursive objects
Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder()
.bucket(bucketName).recursive(true).build());
Then getting all streams of matching the prefix
InputStream stream = minioClient.getObject(GetObjectArgs.builder()
.bucket(bucketName).object(objectName).build());
the list of multiple stream got by the InputStream stream How do we convert it into zip file ?
tried the following code but it's (zipOut) coming as null.
downloading empty zip, How do we fix this ?
ByteArrayOutputStream fos = new ByteArrayOutputStream();
ZipOutputStream zipOut = new ZipOutputStream(fos);
ZipEntry zipEntry1 = new ZipEntry(objectName);
zipEntry1.setSize(resource.contentLength());
zipEntry1.setTime(System.currentTimeMillis());
zipOut.putNextEntry(zipEntry1);
StreamUtils.copy(stream.readAllBytes(), zipOut);
zipOut.closeEntry();
Thanks in advance.

How do I send an image to the output in JSP

I want to open my servlet which basically checks my permissions and if allowed, shows me a picture like it would be a real jpeg. My code so far:
File file = new File("C:/test.jpeg");
FileInputStream fis = new FileInputStream(file);
byte imageBytes[] = new byte[(int) file.length()];
response.setContentType("image/jpeg");
response.setContentLength(imageBytes.length);
response.getOutputStream().write(imageBytes);
response.getOutputStream().flush();
But somehow the image it tries to show me in the browser is corrupted. I already checked that the file exists, as the image.length is not zero. What did I do wrong?
You should start using the newer NIO.2 File API that was added in Java 7, mostly the Files class, because it makes the job much easier.
Option 1: Load file into byte[]
byte[] imageBytes = Files.readAllBytes(Paths.get("C:/test.jpeg"));
response.setContentType("image/jpeg");
response.setContentLength(imageBytes.length);
try (OutputStream out = response.getOutputStream()) {
out.write(imageBytes);
}
Option 2: Stream the file without using a lot of memory (recommended)
Path imageFile = Paths.get("C:/test.jpeg");
response.setContentType("image/jpeg");
response.setContentLength((int) Files.size(imageFile));
try (OutputStream out = response.getOutputStream()) {
Files.copy(imageFile, out);
}

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());
}
}

how to flush two files from HTTPServeletResponse simultaneously

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.

in memory file zip in java

I am trying to read multiple files (can be of any format i.e. pdf, txt, tiff etc) from URLs and zipping them using ZipOutputStream. My code looks like this:
// using in-memory file read
// then zipping all these files in-memory
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipOutputStream zos = new ZipOutputStream(baos);
.....
URL url = new URL(downloadUrl); // can be multiple URLs
ByteArrayOutputStream bais = new ByteArrayOutputStream();
InputStream is = url.openStream();
byte[] byteChunk = new byte[4096];
int n;
while ( (n = is.read(byteChunk)) > 0 )
{
bais.write(byteChunk, 0, n);
}
byte[] fileBytes = bais.toByteArray();
ZipEntry entry = new ZipEntry(fileName);
entry.setSize(fileBytes.length);
zos.putNextEntry(entry);
zos.write(fileBytes);
zos.closeEntry();
// close the url input stream
is.close();
// close the zip output stream
zos.close();
// read the byte array from ByteArrayOutputStream
byte[] zipFileBytes = baos.toByteArray();
String fileContent = new String(zipFileBytes);
I am then passing this content "fileContent" to my perl frontend application.
And I m using perl code to download this zipped file:
WPHTTPResponse::setHeader( 'Content-disposition', 'attachment; filename="test.zip"' );
WPHTTPResponse::setHeader( 'Content-type', 'application/zip');
print $result; // string coming from java application
But the zip file it is giving is corrupted. I think something in going wrong with the data translation.
I'd appreciate any help.
Your problem is thinking that you can output your zip bytes into a string. This string can not be used to reproduce the zip content again. You need to either use the raw bytes or encode the bytes into something that can be represented as a string, such as base64 encoding.

Categories