How to create a zip file in GAEJ servlet

Posted on September 29, 2012 by Lukas under iOS development | 2 comments

GAEJ with zip iconIt’s been already almost seven months since my last post, and I have to admit that I am really ashamed of it. Another one from the infinite pool of people who don’t stand up to their promises, huh? But enough of this, you’ve most likely came here to learn something about creating zip files in Google App Engine / Java servlets, and that’s what this post is (from now on) all about. So, let’s get started, shall we?

What will you learn here

I am going to explain what you need to access your web app’s static files and/or files stored in Blobstore in your servlet; how to pack these files using standard java.util.zip library into one zip file, and how to serve this file in HttpServletResponse of your servlet.

Prerequisites

This tutorial provides example of servlet running on Google App Engine for Java, and is dependent on Apache commons IO library, so you need to be familiar with adding jar files to your project, and you should know the basics of how servlet works (though it’s not required). I am using Eclipse Juno IDE with the App Engine plugin, and I advise you to use it too (it can really save you a lot of troubles). The servlet also expects the following files to be present in the war directory of your App Engine project.
War contents in Eclipse

What will you create

Contents of sample zip file created with Google App Engine for Java
[1] Structure of zip file created by our servlet

Here comes the servlet class itself


package yourpackagehere;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.*;

// We are using this utility for converting InputStreams into byte arrays
// You can get the jar at http://commons.apache.org/io/
import org.apache.commons.io.IOUtils;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@SuppressWarnings("serial")
public class MyZipServlet extends HttpServlet {
    
   public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        
      // We will use this stream as output stream of our ZipOutputStream
      // and later we'll get the data from it by calling toByteArray() method
      ByteArrayOutputStream zipBaos = new ByteArrayOutputStream();
    
      try { 
         // Create the ZIP file output stream (we will write our zip entries (= files) into it)
         ZipOutputStream zipOut = new ZipOutputStream(zipBaos);


         String pathOfFile1InWarDirectory = "/images/markers/OrangeMarker.png";
            
         // The name doesn't have to be the same as the name of the input file
         String nameOfFile1InZipFile = "MyOrangeMarker.png";

         // You can access static files located in your war folder using the following statement
         // (Basic File IO is generally not allowed in App Engine, except of reading static files, 
         // which is exactly what we are doing here.)
         InputStream file1InputStream = getServletContext().getResourceAsStream(pathOfFile1InWarDirectory);

         // We are using Apache commons-io jar for getting all bytes from an inputStream
         // (learn more about it at http://commons.apache.org/ )
         byte[] file1Contents = IOUtils.toByteArray(file1InputStream);

         ZipEntry zipEntry1 = new ZipEntry(nameOfFile1InZipFile);     // Create new zip entry                                                                    // the constructor parameter is filename of the entry
         zipOut.putNextEntry(zipEntry1);            // add the zip entry to the ZipOutputStream
         zipOut.write(file1Contents);              // write contents (byte array) to the ZipOutputStream
         zipOut.closeEntry();                      
    
            
         String pathOfFile2InWarDirectory = "/images/markers/GreenMarker.png";

         // You can specify folder in which you want the file to be added in the file's name
         // (in our case, the folder is named 'markers')
         String nameOfFile2InZipFile = "markers/MyGreenMarker.png";

         InputStream file2InputStream = getServletContext().getResourceAsStream(pathOfFile2InWarDirectory);
         byte[] file2Contents = IOUtils.toByteArray(file2InputStream);
         zipOut.putNextEntry(new ZipEntry(nameOfFile2InZipFile));
         zipOut.write(file2Contents);
         zipOut.closeEntry();


         // Add another file to the 'markers' directory
         InputStream file3InputStream = getServletContext().getResourceAsStream("/images/markers/RedMarker.png");
         byte[] file3Contents = IOUtils.toByteArray(file3InputStream);
         //zipOut.putNextEntry(new ZipEntry("markers/MyRedMarker.png"));
         zipOut.write(file3Contents);
         zipOut.closeEntry();

         zipOut.close();

      } catch (IOException e) {
         e.printStackTrace();
         // Creation of the zip file failed; inform the browser about it
         res.sendError(500, "Creation of the zip file failed with exception:\n\n" + e.getLocalizedMessage());
         return;
      }

      // Get the zip data from the ByteArrayOutputStream
      byte[] zipData = zipBaos.toByteArray();


      // Serve the data to response's stream
      String filename = "MyZipFile.zip";

      // following header statement instructs the web browser 
      // to treat the file as attachment (= to download the file)
      res.setHeader("Content-Disposition", "attachment; filename=" + filename);

      res.setContentType("application/x-download");
      res.setContentLength(zipData.length);
      res.getOutputStream().write(zipData); 
   }
    
}

[2] MyZipServlet.java

The source code is commented in almost pedantic manner, so everything should be clear, but if you don’t understand something, you are welcome to ask me in the comments section below.
In order to have access to your servlet with a user-friendly url, you need to add the following declarations to your project’s web.xml descriptor



   MyZipServlet
   yourpackagehere.MyZipServlet


   MyZipServlet
   /createZip

Now, when you run your app on the local development server, you can access the servlet using http://localhost:8888/createZip . If you have it right, the MyZipFile.zip will be immediately downloaded. If not, you will be presented with error message similar to this one:
Server exception error screenshot
Also, if you are using Safari, don’t be surprised that the folder will be unzipped immediately upon download.

Bonus – adding files from Blobstore to the zip file

I promised to show you how to add files from Blobstore to the zip file, and that’s exactly what’s this section about. The main point is fetching all the blob’s data using the fetchData method. You can view how it is done right here:


// Add these to your import section
import com.google.appengine.api.blobstore.BlobInfo;
import com.google.appengine.api.blobstore.BlobInfoFactory;
import com.google.appengine.api.blobstore.BlobKey;
import com.google.appengine.api.blobstore.BlobstoreService;
import com.google.appengine.api.blobstore.BlobstoreServiceFactory;

   // ... And this into the try block before closing the zipOut ....

   BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();
   BlobInfoFactory bif = new BlobInfoFactory();
        
   BlobKey blobKey = new BlobKey("");
   BlobInfo blobInfo = bif.loadBlobInfo(blobKey);
   long contentSize = blobInfo.getSize();
   byte[] blobContents = blobstoreService.fetchData(blobKey, 0, contentSize);
        
   // add the blobContents the same way we added the previous files
   zipOut.putNextEntry(new ZipEntry("MyBlobFileName"));
   zipOut.write(blobContents);
   zipOut.closeEntry();
  • Rafael de Lima

    Thank you so much for your solution! It is exactly was I looking for, mainly for the blobstore section!

    • Cris Abraham

      Exactly what I wanted! Thanks for this.