ResponseUtil#downloadへの要望

sa-struts-tutorial の DownloadActionをみるとダウンロードのサンプルをみることができる。ResponseUtilには便利にも

download(String fileName, byte[] data)

というメソッドがあるので、さくっとダウンロードさせることができるのだ。サンプルではStringをgetBytesしてファイル化してダウンロードさせているが、実際には実ファイルをダウンロードさせることが多いだろう。それも、

	@Execute(validator = false)
	public String download() {
	    File file = new File("/path/to/file");
	    ResponseUtil.download(file.getName(), FileUtil.getBytes(file));
	    return null;
	}

と、すればできる(ファイル名のエンコーディング対応は省略)
FileUtilというのもorg.seasar.framework.util.FileUtilであり、すぐさま使うことができる。

しかし問題がある。でかいファイルだとFileUtil.getBytes(file)でメモリにデータをためこんでしまうため、Out of Memoryしちゃうのだ。そんなに大きなファイルでなくとも同時に利用するユーザが多いとメモリを圧迫することになる。もちろん、

    @Execute(validator = false)
    public String download() {
        try {
            File file = new File("/path/to/file");
            
            response.setContentType("application/octet-stream");
            response.setHeader("Content-disposition", "attachment; filename=\""
                + file.getName()
                + "\"");
            response.setContentLength(file.length());
            
            InputStreamUtil.copy(FileInputStreamUtil.create(file),
                response.getOutputStream());

        } catch (IOException e) {
            throw new IORuntimeException(e);
        }
        return null;
    }

とゴリって書いてしまえば、重いファイルでもメモリをあまり使わずにスルスルっとダウンロードはさせることはできる。上記のコードで使っているInputStreamUtilクラスやFileInputStreamUtilクラスもSeasarが内包しているクラスなのですぐに使える。

でも、どうせだったら ResponseUtil で対応してほしいなぁと思います。
うーんこんな感じでしょうか。Content-Lengthあり版となし版です。

    public static void download(String fileName, InputStream in) {
        HttpServletResponse response = getResponse();
        try {
            response.setContentType("application/octet-stream");
            response.setHeader("Content-disposition", "attachment; filename=\""
                + fileName
                + "\"");
            OutputStream out = response.getOutputStream();
            InputStreamUtil.copy(in, out);
        } catch (IOException e) {
            throw new IORuntimeException(e);
        }
    }

    public static void download(String fileName, InputStream in, int length) {
        HttpServletResponse response = getResponse();
        response.setContentLength(length);
        download(fileName, in);
    }

直接Fileインスタンスを引数にしちゃうのもありかなぁ。そうしたら内部でfile.length()できるしなぁ。

もしSAStrutsのコミッターの方々がご覧になっていましたら、ぜひご検討いただきたいなと思います。

ちなみに重いファイルは100メガのファイルでためしました。どうやって作ったかというと、コマンドプロンプトから

fsutil file createnew 100mb.data 104857600

と打っただけです(WinXP)。便利ですなぁ。

追記

HttpServletResponse#setContentLength(int) ってあったのね。