怠惰系エンジニアのメモ帳

勉強した内容をメモしていきます。解説ブログではないので悪しからず。

【SpringMVC】Apache commons-fileuploadを使用して、マルチパートリクエストをストリーミングする

モチベーション

SpringMVCで作成されたアプリケーショで、 クライアントからアップロードされたファイル(マルチパート)をストリーミングして処理したかった。
というのも、Springでマルチパートのデータを受け取る場合は、ハンドラメソッドの引数に MultipartFile を取れば良いが、ローカルストレージに一時ファイルを作成してしまう。 (MultipartFileが参照するのはクライアントから送られてきたデータではなく、ローカルストレージに作成されたファイル。) そのため、大容量ファイルが送られてきた際は、一時的とはいえストレージを消費することになるため、多重で大量のファイルが送られてきた場合には容量不足になる可能性がある。

これを避けるためには、クライアントから送られてきたマルチパートデータをストリーミング処理すればよい。

環境

  • Spring MVC
    • 5.0.6RELEASE
  • Apache commons-fileupload
    • 1.3.3

準備

1. Apache commons-fileupload を依存関係に追加する

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.3</version>
</dependency>

2. マルチパートリクエストを無効にする

無効にしないと、ローカルストレージに一時ファイルを作成してしまうため。

  • application.properties
spring.servlet.multipart.enabled=false

コード

流れとしては

  1. マルチパートリクエストであるかを判定する
  2. リクエストコンテキストから、マルチパートのデータを取得する。
  3. マルチパートのデータから、入力ストリームを取得して処理する。

となる。

@PostMapping("/upload")
public void streaming(final HttpServletRequest request) {

    // 1. マルチパートのリクエストであるかを判定
    if (!ServletFileUpload.isMultipartContent(request)) {
        throw new RuntimeException("request is not multipart.");
    }

    // 2. リクエストコンテキストから、マルチパートのデータを取得する。
    final ServletFileUpload servletFileUpload = new ServletFileUpload();
    final FileItemIterator fileItemIterator = servletFileUpload.getItemIterator(request);

    // 3. マルチパートのデータから、入力ストリームを取得して処理する。
    while (fileItemIterator.hasNext()) {
        final FileItemStream itemStream = fileItemIterator.next();
        final InputStream is = itemStream.openStream();
        this.uploadService.execute(is); // ストリームを消費するサービスの想定
    }
}

また、FileItemStreamInputStream 以外にも

  • フィールド名
  • ファイル名
  • Content-type

が取得できる。
マルチパートのデータにはファイル以外も含まれている場合があるので、フィールド名やContent-typeでファイル判別を行う方が良さそう。