10. ファイルダウンロード

StreamResultを使った場合

WebWork2ではファイルの出力用にStreamResultというクラスが用意されています。
通常使うServletDispatcherResultではJSPやVelocityに結果出力を任せるところを、StreamResultは指定したInputStreamをクライアントに出力します。

FileDownloadActionの作成

getInputStream()で返したInputStreamの中身がServletOutputStreamに出力されます。
デフォルトではinputStreamですが、StreamResultが読み込むInputStreamの名前を変えることは出来ます。

public class FileDownloadAction extends ActionSupport  {
    public String execute() throws Exception {
        return SUCCESS;
    }
    
    public InputStream getInputStream() {
        try {
            return new FileInputStream("c:/test");
        }
        catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }
}
xwork.xmlの設定
<action name="filedownload" class="ww2.examples.event.FileDownloadAction">
    <result name="success" type="stream">
        <param name="bufferSize">4096</param>
        <param name="contentType">application/octet-stream</param>
        <param name="contentDisposition">attachment; filename=test.file;</param>
    </result>
</action>
  • bufferSize: 出力時のバッファサイズ(初期値:1024)
  • contentType: Content-Type(初期値:text/plain)
  • contentDisposition: Content-Disposition(初期値:inline)
結果

一応ダウンロード自体はされるのですが欠点・バグが色々あります。

  • 欠点
    • xwork.xmlに書く値は動的に変えられない(たぶん…。xwork.xmlの値をActionから指定する方法があれば別ですが)
    • (追記)${変数名}でActionのgetterの値を入れられました
  • バグ
    • InputStreamがcloseされない(ならActionでcloseしようと思ったのに、prepare()はあってもその逆はないのね…)
    • (追記)修正されました。2.1.8で反映される予定です。

(追記)文句なしにStreamResultは使えます。ていうか使わない手はないです。

Action内で出力する場合

とりあえずAction内でHttpServletResponseを取得して直接出力すればやりたい放題なのでその方法も書いておきます。(非推奨)

FileDownloadBySelfActionを作成

ServletResponseAwareインターフェースを実装するとHttpServletResponseがセットされる(intercepterにservlet-configが含まれている必要あり)ので、取得したHttpServletResponseを使って直接ファイルを書き出します。
また、Resultの処理は行わないためexecute()の戻り値はActionインターフェースの定数NONEを返します。

public class FileDownloadBySelfAction extends ActionSupport implements
        ServletResponseAware {
    private HttpServletResponse res;

    public String execute() throws Exception {
        res.setContentType("application/octet-stream");
        res.setHeader("Content-Disposition", "attachment; filename=text.file");
        OutputStream out = null;
        InputStream in = null;
        try {
            out = res.getOutputStream();
            in = new FileInputStream("c:/test");
            CopyUtils.copy(in, out);
            out.flush();
        }
        finally {
            IOUtils.closeQuietly(in);
        }

        return NONE;
    }

    public void setServletResponse(HttpServletResponse res) {
        this.res = res;
    }
}
xwork.xmlの設定

Resultがないので超シンプルです。(実際のアプリではエラー時のResultは指定する必要がありますが)

<action name="filedownloadbyself" 
    class="ww2.examples.event.FileDownloadBySelfAction" />
結果

当たり前ですが普通に使えます。もうServletAPI直接たたいてるんだから当たり前ですが。

独り言

やっぱりStreamResultを使う方がきれいな感じですねぇ。
とりあえず使えそうな独自のStreamResultもどきを作るか、もっと使いやすいように提案していくのがいいかも。