📑

Commons FileUpload再検討〜Spring framework 6 を見据えて

2022/07/07に公開

はじめに

Spring framework 6 では、昔から使われてきたServletベースのライブラリのいくつかが使えなくなります。

issue:
Drop outdated Servlet-based integrations: Commons FileUpload, FreeMarker JSP support, Tiles

この記事では、この中のCommons FileUploadに着目しています。

Servlet標準のファイルアップロードAPI[1]と比較をしつつ、Spring framework 6でもCommons FileUploadを使い続けたい場合のワークアラウンドなどの紹介を行い、来たるSpring framework 6に備えます。

記事の内容について

以下の内容を扱っています。

  • シンプルなServletでの、Commons FileUpload、Servlet標準のAPIの使い方
  • Spring BootでのCommons FileUpload、Servlet標準のAPIの使い方。Spring Securityとの兼ね合いにも触れる。
  • Spring framework 6を見据えてCommons FileUploadをどう扱うか?
  • (Appendix)Commons FileUpload、Servlet標準のAPIの簡単な性能比較

Commons FileUploadの話の比重が大きいですが、Servlet, Spring MVCのファイルアップロードにまつわる主要なポイントも、ある程度網羅できるように意識しました。

今さらCommons FileUploadを使う動機はあるのか?

殊Spring frameworkに関しては、一見、StandardServletMultipartResolver[2]を使用すればよいだけのように思われます(Upgrading to Spring Framework 6.x参照)。

しかし、Commons FileUploadにはStreaming APIというServlet標準のファイルアップロードAPIには無いAPIがあります。場合によってはこれがCommons FileUploadを使う(使いたい)理由になると考えています[3]

また、Commons FileUploadはここ数年のリリース頻度は高くない[4]ものの、Tilesなどとは異なり、開発が終了しているわけではない模様です。今後も使い続ける希望がないわけではありません。

本記事で使用するソースコード

本記事内で参照するコードは以下のリポジトリに配置しています。

https://github.com/st-user/servlet-fileupload-examples

記事本文内で適宜参照します。記事の説明のためにコメント挿入しているなど、リポジトリの方にあるソースと記事内のソースは若干異なる場合があります。

Commons FileUploadとServlet標準のFileUpload

まずは、Commons FileUploadとServlet標準のファイルアップロードAPIの使い方を概観します。

  • シンプルにServletのみを使用したもの
  • Spring Boot(Spring MVC)を使用したもの

の2つそれぞれにつき、

  • Commons FileUpload
  • Commons FileUpload Streaming API
  • Servlet標準のファイルアップロードAPI

の3つの実装方法を簡単に示します。

シンプルなServletでの使い方

ソースコード

使い方は至って単純で、以下のような形でいくつかのパラメータを設定した後、HTTPリクエストボディのフィールド名やファイル内容を取得します。

Commons FileUpload (Streamingではない方のAPI)


// HttpServletRequestを利用するので、HttpServletのdoPostメソッド内などで呼び出す。
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException {

    if (!ServletFileUpload.isMultipartContent(req)) {
        throw new UnsupportedOperationException("Requests must contain multipart content.");
    }

    // 一時ファイルを後でClean upするためtrackerを設定する
    FileCleaningTracker tracker =
        FileCleanerCleanup.getFileCleaningTracker(req.getServletContext());
    DiskFileItemFactory factory = new DiskFileItemFactory();
    factory.setFileCleaningTracker(tracker);
    
    // !! FILE_SIZE_THRESHOLDについては後述
    factory.setSizeThreshold(FILE_SIZE_THRESHOLD);

    // 一時ファイルを保存するディレクトリを設定
    File repository =
        (File) req.getServletContext().getAttribute("javax.servlet.context.tempdir");
    factory.setRepository(repository);
    ServletFileUpload upload = new ServletFileUpload(factory);
    
    // !! MAX_REQUEST_SIZE、MAX_FILE_SIZEについては後述
    upload.setSizeMax(MAX_REQUEST_SIZE);
    upload.setFileSizeMax(MAX_FILE_SIZE);

    try {
        List<FileItem> fileItems = upload.parseRequest(req);
        for (FileItem item : fileItems) {
            if (item.isFormField()) {
                System.out.printf("%s(%s) is a simple form field.%n", item.getFieldName(),
                    item.getString(req.getCharacterEncoding())); // UTF-8
            } else {
                try (InputStream is = item.getInputStream()) {
                    byte[] buf = new byte[BUFFER_SIZE];
                    int n;
                    long totalBytes = 0;
                    while ((n = is.read(buf)) != -1) {
		    
		        // 簡単のため、単純にバイト数をカウントするのみとする
                        totalBytes += n;
                    }
                    System.out.printf("The size of %s is %d bytes.%n", item.getName(),
                        totalBytes);
                }
            }
        }
    } catch (FileUploadException e) {
        ...
    }

    ...
}

Commons FileUpload (Streaming API)


// HttpServletRequestを利用するので、HttpServletのdoPostメソッド内などで呼び出す。
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException {

    if (!ServletFileUpload.isMultipartContent(req)) {
        throw new UnsupportedOperationException("Requests must contain multipart content.");
    }

    ServletFileUpload upload = new ServletFileUpload();
    
    // !! MAX_REQUEST_SIZE、MAX_FILE_SIZEについては後述
    upload.setSizeMax(MAX_REQUEST_SIZE);
    upload.setFileSizeMax(MAX_FILE_SIZE);

    try {
        FileItemIterator iter = upload.getItemIterator(req);
        while (iter.hasNext()) {

            FileItemStream item = iter.next();
            try (InputStream is = item.openStream()) {
                byte[] buf = new byte[BUFFER_SIZE];
                int n;
                long totalBytes = 0;
                while ((n = is.read(buf)) != -1) {
		    // 簡単のため、単純にバイト数をカウントするのみとする
                    totalBytes += n;
                }

                if (item.isFormField()) {
                    System.out.printf("%s(%s) is a simple form field.%n", item.getFieldName(),
                        new String(buf, 0, (int) totalBytes, StandardCharsets.UTF_8));
                } else {
                    System.out.printf("The size of %s is %d bytes.%n", item.getName(),
                        totalBytes);
                }

            }
        }
    } catch (FileUploadException e) {
        ...
    }

    ...
}

Servlet標準


// アノテーションまたはweb.xmlで、multipart/form-dataを扱う旨を指定
// !! FILE_SIZE_THRESHOLD、MAX_REQUEST_SIZE、MAX_FILE_SIZEについては後述
@MultipartConfig(
    fileSizeThreshold = FILE_SIZE_THRESHOLD,
    maxFileSize = MAX_FILE_SIZE,
    maxRequestSize = MAX_REQUEST_SIZE
)
public class FileUploadServlet extends HttpServlet {

    // HttpServletRequestを利用するので、HttpServletのdoPostメソッド内などで呼び出す。
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {

        for (Part part : req.getParts()) {

            try (InputStream is = part.getInputStream()) {
                byte[] buf = new byte[BUFFER_SIZE];
                int n;
                long totalBytes = 0;
                while ((n = is.read(buf)) != -1) {
		    // 簡単のため、単純にバイト数をカウントするのみとする
                    totalBytes += n;
                }
                if (part.getSubmittedFileName() == null) {
                    System.out.printf("%s(%s) is a simple form field.%n", part.getName(),
                        new String(buf, 0, (int) totalBytes, StandardCharsets.UTF_8));
                } else {
                    System.out.printf("The size of %s is %d bytes.%n", part.getSubmittedFileName(),
                        totalBytes);
                }
            }
        }

        ...
    }
}

説明

主要な設定値

実際に使用する際には、以下のパラメータをよく意識すると思います。パラメータ名は便宜上セッター名または、アノテーションのフィールド名を記載します。

Commons FileUpload Commons FileUpload Streaming Servlet標準
アップロードファイルを一時的にファイルに保存する閾値 setSizeThreshold 使用しない fileSizeThreshold
1つ1つのファイルのファイルサイズ最大値 setFileSizeMax setFileSizeMax maxFileSize
リクエスト全体のサイズの最大値[5] setSizeMax setSizeMax maxRequestSize

この中で着目していただきたいのが一番上の「アップロードファイルを一時的にファイルに保存する閾値」です。Commons FileUploadのStreaming APIではない方やServlet標準のAPIでは、

  • この閾値を超えないサイズのファイルの場合、ファイルデータはメモリ上に格納される。
  • この閾値を超えるサイズのファイルの場合、OSのディスク上に、一時ファイルが作成され、ファイルデータは一時的にそのファイルに書き込まれる。ファイルデータを読み込む場合(getInputStreamなどを呼び出した場合)、そのファイルからデータを読み込むことになる。

一方、Commons FileUploadのSteaming APIの方では、一時ファイルを作成する必要がないので「アップロードファイルを一時的にファイルに保存する閾値」の設定はしません。

作成される一時ファイルは以下のようなものです。以下はCommons FileUploadの例です。

upload_d64a0b00_f9cd_416f_9eb3_f7fc5d99eeaf_00000001.tmp
一時ファイルを格納するディレクトリはどこか?

Commons FileUploadの場合、一時ファイルを格納するディレクトリはDiskFileItemFactory#setRepositoryで指定します。Servlet標準のAPIではServletコンテナが管理している模様です[6]

一時ファイルはどのように削除するのか?
Commons FileUpload
  • org.apache.commons.io.FileCleaningTrackerを使用する。FileItemが使用されなくなった後で[7]、バックグラウンドスレッドにおいて自動で削除されるようにできる。
  • または、org.apache.commons.fileupload.FileItem#deleteを呼び出し、明示的に削除する。
Servlet標準
  • Servletコンテナ管理の、自動削除に任せる[8]
  • または、javax.servlet.http.Part#deleteを呼び出し、明示的に削除する。

Spring Boot(Spring MVC)での使い方

Springの場合、定型的な処理はフレームワークの側で行われるためコントローラーのメソッドはより簡潔に書くことができます。各種パラメータは基本的に、プロパティファイルやBean定義で設定します。

Commons FileUploadの場合(Streaming APIでない場合)も、Servlet標準のAPIの場合も、MultipartResolverというコンポーネントが主役です[9]

Commons FileUpload

ソースコード

以下の例では、単一のアプリケーション内でStreamingではないエンドポイントと、Streaming APIを使用するエンドポイントを共存させています。この際に重要になるのはresolveLazilyというプロパティです。これをtrueとすることで、コントローラーのパラメータなどで明示しない限り、multipart/form-dataリクエストボディをフレームワーク側に処理させないことが可能となります。

(Bean定義)


@Component
public class MyComponents {
    
    @Bean
    public CommonsMultipartResolver commonsMultipartResolver() {
        CommonsMultipartResolver resolver = new CommonsMultipartResolver();
        resolver.setMaxInMemorySize(FILE_SIZE_THRESHOLD);
        resolver.setMaxUploadSize(MAX_REQUEST_SIZE);
        resolver.setMaxUploadSizePerFile(MAX_FILE_SIZE);
	// Streaming APIのためにresolveLazily=trueとする
        resolver.setResolveLazily(true);
        return resolver;
    }

    ....
}

(コントローラー)

@Controller
public class FileUploadController {

    // ファイルデータではないフィールドのデータは、
    // @RequestParamでパラメータ名を指定して取得できる
    @PostMapping("/normal")
    public View upload(@RequestParam List<CommonsMultipartFile> uploadFile,
                       @RequestParam String textData) throws IOException {

        for (CommonsMultipartFile item : uploadFile) {

            byte[] buf = new byte[BUFFER_SIZE];
            long totalBytes = 0;
            try (InputStream is = item.getInputStream()) {
                int n;
                while ((n = is.read(buf)) != -1) {
                    totalBytes += n;
                }
            }
            System.out.printf("The size of %s is %d bytes.%n", item.getOriginalFilename(),
                totalBytes);

        }
        System.out.printf("textData(%s) is a simple form field.%n", textData);

        return ...
    }

    // フレームワーク側にStreaming APIを使用させることはできないので、
    // フレームワーク側には処理させず、HttpServletRequestを直接受け取り、自前で処理する。
    @PostMapping("/streaming")
    public View streamingUpload(HttpServletRequest req) throws IOException, FileUploadException {

        if (!ServletFileUpload.isMultipartContent(req)) {
            throw new UnsupportedOperationException("Requests must contain multipart content.");
        }

        ServletFileUpload upload = new ServletFileUpload();
        upload.setSizeMax(MAX_REQUEST_SIZE);
        upload.setFileSizeMax(MAX_FILE_SIZE);

        FileItemIterator iter = upload.getItemIterator(req);
        while (iter.hasNext()) {

            FileItemStream item = iter.next();
            try (InputStream is = item.openStream()) {
		// 処理内容はServletのみを使用する場合と同じため割愛
            }
        }

        return .....
    }
}


Servlet標準

ソースコード

コントローラーの部分の書き方はCommons FileUploadのStreaming APIでない方とほぼ同じです。この例では各種パラメータはプロパティファイルapplication.propertiesで設定していますが、Commons FileUploadと同じ要領でStandardServletMultipartResolverを明示的にBean定義し、設定することも可能です。

(application.properties)

spring.servlet.multipart.file-size-threshold=1024
spring.servlet.multipart.max-file-size=-1
spring.servlet.multipart.max-request-size=-1
spring.servlet.multipart.enabled=true

@Controller
public class FileUploadController {

    // ファイルデータではないフィールドのデータは、
    // @RequestParamでパラメータ名を指定して取得できる
    @PostMapping("/upload")
    public View upload(@RequestParam List<MultipartFile> uploadFile, @RequestParam String textData)
        throws IOException {

        for (MultipartFile item : uploadFile) {

            byte[] buf = new byte[2048];
            long totalBytes = 0;
            try (InputStream is = item.getInputStream()) {
                int n;
                while ((n = is.read(buf)) != -1) {
                    totalBytes += n;
                }
            }
            System.out.printf("The size of %s is %d bytes.%n", item.getOriginalFilename(),
                totalBytes);

        }
        System.out.printf("textData(%s) is a simple form field.%n", textData);

        return ...
    }
}

説明

主要な設定値

注目すべき設定値は「シンプルなServlet」の場合と同様です。以下、Spring固有のポイントをいくつか記します。

一時ファイルを格納するディレクトリはどこか?

Commons FileUploadの場合のデフォルトはjavax.servlet.context.tempdirです[10]。Servlet標準のAPIではServletコンテナが管理している模様です[6:1]

一時ファイルはどのように削除するのか?

Commons FileUpload(CommonsMultipartResolver)の場合も、Servlet標準のAPIの場合(StandardServletMultipartResolver)も、明示的に削除処理が呼び出されています。

具体的には、リクエストの終了処理[11]において、org.springframework.web.multipart.MultipartResolver#cleanupMultipartが呼ばれ、その内部で、

  • Commons FileUploadの場合は、org.apache.commons.fileupload.FileItem#delete
  • Servlet標準のAPIの場合は、javax.servlet.http.Part#delete

が呼ばれています。

Spring Securityを使用する場合の考慮点

Spring Securityを使う際に悩ましいのが、CSRF[12] TokenをどのようにHTTPリクエストに含めるかです。

Spring SecurityではServlet Filter内でCSRF Tokenのチェックがされています。HTTPリクエストボディにCSRF Tokenが含まれている場合、Spring SecurityのFilter処理の前に、リクエストボディがパースされている必要があります。一方で、Streaming APIを使用したい場合、コントローラーのメソッドの前にリクエストボディがパースされてはいけません。

以下のような選択肢があります。

Streaming APIを使わない場合
  • CSRF Tokenは、HTTPリクエストのボディに含める。その上で、Spring SecurityのFilter群より先に、MultipartFilterを配備し、Spring SecurityのFilterの処理に先んじてリクエストボディをパースする。
  • CSRF TokenをURLに含める

Streaming APIを使う場合

  • CSRF TokenをURLに含める

より詳細な議論については、Spring Security公式のドキュメントMultipart (file upload) - Cross Site Request Forgery (CSRF) for Servlet Environmentsを参照してください。

今回は、URLにCSRF Tokenを含め、Streaming APIも使えるようにしたサンプルを作成しています(ソースコード)。

Commons FileUploadの現状

現在リリースさている最新バージョンは1.4で、これは2018年12月にリリースされたものです[13]。当然ながら、javax.*のパッケージに依存しており、jakarta.*(jakarta EE 9+)には対応していません。

一方で、Spring frameworkはversion 6からはjakarta EE 9以上が必要とされるようになります。そのため、Drop outdated Servlet-based integrations: Commons FileUpload, FreeMarker JSP support, Tilesにより、Commons FileUpload関係のモジュールが削除されています。

Commons FileUploadでは一応jakarta EE 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/)。

Spring framework 6でもCommons FileUploadを使いたい時は?

StandardServletMultipartResolverなどを使用(移行)できるのであれば、それがベターだとは思いますが、どうしてもStreaming APIを使用したい場合などはどのようにすればよいのでしょうか?

以下の方法が考えられます。

  1. 2.xが正式にリリースされるまで待つ。
  2. 2.xのSNAPSHOT版を使う。
  3. 1.4のjarをEclipse Transformer projectで変換して使用する。

正式に2.xがリリースされればそれを使用するのが望ましいですが、それが期待できない(待てない)場合でも、2.xのSNAPSHOTを使用する、または、1.4のjarをEclipse Transformer projectを使用して変換することにより、動作させることは可能です。

以降で2,3について簡単に説明します。

SNAPSHOT版のサンプルと、Eclipse Transformer projectを使用したサンプルを作成しましたので、適宜参照してください。

2.xのSNAPSHOTを使用する

Maven Centralではなく、2.0-SNAPSHOTのリポジトリのjarを使用します。以下はMavenのpom.xmlの例です。

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-fileupload2</artifactId>
    <version>2.0-SNAPSHOT</version>
</dependency>
<repository>
    <id>apache.snapshots</id>
    <name>Apache Development Snapshot Repository</name>
    <url>https://repository.apache.org/content/repositories/snapshots/</url>
    <releases>
        <enabled>false</enabled>
    </releases>
    <snapshots>
        <enabled>true</enabled>
    </snapshots>
</repository>

なお、執筆時点2022年7月上旬においては、1.4と比べて以下のような変更点があります。

  • パッケージ名はorg.apache.commons.fileupload.*から、org.apache.commons.fileupload2.*になっている
  • 一部クラスはjakarta EE対応のものが新規作成されている。例えばorg.apache.commons.fileupload.servlet.ServletFileUploadの代わりにorg.apache.commons.fileupload2.jaksrvlt.JakSrvltFileUploadを使う必要がある。
import org.apache.commons.fileupload2.FileItemIterator;
import org.apache.commons.fileupload2.FileItemStream;
import org.apache.commons.fileupload2.FileUploadException;
import org.apache.commons.fileupload2.jaksrvlt.JakSrvltFileUpload;

...

if (!JakSrvltFileUpload.isMultipartContent(req)) {
    throw new UnsupportedOperationException("Requests must contain multipart content.");
}

JakSrvltFileUpload upload = new JakSrvltFileUpload();
upload.setSizeMax(MAX_REQUEST_SIZE);
upload.setFileSizeMax(MAX_FILE_SIZE);

...

Eclipse Transformer projectを使用する

Eclipse Transformer projectは、コンパイル済みのjarやWARなどの名前空間(パッケージ名)を変換するツールを提供するプロジェクトです。

このプロジェクトを利用し、1.4のjarファイルを変換し、jakarta EE 9+のパッケージ名(jakarta.*)に依存する新しいjarファイルを作成できます。

以下のようにバイナリをダウンロードし、対象のjarを指定して実行することで新たなjarが作れます。

ダウンロードリンク: https://projects.eclipse.org/projects/technology.transformer/downloads

# zipを解凍
unzip org.eclipse.transformer.cli-0.4.0-distribution.jar

# Mavenのローカルリポジトリにあるjarを変換する。

PATH_TO_YOUR_MAVEN_LOCAL_REPO=
# e.g. PATH_TO_YOUR_MAVEN_LOCAL_REPO=${HOME}/.m2/repository 

java -jar org.eclipse.transformer.cli-0.4.0.jar \
  ${PATH_TO_YOUR_MAVEN_LOCAL_REPO}/commons-fileupload/commons-fileupload/1.4/commons-fileupload-1.4.jar \
  ${PATH_TO_YOUR_MAVEN_LOCAL_REPO}/commons-fileupload/commons-fileupload/1.4/commons-fileupload-1.4-jakarta.jar
  

このようにして生成したjarは以下ようにして、使用することができます。classifierを使っているところがポイントです。

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
    <classifier>jakarta</classifier>
</dependency>

そのほか考慮点

推移的に依存するライブラリの脆弱性

Commons FileUploadは、Commons IOに依存しています。バージョン1.4の時点では、Commons IOのバージョン2.2に依存しています。

Commons IOの同バージョンは以下の脆弱性の影響を受けます。

Commons IOの現在の最新は、2.11であり、この脆弱性は修正済みです。

まとめ

Commons FileUpload、Servlet標準のAPIの使い方や設定値、仕組みなどにつき、Spring frameworkの使用も想定して解説してきました。

また、Spring framework 6でCommons FileUploadを使い続ける方法についても模索しました。

Appendix

Commons FileUpload と Servlet FileUploadの性能比較

簡易的なものですが、参考程度に以下の性能比較を行いました。

概要

環境

  • macOS Monterey (Apple M1, Memory 8GB)
  • OpenJDK Runtime Environment Corretto-17.0.3.6.1

比較対象

  1. Spring BootでCommons FileUpload (Streaming APIでない)を使用したパターン(ソースコード)
  2. Spring BootでCommons FileUpload Streaming APIを使用したパターン(ソースコード)
  3. Spring BootでServlet標準のAPIを使用したパターン(ソースコード)

操作

1GBのファイルを3ファイル同時にアップロード。
(フォームには、input type="text"のフィールドも1つ含まれている)

測定

  • 操作時のサーバーサイドのHTTPリクエストの処理時間を測定。これを5回ほど行った。
  • 上記の実行時間測定時、jconsoleにてCPUやメモリ使用量のグラフを確認

結果

  • 実行時間について、1と3(非Streaming API)は同じくらい。2(Streaming API)は両者の60%ほどの時間で終了している。
  • CPUやメモリ使用量はどれもさほど変わらない。

Spring BootでCommons FileUpload (Streaming APIでない)を使用したパターン

/normal takes 16036 ms.
/normal takes 15713 ms.
/normal takes 15862 ms.
/normal takes 15454 ms.
/normal takes 15553 ms.

fileupload-servlet-standard-small

Spring BootでCommons FileUpload Streaming APIを使用したパターン

/streaming takes 9360 ms.
/streaming takes 9361 ms.
/streaming takes 9242 ms.
/streaming takes 9272 ms.
/streaming takes 9324 ms.

fileupload-streaming-jconsole-small

Spring BootでServlet標準のAPIを使用したパターン

/upload takes 15549 ms.
/upload takes 15703 ms.
/upload takes 15804 ms.
/upload takes 15513 ms.
/upload takes 15603 ms.

fileupload-servlet-standard-small

脚注
  1. 本記事ではjavax.servlet.annotation.MultipartConfig(jakarta.servlet.annotation.MultipartConfig)などを利用した、Servletコンテナによりデフォルトで(サードパーティ製のライブラリなしで)提供されるAPI群のことを、Servlet標準のファイルアップロードAPIなどと表現します。 ↩︎

  2. Spring frameworkにおける、Servlet標準のファイルアップロードAPIを使用してmultipart/form-dataを処理するクラスです。参考:1.1.12. Multipart Resolver - Web on Servlet Stack ↩︎

  3. このほかにもアップロード処理の進捗状況(何バイトまで読み込んだかなど)を把握できるようにするProgressListenerという機能もあります。参考:commons fileUploadのWatching progress ↩︎

  4. 記事執筆時点(2022/7)における最新バージョンは1.4であり、リリースされたのは2018/12頃です。 ↩︎

  5. setSizeMaxmaxRequestSizeが厳密に何のサイズをカウントしているか(リクエストボディのみなのかHTTPヘッダも含めたものなのかなど)はjavadocなどからは明確ではありませんでしたが、動作確認した限りでは、Commons FileUploadの1.4についてはHttpServletRequest#getHeader("Content-Length")の戻り値で判断しているようです。 ↩︎

  6. Servlet標準のファイルアップロードAPIの場合、javadocやServlet仕様を参照しても、どのディレクトリに保存されるのかわかりませんでした。今回作成したサンプルプロジェクトでは、javax.servlet.context.tempdirに作成されているようでした。ただしWatchService API(参考A Guide to WatchService in Java NIO2 - Baeldung
    )を使用したログ確認の結果により判断しているに過ぎないので、これ以外のディレクトリに作成される可能性がないことなどの確証は得られていません。 ↩︎ ↩︎

  7. バージョン1.4の実装に基づいてより正確に述べると、「生成されたFileItemreferentとするPhantomReferenceがenqueueされたタイミング」で削除されます。興味のある方はorg.apache.commons.io.FileCleaningTrackerorg.apache.commons.fileupload.disk.DiskFileItemFactoryの実装を参照してみてください。 ↩︎

  8. javax.servlet.http.Part#deleteのjavadocの記述を参照しています。今回作成したサンプルプロジェクトでは、おおむねHTTPリクエストが終了した直後に削除されていました。 ↩︎

  9. 1.1.12. Multipart Resolver - Web on Servlet Stack ↩︎

  10. org.springframework.web.multipart.commons#setUploadTempDirのjavadoc参照。 ↩︎

  11. 今回作成したサンプルプロジェクトでは、DispatcherServlet内で呼び出されています。他にも、MultipartFilterにも呼び出し箇所があります。 ↩︎

  12. Cross-site request forgery - wikipedia ↩︎

  13. Apache Commons FileUpload - MVN repository ↩︎

Discussion