Download generated ZIP file on button click

I’m running Vaadin 6.8.9 and cannot see how to use a StreamResource to download a large ZIP file that is built on-the-fly.

When this ZIP download is started by a click that runs a traditional servlet, it was straightforward:

response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"" );
ZipOutputStream zos = new ZipOutputStream(response.getOutputStream());
zos.setLevel(9);

....write out various generated contents in multiple ZipEntry....such as this:

ZipEntry zipEntry = new ZipEntry( "index.html" );
zipEntry.setSize(htmlBytes.length);
zos.putNextEntry(zipEntry);
zos.write(htmlBytes);
zos.closeEntry();

The idea is straightforward in that we take the standard Servlet response output stream and wrap it in a ZipOutputStream so all the data we write out is sent back to the browser. It doesn’t require a temporary file, and the data is returned to the browser as it’s being generated rather than having to wait until the zip file is fully generated, and then sending it back.

Is there a way to do this in Vaadin when a button is clicked? The issue I see is that I don’t have access to the HttpServletResponse object, but instead will create a StreamResource that itself operates on an InputStream. Well, to have an InputStream, I’d first have to create the ZIP file locally and then read it back. Is there a way to more simply attach my ZipOutputStream so it will be sent back?

Or should I abandon using a button and turn it into a Link that simply has the URL to a traditional Servlet that can stream the response directly?

Thanks for any ideas and pointers!

Hi David,

here is the part of a Button ClickListener that I use for the download of a zip file:

    else if (event.getButton() == downloadLogBtn)
    {
      try
      {
        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
        // fill stream with content of zipped log file
        loggingBO.zipLogfile(baos);
        final StreamResource res = new StreamResource(new StreamResource.StreamSource()
        {
          @Override
          public InputStream getStream()
          {
            return new ByteArrayInputStream(baos.toByteArray());
          }
        }, "log.zip", getApplication());
        getWindow().open(res, "_blank");
      }
      catch (Exception ex)
      {
        // show errors here...
      }
    }

HTH

Andreas

Thanks, Andreas, for the code sample. The code is basically creating the ZIP file in memory first, and then streaming the completed ZIP contents as the response. That’s fine as long as the ZIP file is not too big, but in our case, the ZIP file typically is many megabytes big. So our options are to keep build the entire ZIP in memory or write it to disk, but only after all that has taken place is the data then transferred to the client.

We’re looking for a way to have the request come in and the response be the ZIP stream itself so that as we build the ZIP entries, they are being sent down the wire to avoid caching it on our system and to avoid the download having to wait until the ZIP file is fully built. Of course, that assume that what we’re doing above in our servlet is doing all that since I’m not entirely sure what Tomcat does, but my impression is that when we wrap the HttpServletResponse’s output stream in a ZIP output stream, as we write to the ZIP output stream and blocks fill up, they are being transferred to the client and not being cached.

May have answered my own question, but checking if anybody knows this isn’t right.

Instead of opening a StreamResource, changed the button to open an ExternalResource that point to my Servlet URL where the HttpServletResponse’s output stream can be wrapped in the ZipOutputStream. It seems to work as expected.

David, I know this is an old post. How did you use your ExternalResource to call your servlet. I have it like this:

ExternalResource resource = new ExternalResource(“/” +someContextPath() + “/downloadServlet” );

What else do you need to do?
Thanks.

I guess that’s essentially what I have. I know that I have two flavors that trigger a servlet to do the actual serving of the response.

In one, I have a button that opens a link to return a ZIP file, such as:

	    		exportButton = new Button(vaadinUi.getMsg("ReportView.searchBar.exportButton.label"));
	    		exportButton.addClickListener(new Button.ClickListener() {
					public void buttonClick(final ClickEvent event) {
						EsfVaadinUI vaadinUi = EsfVaadinUI.getInstance();
			    		WebBrowser wb = Page.getCurrent().getWebBrowser();
						String url = vaadinUi.getEsfApp().getExternalContextPath()+"/reports/?type=export-transaction-list&rtid="+reportTemplate.getId()+"&c="+foundTranListPickupCode;
						Page.getCurrent().open(url, wb.isChrome() || wb.isSafari() ? "_top" : "_blank");
		            }
		        });
		    	buttonLayout.addComponent(exportButton);

And another that was used for downloading via a Link:

 Link downloadFileLink = new Link();
downloadFileLink.setResource(new ExternalResource(documentFile.fileVersion.getFileByIdUrl()));
downloadFileLink.setTargetName(wb.isChrome() || wb.isSafari() ? "_top" : "_blank");
downloadFileLink.setTargetWidth(300);
downloadFileLink.setTargetHeight(300);
downloadFileLink.setTargetBorder(BorderStyle.NONE);

Of course, the real action is in the servlet that send back the response directly, so if you want to do a zip file, it might be something like:

            response.setContentType(Application.CONTENT_TYPE_ZIP);
            response.setHeader("Content-Disposition", "attachment; filename=\"" + exportFileName + "\"" );
            zos = new ZipOutputStream(response.getOutputStream());
            zos.setLevel(9);