ぺーぺーSEのブログ

備忘録・メモ用サイト。

SpringMVCでファイルダウンロード/zip圧縮ダウンロード

SpringMVCでファイルダウンロード、zip圧縮ファイルダウンロードのサンプル。

サンプルの内容

「http://[FQDN]:8080/spring3-mvc-down/download」

クライアントからのアクセスに対して、サーバローカルのファイル(C:\tmp\hoge1.csv)をInputStreamで読み込み、Streamのままレスポンスを返す。

「http://[FQDN]:8080/spring3-mvc-down/download/zip」

クライアントからのアクセスに対して、サーバローカルのファイル(C:\tmp\hoge2.csv)をInputStreamで読み込み、zip圧縮したStreamに変換し、Streamのままレスポンスを返す。

「http://[FQDN]:8080/spring3-mvc-down/download/zip/all」

クライアントからのアクセスに対して、サーバローカルのファイル(「C:\tmp\hoge1.csv」と「C:\tmp\hoge2.csv」と「C:\tmp\hoge3.csv」)をInputStreamで読み込み、zip圧縮したStreamに変換し、Streamのままレスポンスを返す。

※上記はEclipse(WTP)で実行した場合のURI

ギガサイズのファイルをJavaヒープ64M(-Xms64m -Xmx64)で動かしてみた結果OOMが発生しなかったので、zip圧縮処理の際オンメモに圧縮対象のファイルを全展開していない様子。

サンプルの作成

Eclipseで「spring3-mvc-down」というプロジェクトで作る。
Maven2を利用。(なんで3じゃないねんって突っ込みは無しで。)


次に以下を作成する。

  • POMファイル(pom.xml
  • コントローラクラス(jp.sample.app.download.controller.DownloadController)
    • HTTPリクエストに対する処理を実行するクラス。
    • @Controllerや@RequestMappingアノテーションを使用する。
  • Bean定義ファイル(/WEB-INF/mvc-dispatcher-servlet.xml
  • web.xml



■POMファイル(pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>jp.sample.app</groupId>
  <artifactId>spring3-mvc-down</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  <packaging>war</packaging>
  <name>${project.artifactId}</name>
  <description>SpringMVC3でファイルダウンロードのサンプル</description>

  <build>
    <plugins>
      <!-- コンパイラの設定 -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.7</source>
          <target>1.7</target>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
    </plugins>
  </build>

  <dependencies>
    <!-- javax.servlet -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.5</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jsp-api</artifactId>
      <version>2.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
    </dependency>

    <!-- Validation -->
    <dependency>
      <groupId>javax.validation</groupId>
      <artifactId>validation-api</artifactId>
      <version>1.1.0.CR3</version>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>5.0.0.Final</version>
    </dependency>

    <!-- Spring Framework -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>3.2.2.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>3.2.2.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>3.2.2.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>3.2.2.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>3.2.2.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>3.2.2.RELEASE</version>
    </dependency>

    <!-- junit -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit-dep</artifactId>
      <version>4.4</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>



■コントローラクラス(jp.sample.app.download.controller.DownloadController)

package jp.sample.app.download.controller;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/download")
public class DownloadController {

    static final String dir = "C:\\tmp\\";
    static final String[] targetFiles = {"hoge1.csv", "hoge2.csv", "hoge3.csv"};
    static String fileName;
    static String zipFileName;

    static {
        try {
            fileName = new String("サンプル.csv".getBytes("MS932"), "ISO-8859-1");
            zipFileName = new String("サンプル.zip".getBytes("MS932"), "ISO-8859-1");
        } catch (UnsupportedEncodingException e) {
        }
    }

    @RequestMapping
    public void download(HttpServletResponse res) throws Exception {

        res.setContentType("text/csv;charset=MS932");
        res.setHeader("Content-Disposition", "attachment; filename=" + fileName);

        OutputStream os = res.getOutputStream();

        InputStream in = new FileInputStream(dir + targetFiles[0]);

        byte[] b = new byte[1024];
        int len;
        while((len = in.read(b)) != -1) {
            os.write(b, 0, len);
        }
        in.close();
        os.close();
    }

    @RequestMapping("zip")
    public void downloadZip(HttpServletResponse res) throws Exception {

        res.setContentType("application/octet-stream;charset=MS932");
        res.setHeader("Content-Disposition", "attachment; filename=" + zipFileName);
        res.setHeader("Content-Transfer-Encoding", "binary");

        OutputStream os = res.getOutputStream();
        ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(os));
        ZipEntry ze = new ZipEntry(targetFiles[1]);
        zos.putNextEntry(ze);

        InputStream in = new BufferedInputStream(new FileInputStream(dir + targetFiles[1]));

        byte[] b = new byte[1024];
        int len;
        while((len = in.read(b)) != -1) {
            zos.write(b, 0, len);
        }
        in.close();
        zos.closeEntry();
        zos.close();
    }

    @RequestMapping("zip/all")
    public void downloadZipAll(HttpServletResponse res) throws Exception {

        res.setContentType("application/octet-stream;charset=MS932");
        res.setHeader("Content-Disposition", "attachment; filename=" + zipFileName);
        res.setHeader("Content-Transfer-Encoding", "binary");

        OutputStream os = res.getOutputStream();
        ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(os));

        for(int i=0; i<targetFiles.length; i++){

            ZipEntry ze = new ZipEntry(targetFiles[i]);
            zos.putNextEntry(ze);

            InputStream in = new BufferedInputStream(new FileInputStream(dir + targetFiles[i]));

            byte[] b = new byte[1024];
            int len;
            while((len = in.read(b)) != -1) {
                zos.write(b, 0, len);
            }
            in.close();
            zos.closeEntry();
        }
        zos.close();
    }
}



■Bean定義ファイル(/WEB-INF/mvc-dispatcher-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:beans="http://www.springframework.org/schema/beans"
  xmlns:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation="http://www.springframework.org/schema/mvc
  http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
  http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://www.springframework.org/schema/context
  http://www.springframework.org/schema/context/spring-context-3.0.xsd">

  <context:component-scan base-package="jp.sample" />
  <annotation-driven />

  <resources mapping="/resources/**" location="/resources/" />

</beans:beans>



■web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:jsp="http://java.sun.com/xml/ns/javaee/jsp" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
  <display-name>SpringMVC-Application-sample</display-name>
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/mvc-dispatcher-servlet.xml</param-value>
  </context-param>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <listener>
    <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
  </listener>
  <servlet>
    <servlet-name>mvc-dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>mvc-dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
  </welcome-file-list>
</web-app>

サンプルの応答電文

「http://[FQDN]:8080/spring3-mvc-down/download」
HTTPヘッダ
Status Code 200 OK
Content-Disposition attachment; filename=サンプル.csv
Content-Length [数字]
Content-Type text/csv;charset=MS932
Date [日付]
Server Apache-Coyote/1.1


「http://[FQDN]:8080/spring3-mvc-down/download/zip」
HTTPヘッダ
Status Code 200 OK
Content-Disposition attachment; filename=サンプル.csv
Content-Transfer-Encoding binary
Content-Type application/octet-stream;charset=MS932
Date [日付]
Server Apache-Coyote/1.1
Transfer-Encoding chunked


「http://[FQDN]:8080/spring3-mvc-down/download/zip/all」

上記と同じ。


サンプルの処理速度測定

右寄せにならなくて見辛い。。。

ファイル数 ファイルサイズ(MByte) 合計ファイルサイズ(MByte) 処理時間(ミリ秒)
1 1 1 16.3
10 1 10 192.3
100 1 100 2610.3
1000 1 1000 20727.3
ファイル数 ファイルサイズ(MByte) 合計ファイルサイズ(MByte) 処理時間(ミリ秒)
1 10 10 192.3
10 10 100 1768.0
100 10 1000 18896.7
ファイル数 ファイルサイズ(MByte) 合計ファイルサイズ(MByte) 処理時間(ミリ秒)
1 100 100 1773.3
10 100 1000 18813.7
ファイル数 ファイルサイズ(MByte) 合計ファイルサイズ(MByte) 処理時間(ミリ秒)
1 1000 1000 17290.0

※処理時間は3回とった値の平均(少数大第2位を四捨五入)。サーバ側でzip化してStreamで流し切るまでの時間なのでネットワーク遅延は入ってない。

環境

プロセッサ:Intell(R) Core(TM) i3-2350M CPU @ 2.30GHz 2.30Ghz
メモリ:2.00GB
OS:Windows 7 Professional Service Pack1 32ビット
JDK:7 update 17
Javaヒープ:デフォ
APサーバ:Tomcat 7(Eclipse/WTP