Servletで動的にファイルを生成して、それをダウンロードする場合。
Tomcat 5.5.23
MyFaces 1.1.5
Tomahawk 1.1.6
Ajax4jsf 1.1.1
普通にファイルをダウンロードする場合、
ServletResponse res = …; HttpServletResponse httpRes = (HttpServletResponse) res; httpRes.setContentType("application/pdf"); httpRes.setHeader("Content-Disposition", "attachment; filename=hoge.pdf")); httpRes.getOutoutStream().write(生成したファイルを書く); httpRes.flushBuffer();
みたいな感じだけど、MyFaces/TomahawkとAjax4jsfで扱うHttpServletResponseのOutputStreamは、ByteArrayOutputStreamなのです。
なので、サイズの大きなファイルをダウンロードしようとすると、リソース喰います(ファイルが大きい/リクエストが多いとOutOfMemoryErrorになっちゃう)。
ということで、ファイルダウンロードのアクションで、
1.ダウンロード用一時ファイルをファイルシステム上に作る
2.それをセッションに持たせる
3.自画面を再描画する
で、自画面に邪魔になんないように、ifremeタグでセッションのファイルをダウンロードするリクエストのurlを書いとく。
<c:if test="セッションがダウンロード用一時ファイルを持っている"> <iframe src="<%= request.getContextPath() %>/download" width="0" height="0"></iframe> </c:if>
セッションがダウンロード用一時ファイルを持ってる場合には、下記のように先頭のfilterでURLをフックしてレスポンスを書く。
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpReq = (HttpServletRequest) req; if (httpReq.getRequestURI().endsWith("/download") == true) { if (セッションがダウンロード用一時ファイルを持っている) { httpRes.setContentType("application/pdf"); httpRes.setHeader("Content-Disposition", "attachment; filename=hoge.pdf")); httpRes.getOutoutStream().write(生成したファイルをStreamで書く); httpRes.flushBuffer(); //セッションからダウンロード用一時ファイルを削除する //ファイルシステム上のダウンロード用一時ファイルを削除する return; } } // 次のフィルターへ chain.doFilter(req, res); }
なんで、先頭のfilterにするかというと、MyFaces/TomahawkとAjax4jsfのfilterを通った時点で、HttpServletRequestオブジェクト+そのOutputStreamのオブジェクトの型が変わってるwww
先頭のfilterだと、HttpServletRequestはResponseFacadeクラスで、OutputStreamは
CoyoteOutputStreamクラスだったりする。
これは普通のStream(普通ってなんだよw)。
しかし、MyFaces/TomahawとAjax4jsfのfilterを通った(実際はAjax4jsf→MyFaces)時点で、HttpServletRequestはExtensionsResponseWrapperクラスで、OutputStreamはByteArrayOutputStreamになってたりする。
という訳です。
ちなみにダウンロード用一時ファイルクラスってのを作って、ファイル名をそのオブジェクトのオブジェクトIDとかハッシュ値にして、finalizeでファイルシステム上からファイルを削除するようにするとよさげ。