I have a question regarding dynamically creating and streaming ZIP files. I have multiple large files stored on remote HTTP servers (for example Amazon S3).
Now I want the user to download let's say 100 files as one ZIP file.
I could download all 100 files, zip them and stream them to the user, but that would be wasting lots of resources. So my approach is download the first file, stream it to the user, download the next file, stream it to the user and so on.
This is the test code:
public class TestController extends Controller {
public Result test() throws Exception {
InputStream is = getDynamicStreamSomewhere();
response().setContentType("application/zip");
response().setHeader("Content-Disposition", "attachment;filename=test.zip");
return ok(is);
}
private InputStream getDynamicStreamSomewhere() throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipOutputStream zos = new ZipOutputStream(baos);
String url1 = "http://www.example.com/largefile1.bin";
String url2 = "http://www.example.com/largefile2.bin";
ZipEntry entry1 = new ZipEntry("file1.bin");
zos.putNextEntry(entry1);
URL website = new URL(url2);
InputStream in = website.openStream();
IOUtils.copy(in, zos);
in.close();
zos.closeEntry();
ZipEntry entr2 = new ZipEntry("file2.bin");
zos.putNextEntry(entr2);
URL websitea = new URL(url1);
InputStream ina = websitea.openStream();
IOUtils.copy(ina, zos);
ina.close();
zos.closeEntry();
return new ByteArrayInputStream(baos.toByteArray());
}
}
But as far as I debugged it, that does not really stream file by file but download everything and then stream it to the user.
What I am missing is something like flushing the output buffer to the user after every file (or maybe after each 4KB block).
I know how to do it with Java servlets, but not with Play Framework. Any help is appreciated!
Thank you!
Related
There are already many similar questions and tutorials available about this topic still I didn't found what I wanted to do.
I want to make an API, though which I can upload the zip file, that zip is containing xml files I am reading those files and sending the content as response.
The only problem here is currently I am storing the file inside of my project directory, but I don't want that I want to read the file directly without storing it in an directory.
So basically user will hit my API, upload the file and in response he will get the data present inside of files in that zip.
My Code:
#RequestMapping(value ="/upload", method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<Object> uploadFile(#RequestParam("file")MultipartFile file) throws IOException {
System.out.println(file.getOriginalFilename());
File converFile = new File("src/main/"+file.getOriginalFilename());
converFile.createNewFile();
FileOutputStream fout = new FileOutputStream(converFile);
fout.write(file.getBytes());
//unzipping file and reading data;
String zipFileName = "src/main/"+file.getOriginalFilename();
List<String> str = new ArrayList<String>();
byte[] buffer = new byte[1024];
ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFileName));
ZipEntry zipEntry;
int read;
while ((zipEntry = zis.getNextEntry())!= null) {
while ((read = zis.read(buffer, 0, 1024)) >= 0) {
str.add(new String(buffer,0,read));
}
}
while (zipEntry != null){
zipEntry = zis.getNextEntry();
}
zis.closeEntry();
zis.close();
System.out.println("Unzip complete");
System.out.println("--------------------------------");
System.out.println("List"+ str);
fout.close();
return new ResponseEntity<>(str, HttpStatus.OK);
}
This code is working fine uploading file saving it in project directory (src/main) and reading file data and giving the data as response.
How can I eliminate the saving part and do all these in code only.
You can use ByteArrayInputStream instead of FileInputStream, like this:
ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(file.getBytes()));
I have create Rest Service and I am trying to Generate Zip file. This Zip file created from muliple PDF files which are downloaded using method InputStream inpuStream = new URL(url).openStream() . I am able to Generate Zip file Which included PDF files but PDF files are broken.
Even If i try to Generate it from String its coming as broken PDF and i am getting Error message "Not a supported File Type or file is broken or damaged". Its simple code but seems like i am unable to track the mistake.
I have provided my controller , service method for your reference.
1)Controller:
#GetMapping("/getZipFile")
public void getZipFile(HttpServletResponse response) throws RestException {
try {
ByteArrayOutputStream baos = generateZipService.getZipFile();
ServletOutputStream responseOutPutStream = response.getOutputStream();
response.setContentType("APPLICATION/OCTET-STREAM");
response.setStatus(HttpServletResponse.SC_OK);
response.addHeader("Content-Disposition", "attachment; filename=\"GeneratedZipFile.zip\"");
responseOutPutStream.write(baos.toByteArray());
responseOutPutStream.flush();
} catch (Exception e) {
throw new RestException("Error In downloading Zip File");
}
}
2)Service Method
public ByteArrayOutputStream getZipFile() throws Exception{
List<ZipFileName> zipFileNames= zipFileNameDao.getZipFileName();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ZipOutputStream zipOut= new ZipOutputStream(baos);
for (String fileName : zipFileNames) {
InputStream inpuStream = new ByteArrayInputStream( "this is test to generarte pdf test file this is test tdfsfs this is test to generarte pdf test file this is test tdfsfs".getBytes(Charsets.UTF_8) );
createZipFile(inpuStream,zipOut,fileName);
inpuStream.close();
}
zipOut.flush();
baos.flush();
zipOut.close();
baos.close();
return baos;
}
3)createzipfile from service method :
```private void createZipFile(InputStream inputStream, ZipOutputStream zipOut,String fileName) throws IOException {
ZipEntry zipEntry = new ZipEntry(fileName+".pdf");
BufferedInputStream bis = new BufferedInputStream(inputStream);
zipOut.putNextEntry(zipEntry);
zipOut.write(IOUtils.toByteArray(inputStream));
zipOut.closeEntry();
bis.close();
inputStream.close();
}
Also , Another question is about using channels. I read channels are better when you have large files to downlaod from server . I have less then 20 kb of file so should I use Java.nio or just Zipoutputstream is fine.
I try with "response.setContentType("APPLICATION/ZIP")" but it didnt change the outcome of the project.
Thank you for your help..
The code worked fine only thing missing was to pass authentication with the openStream() method because of which I was getting the broken PDF. I opened the pdf with notepad++ and found the error ..
I resolved it.
Thank you
I try to create a zip file and on-the-fly via NanoHTTPD
That is what I currently have:
#Override
public Response serve(IHTTPSession session) {
String uri = session.getUri();
if (uri.toString().equals("/test.zip")) {
try {
PipedOutputStream outPipe = new PipedOutputStream();
ZipEncryptOutputStream zeos = new ZipEncryptOutputStream(outPipe, "Foo");
ZipOutputStream zos = new ZipOutputStream(zeos);
File file = new File("Test.txt");
PipedInputStream inPipe = new PipedInputStream(outPipe, 2048);
ZipEntry ze = new ZipEntry("Test.txt");
zos.putNextEntry(ze);
FileInputStream fis = new FileInputStream(file);
IOUtils.copy(fis, zos);
fis.close();
zos.closeEntry();
zos.close();
return newChunkedResponse(Response.Status.OK, "application/zip", inPipe);
But when debugging - of course - takes some time, because everything is saved to the ZIP file first.
I guess I have to do the writing to zos somehow in a call back and after wards close the entry and stream? But how?
Is that an efficient way of doing it (I am aiming on Android) ? Creating temp files would be much easier - but it should work on low-end smartphones and also the initial wait of creating the zip file (around 40 MB) should be low when downloading.
Hello Stack Overflow community,
I am doing multistep processing on some data I am receiving with a java Servlet. The current process I have is that I input the files to a server using Apache File Upload and convert them to a File. Then once input1 is populated with data, I run through a flow similar to this (where the process functions are xsl transforms):
File input1 = new File(FILE_NAME); // <---this is populated with data
File output1 = new File(TEMP_FILE); // <---this is the temporary file
InputStream read = new FileInputStream(input1);
OuputStream out = new FileOutputStream(output1);
process1ThatReadsProcessesOutputs( read, out);
out.close();
read.close();
//this is basically a repeat of the above process!
File output2 = new File(RESULT_FILE); // <--- This is the result file
InputStream read1 = new FileInputStream(output1);
OutputStream out1 = new FileOutputStream(output2);
Process2ThatReadsProcessesOutputs( read1, out1);
read1.close();
out1.close();
…
So my question is if there is a better way to do this so I do not have to create those temporary Files and recreate streams to those Files? (I am assuming I am incurring a decent performace penatly)
I saw this Most Efficient Way to create InputStream from OutputStream but I am not sure if this is the best route to go...
Just replace FileOutputStream to ByteArrayInputStream vice/versa.
Example:
ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
I don't know why are you converting the FileItem retrieved with Apache Commons if you don't really needed. You can use the same InputStream that each FileItem has to using and read the content of the uploaded file:
// create/retrieve a new file upload handler
ServletFileUpload upload = ...;
// parse the request
List<FileItem> items = (List<FileItem>) upload.parseRequest(request);
/* get the FileItem from the List. Yes, it's not a best practice because you must verify
how many you receive, and check everything is ok, etc.
Let's suppose you've done it */
//...
FileItem item = items.get(0);
// get the InputStrem to read the contents of the file
InputStream is = item.getInputStream();
So finally, you can use the InputStream object to read the uploaded stream sent by the client avoiding unnecessary instantiations.
And yes, it's really recommended to use Buffered clases like BufferedInputStream and BufferedOutputStream.
The other idea could be to avoid FileOutputStream (the middle one) and replace it with ByteArrayOutputStream if you don't need to be written in disk (always is slower than working in memory).
Java 9 brings a new answer to the question:
// All bytes from an InputStream at once
byte[] result = new ByteArrayInputStream(buf)
.readAllBytes();
// Directly redirect an InputStream to an OutputStream
new ByteArrayInputStream(buf)
.transferTo(System.out);
My goal is to put multiple java.io.File objects into a zip file and print to HttpServletResponse for the user to download.
The files were created by the JAXB marshaller. It's a java.io.File object, but it's not actually on the file system (it's only in memory), so I can't create a FileInputStream.
All resources I've seen use the OutputStream to print zip file contents. But, all those resources use FileInputStream (which I can't use).
Anyone know how I can accomplish this?
Have a look at the Apache Commons Compress library, it provides the functionality you need.
Of course "erickson" is right with his comment to your question. You will need the file content and not the java.io.File object. In my example I assume that you have a method
byte[] getTheContentFormSomewhere(int fileNummer) which returns the file content (in memory) for the fileNummer-th file. -- Of course this function is poor design, but it is only for illustration.
It should work a bit like this:
void compress(final OutputStream out) {
ZipOutputStream zipOutputStream = new ZipOutputStream(out);
zipOutputStream.setLevel(ZipOutputStream.STORED);
for(int i = 0; i < 10; i++) {
//of course you need the file content of the i-th file
byte[] oneFileContent = getTheContentFormSomewhere(i);
addOneFileToZipArchive(zipOutputStream, "file"+i+"."txt", oneFileContent);
}
zipOutputStream.close();
}
void addOneFileToZipArchive(final ZipOutputStream zipStream,
String fileName,
byte[] content) {
ZipArchiveEntry zipEntry = new ZipArchiveEntry(fileName);
zipStream.putNextEntry(zipEntry);
zipStream.write(pdfBytes);
zipStream.closeEntry();
}
Snipets of your http controller:
HttpServletResponse response
...
response.setContentType("application/zip");
response.addHeader("Content-Disposition", "attachment; filename=\"compress.zip\"");
response.addHeader("Content-Transfer-Encoding", "binary");
ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream();
compress(outputBuffer);
response.getOutputStream().write(outputBuffer.toByteArray());
response.getOutputStream().flush();
outputBuffer.close();
Turns out I'm an idiot :) The file that was being "created" was saving to invalid path and swallowing the exception, so I thought it was being "created" ok. When I tried to to instantiate a new FileInputStream, however, it complained that file didn't exist (rightly so). I had a brainfart and assumed that the java.io.File object actually contained file information in it somewhere. But as erickson pointed out, that was false.
Thanks Ralph for the code, I used it after I solved the invalid pathing issue.
My code:
ZipOutputStream out = new ZipOutputStream(response.getOutputStream());
byte[] buf = new byte[1024];
File file;
InputStream in;
// Loop through entities
for (TitleProductAccountApproval tpAccountApproval : tpAccountApprovals) {
// Generate the file
file = xmlManager.getXML(
tpAccountApproval.getTitleProduct().getTitleProductId(),
tpAccountApproval.getAccount().getAccountId(),
username);
// Write to zip file
in = new FileInputStream(file);
out.putNextEntry(new ZipEntry(file.getName()));
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
out.closeEntry();
in.close();
}
out.close();