Servlet3.0で追加されたファイルアップロードを使う

Servlet3.0から、HttpServletRequest#getParts()というメソッドが追加され、ついにJavaEE標準でファイルアップロードが使えるようになりました。

JSFと連携する方法を調査したので、過程と結論をメモしておきます。

@MultipartConfigアノテーションの付いたServletが必要

前提として、getParts()を使うには@MultipartConfigアノテーションの付いたServletが必要です。
この制約のおかげで実はgetParts()はかなり使いにくくなっています。

JSFとは連携出来ない?

私が調査したのはJSFでアップロードファイルを取得する方法ですが、FacesServletには@MultipartConfigアノテーションは付いておらず、また、FacesServletはfinalで継承出来ません。
つまりFacesServletまでリクエスト処理が進んでしまうとHttpServletRequest#getParts()でのアップロードファイル処理は不可能です。
なんとかしてその前で処理する必要があります。

Filterでやる?

こんなときはFilterを使うのが定石、と思い
「Filterを使ってmultipart/form-dataなリクエストをパースして、application/x-www-form-urlencodedに偽造してFacesServletにフォワードしよう作戦」を考えたのですが、これはうまくいきません。
Filterの中ではHttpServletRequest#getParts()は機能しないようなのです。
Filterには@MultipartConfigアノテーションが付けられないから、当然と言えば当然。

ちなみに、たとえFilterの後続で処理されるServletに@MultipartConfigアノテーションが付いていたとしても、Filterの中ではgetParts()は機能しません。
「こんなときくらいいーじゃん!」と思わないでもないですが、Filter内でフォワードされることを考慮すると必ずしも@MultipartConfigアノテーションの付いたServletに処理が移るとは限らないため、仕方がない仕様でしょう。

アップロード専用Servlet中継作戦

最後の手段として

  1. @MultipartConfigアノテーションを付けたアップロード専用のServletを作り
  2. multipart/form-dataのリクエストはいったんアップロード専用Servletフォワード
  3. リクエストをパースしてapplication/x-www-form-urlencodedに偽造した後にFacesServletにフォワード

という作戦を考えました。
これなら@MultipartConfigアノテーションの付いたServlet内でgetParts()を呼ぶんだからさすがにうまくいくだろう、と期待したのですが、残念なことに動作しませんでした。
これがダメな理由はちょっと見当が付きません。
「たとえ@MultipartConfigアノテーションが付いているServlet内であってもフォワード時にはgetParts()は機能しない」と覚えておくしかないです、今のところ・・・

結局どうやってJSFと連携するのか

結局

  • Filterでcommons-fileuploadを使ってmultipart/form-dataをパース

に行き着きました。

・・・「HttpServletRequest#getParts()はどこにいったの?」という質問は受け付けません。

getParts()は使えない?

今回の調査で、正直「getParts()使えない」という印象を持ってしまいました。
せめてFilterで機能させることの出来る仕様であってほしかったです。そうすれば通常のリクエストとmultipart/form-dataのリクエストを区別なく扱う工夫が出来たため、あらゆるフレームワークと連携することが可能になったのに、と思います。

またJSFでアップロードファイルが扱えないのはどう考えてもJavaEE仕様の落ち度でしょう。心底がっかりです。

あと、セキュリティ面からアップロードファイルのサイズ制限などの設定は必須ですが、アノテーションでしか指定出来ないのは困ります。
今回調査したJSFのように、既存フレームワークとの連携が非常にやりにくくなりますし、サイズ制限が異なるだけの処理にわざわざServletを個別に作らなければいけません。
例えばweb.xmlでも設定出来るようにする、などの逃げ道がほしかったです。

なぜこうなっているのか

エラー処理を考えると、こうなっている理由が少し分かる気がします。
例えばサイズ制限を超えるアップロードファイルを検出したときは「サイズが50MBを超えています」などど画面に出す必要があるでしょう。
これが出来るのは普通はアプリケーションコードの中なわけで、Filterの中でこの状況を検出してもアプリケーションに伝えるのは難しいです。アプリケーション側でどのようにアップロードファイルを扱うかがFilter側で分かっている場合はこの限りではないですが、汎用的なAPIとしては難しいでしょう。
だから、アプリケーションコード中でしかgetParts()が呼ばれないような工夫がされている・・・というのは考えすぎ?


あと、最近のファイルアップロードのUIはリッチになってきて、他の入力項目とは別にバックグラウンドでAjaxFlashを使って単体でアップロードすることが多い気がします。
なのでJSFなどのフレームワークとの連携を考えるのがそもそもまちがいで、単体でアップロードさせてそのファイルをサーバ側でアプリケーションデータと関連付けるような設計にしなさいね、ということ・・・なのか?

結論

getParts()はまだ新しいAPIなので、今後に期待したいと思います。



(2011/3/31更新)

  • 誤記を修正(「application/xxx-form-encoded」→「application/x-www-form-urlencoded」)
  • フォワード」と「FORWARD」が混在していたので「フォワード」に統一