【Servlet】 ZIP圧縮と同時にファイルをダウンロードさせる

はじめに

 * ファイルを圧縮して、そのファイルをダウンロードするといった場合、
 以下のようなことになる

 1) 一次ファイルを作成しなければならない(そして、そのファイルを消さなければならない)
 2) ZIP化する際にループし、ダウンロード時でも同じようなループするので処理が無駄

サンプル

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

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

/**
 * Servlet implementation class ZipDownloadServlet
 */
public class SampleZipDownloadControl extends HttpServlet {
   private static final long serialVersionUID = 1L;

   /**
    * @see HttpServlet#HttpServlet()
    */
   public SampleZipDownloadControl() {
      super();
   }

   /**
    * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
    */
   protected void doGet(HttpServletRequest request, HttpServletResponse response)
         throws ServletException, IOException {
      this.downloadZipFile(
            response,
            "Sample.zip",
            "C:\\temp\\Sample1",
            "C:\\temp\\Sample2",
            "C:\\temp\\Sample1.txt",
            "C:\\temp\\Sample2.txt");
   }

   /**
    * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
    */
   protected void doPost(HttpServletRequest request,
         HttpServletResponse response) throws ServletException, IOException {
      this.doGet(request, response);
   }

   private void downloadZipFile(HttpServletResponse response,
         String outputZipFileName, String... inputFilePathes)
         throws ServletException {
      try (ServletOutputStream outputStream = response.getOutputStream();
            ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream);) {
         response.setContentType("application/zip");
         response.setHeader("Content-Disposition",
               String.format("attachment; filename=\"%s\"", outputZipFileName));

         for (String inputFilePath : inputFilePathes) {
            File file = new File(inputFilePath);
            this.zip(zipOutputStream, file, file);
         }
      } catch (Exception ex) {
         ex.printStackTrace();
         throw new ServletException("Download failed...", ex);
      }
   }

   private void zip(ZipOutputStream zipOutputStream, File rootFile,
         File targetFile) {
      if (targetFile.isDirectory()) {
         // Directory
         File[] files = targetFile.listFiles();
         for (File file : files) {
            this.zip(zipOutputStream, rootFile, file);
         }
      } else {
         // File
         String zipEntryPath = this.getZipEntryPath(rootFile, targetFile);
         ZipEntry zipEntry = new ZipEntry(zipEntryPath);
         try (BufferedInputStream inputStream = new BufferedInputStream(
               new FileInputStream(targetFile))) {
            zipOutputStream.putNextEntry(zipEntry);

            this.writeStream(inputStream, zipOutputStream);

            zipOutputStream.closeEntry();
         } catch (FileNotFoundException ex) {
            ex.printStackTrace();
         } catch (IOException ex) {
            ex.printStackTrace();
         }
      }
   }

   private String getZipEntryPath(File rootFile, File targetFile) {
      int lengthToExtractZipPath = rootFile.getPath().length()
            - rootFile.getName().length();
      return targetFile.getPath().replace("\\\\", "/")
            .substring(lengthToExtractZipPath);
   }

   private void writeStream(InputStream inputStream, OutputStream outputStream)
         throws IOException {
      int availableByteNumber;
      while ((availableByteNumber = inputStream.available()) > 0) {
         byte[] buffers = new byte[availableByteNumber];
         int readByteNumber = inputStream.read(buffers);
         if (readByteNumber < 0) {
            break;
         }
         outputStream.write(buffers, 0, readByteNumber);
      }
   }
}

動作補足

 * 以下にも対応。

1) フォルダが入れ子
2) 複数フォルダを指定してZip化する

問題

1) Windows7において、日本語のファイル名が文字化けする
 ※ Windows8.1だと起こらない。

解決法

 * Apache のツールを使う
http://symfoware.blog68.fc2.com/blog-entry-724.html
http://www.saka-en.com/java/java-zip-compress/


関連記事

ZIP 処理 ~圧縮編~

http://blogs.yahoo.co.jp/dk521123/33497835.html

ZIP 処理 ~解凍編~

http://blogs.yahoo.co.jp/dk521123/33645352.html

ファイル ダウンロード

http://blogs.yahoo.co.jp/dk521123/33641421.html

Java】URLからファイルをダウンロードするには...

http://blogs.yahoo.co.jp/dk521123/34362429.html