昔から使われてきたServletベースのライブラリのいくつかが使えなくなります。FileUploadに着目しています。記事の内容について以下の内容を扱っています。FileUpload、Servlet標準のAPIの使い方。Securityとの兼ね合いにも触れる。MVCのファイルアップロードにまつわる主要なポイントも、ある程度網羅できるように意識しました。APIというServlet標準のファイルアップロードAPIには無いAPIがあります。FileUploadを使う(使いたい)理由になると考えています[3]。FileUploadはここ数年のリリース頻度は高くない[4]ものの、Tilesなどとは異なり、開発が終了しているわけではない模様です。今後も使い続ける希望がないわけではありません。本記事で使用するソースコード本記事内で参照するコードは以下のリポジトリに配置しています。https://github.com/st-user/servlet-fileupload-examples記事本文内で適宜参照します。記事の説明のためにコメント挿入しているなど、リポジトリの方にあるソースと記事内のソースは若干異なる場合があります。FileUploadとServlet標準のファイルアップロードAPIの使い方を概観します。APIServlet標準のファイルアップロードAPIの3つの実装方法を簡単に示します。シンプルなServletでの使い方ソースコード使い方は至って単純で、以下のような形でいくつかのパラメータを設定した後、HTTPリクエストボディのフィールド名やファイル内容を取得します。HttpServletRequestを利用するので、HttpServletのdoPostメソッド内などで呼び出す。HttpServletRequestを利用するので、HttpServletのdoPostメソッド内などで呼び出す。HttpServletRequestを利用するので、HttpServletのdoPostメソッド内などで呼び出す。実際に使用する際には、以下のパラメータをよく意識すると思います。パラメータ名は便宜上セッター名または、アノテーションのフィールド名を記載します。maxRequestSizeこの中で着目していただきたいのが一番上の「アップロードファイルを一時的にファイルに保存する閾値」です。APIではない方やServlet標準のAPIでは、この閾値を超えないサイズのファイルの場合、ファイルデータはメモリ上に格納される。この閾値を超えるサイズのファイルの場合、OSのディスク上に、一時ファイルが作成され、ファイルデータは一時的にそのファイルに書き込まれる。ファイルデータを読み込む場合(getInputStreamなどを呼び出した場合)、そのファイルからデータを読み込むことになる。APIの方では、一時ファイルを作成する必要がないので「アップロードファイルを一時的にファイルに保存する閾値」の設定はしません。作成される一時ファイルは以下のようなものです。FileUploadの例です。FileUploadの場合、一時ファイルを格納するディレクトリはDiskFileItemFactory#setRepositoryで指定します。Servlet標準のAPIではServletコンテナが管理している模様です[6]。FileUploadorg.apache.commons.io.FileCleaningTrackerを使用する。FileItemが使用されなくなった後で[7]、バックグラウンドスレッドにおいて自動で削除されるようにできる。org.apache.commons.fileupload.FileItem#deleteを呼び出し、明示的に削除する。Servlet標準Servletコンテナ管理の、自動削除に任せる[8]。javax.servlet.http.Part#deleteを呼び出し、明示的に削除する。MVC)での使い方Springの場合、定型的な処理はフレームワークの側で行われるためコントローラーのメソッドはより簡潔に書くことができます。各種パラメータは基本的に、プロパティファイルやBean定義で設定します。APIでない場合)も、Servlet標準のAPIの場合も、MultipartResolverというコンポーネントが主役です[9]。APIを使用するエンドポイントを共存させています。この際に重要になるのはresolveLazilyというプロパティです。これをtrueとすることで、コントローラーのパラメータなどで明示しない限り、multipart/form-dataリクエストボディをフレームワーク側に処理させないことが可能となります。フレームワーク側には処理させず、HttpServletRequestを直接受け取り、自前で処理する。APIでない方とほぼ同じです。FileUploadと同じ要領でStandardServletMultipartResolverを明示的にBean定義し、設定することも可能です。注目すべき設定値は「シンプルなServlet」の場合と同様です。以下、Spring固有のポイントをいくつか記します。FileUploadの場合のデフォルトはjavax.servlet.context.tempdirです[10]。Servlet標準のAPIではServletコンテナが管理している模様です[6:1]。FileUpload(CommonsMultipartResolver)の場合も、Servlet標準のAPIの場合(StandardServletMultipartResolver)も、明示的に削除処理が呼び出されています。FileUploadの場合は、org.apache.commons.fileupload.FileItem#deleteServlet標準のAPIの場合は、javax.servlet.http.Part#deleteが呼ばれています。TokenをどのようにHTTPリクエストに含めるかです。Tokenのチェックがされています。SecurityのFilter処理の前に、リクエストボディがパースされている必要があります。APIを使用したい場合、コントローラーのメソッドの前にリクエストボディがパースされてはいけません。以下のような選択肢があります。Tokenは、HTTPリクエストのボディに含める。SecurityのFilterの処理に先んじてリクエストボディをパースする。Environmentsを参照してください。APIも使えるようにしたサンプルを作成しています(ソースコード)。現在リリースさている最新バージョンは1.4で、これは2018年12月にリリースされたものです[13]。9+)には対応していません。9以上が必要とされるようになります。FileUpload関係のモジュールが削除されています。9を想定したissueやバージョン2系リリースのためのissueが上がっていますが、本記事執筆時点(2022年7月上旬)でまだ正式にリリースはされていません。2.0.0のSNAPSHOT版は作成されているようです(https://repository.apache.org/content/repositories/snapshots/org/apache/commons/commons-fileupload2/2.0-SNAPSHOT/)。APIを使用したい場合などはどのようにすればよいのでしょうか?以下の方法が考えられます。2.xが正式にリリースされるまで待つ。2.xのSNAPSHOT版を使う。projectで変換して使用する。projectを使用して変換することにより、動作させることは可能です。以降で2,3について簡単に説明します。projectを使用したサンプルを作成しましたので、適宜参照してください。Centralではなく、2.0-SNAPSHOTのリポジトリのjarを使用します。以下はMavenのpom.xmlの例です。</snapshots></repository>なお、執筆時点2022年7月上旬においては、1.4と比べて以下のような変更点があります。EE対応のものが新規作成されている。org.apache.commons.fileupload.servlet.ServletFileUploadの代わりにorg.apache.commons.fileupload2.jaksrvlt.JakSrvltFileUploadを使う必要がある。projectは、コンパイル済みのjarやWARなどの名前空間(パッケージ名)を変換するツールを提供するプロジェクトです。9+のパッケージ名(jakarta.*)に依存する新しいjarファイルを作成できます。以下のようにバイナリをダウンロードし、対象のjarを指定して実行することで新たなjarが作れます。Mavenのローカルリポジトリにあるjarを変換する。このようにして生成したjarは以下ようにして、使用することができます。classifierを使っているところがポイントです。IOに依存しています。IOのバージョン2.2に依存しています。IOの同バージョンは以下の脆弱性の影響を受けます。IOの現在の最新は、2.11であり、この脆弱性は修正済みです。frameworkの使用も想定して解説してきました。FileUploadを使い続ける方法についても模索しました。簡易的なものですが、参考程度に以下の性能比較を行いました。BootでServlet標準のAPIを使用したパターン(ソースコード)操作1GBのファイルを3ファイル同時にアップロード。操作時のサーバーサイドのHTTPリクエストの処理時間を測定。これを5回ほど行った。API)は同じくらい。API)は両者の60%ほどの時間で終了している。CPUやメモリ使用量はどれもさほど変わらない。本記事ではjavax.servlet.annotation.MultipartConfig(jakarta.servlet.annotation.MultipartConfig)などを利用した、Servletコンテナによりデフォルトで(サードパーティ製のライブラリなしで)提供されるAPI群のことを、Servlet標準のファイルアップロードAPIなどと表現します。frameworkにおける、Servlet標準のファイルアップロードAPIを使用してmultipart/form-dataを処理するクラスです。↩︎このほかにもアップロード処理の進捗状況(何バイトまで読み込んだかなど)を把握できるようにするProgressListenerという機能もあります。記事執筆時点(2022/7)における最新バージョンは1.4であり、リリースされたのは2018/12頃です。FileUploadの1.4についてはHttpServletRequest#getHeader("Content-Length")の戻り値で判断しているようです。↩︎Servlet標準のファイルアップロードAPIの場合、javadocやServlet仕様を参照しても、どのディレクトリに保存されるのかわかりませんでした。今回作成したサンプルプロジェクトでは、javax.servlet.context.tempdirに作成されているようでした。Baeldung)を使用したログ確認の結果により判断しているに過ぎないので、これ以外のディレクトリに作成される可能性がないことなどの確証は得られていません。↩︎バージョン1.4の実装に基づいてより正確に述べると、「生成されたFileItemをreferentとするPhantomReferenceがenqueueされたタイミング」で削除されます。興味のある方はorg.apache.commons.io.FileCleaningTrackerやorg.apache.commons.fileupload.disk.DiskFileItemFactoryの実装を参照してみてください。↩︎javax.servlet.http.Part#deleteのjavadocの記述を参照しています。今回作成したサンプルプロジェクトでは、おおむねHTTPリクエストが終了した直後に削除されていました。↩︎org.springframework.web.multipart.commons#setUploadTempDirのjavadoc参照。今回作成したサンプルプロジェクトでは、DispatcherServlet内で呼び出されています。他にも、MultipartFilterにも呼び出し箇所があります。↩︎TomokiSatoソフトウェアエンジニア。SaaSを開発しています。今はRust、IoT、WebRTCに興味があります。バッジを贈って著者を応援しようバッジを受け取った著者にはZennから現金やAmazonギフトカードが還元されます。バッジを贈るDiscussionTomokiSatoソフトウェアエンジニア。SaaSを開発しています。今はRust、IoT、WebRTCに興味があります。Proよくある質問LinksX(Twitter)GitHubメディアキットLegal利用規約プライバシーポリシ。