ドキュメント

§ファイルアップロードの処理

§multipart/form-dataを使用したフォームでのファイルアップロード

ウェブアプリケーションでファイルをアップロードする標準的な方法は、ファイル添付データと標準的なフォームデータを混ぜることができる特別なmultipart/form-dataエンコーディングを使用したフォームを使用することです。

注記: フォームを送信するために使用されるHTTPメソッドはPOSTGETではない)でなければなりません。

HTMLフォームの作成から始めます

@helper.form(action = routes.HomeController.upload(), Symbol("enctype") -> "multipart/form-data") {

    <input type="file" name="picture">

    <p>
        <input type="submit">
    </p>

}

次に、uploadアクションを定義します

import java.nio.file.Paths;
import play.libs.Files.TemporaryFile;
import play.mvc.Controller;
import play.mvc.Http;
import play.mvc.Result;

public class HomeController extends Controller {

  public Result upload(Http.Request request) {
    Http.MultipartFormData<TemporaryFile> body = request.body().asMultipartFormData();
    Http.MultipartFormData.FilePart<TemporaryFile> picture = body.getFile("picture");
    if (picture != null) {
      String fileName = picture.getFilename();
      long fileSize = picture.getFileSize();
      String contentType = picture.getContentType();
      TemporaryFile file = picture.getRef();
      file.copyTo(Paths.get("/tmp/picture/destination.jpg"), true);
      return ok("File uploaded");
    } else {
      return badRequest().flashing("error", "Missing file");
    }
  }
}

getRef()メソッドを使用すると、TemporaryFileへの参照が得られます。これは、Playがファイルアップロードを処理するデフォルトの方法です。

最後に、POSTルートを追加します

POST  /          controllers.HomeController.upload(request: Request)

注記: 空のファイルは、ファイルがまったくアップロードされなかった場合と同じように扱われます。これは、multipart/form-dataファイルアップロードパートのfilenameヘッダーが空の場合にも当てはまります(ファイル自体が空ではない場合でも)。

§ファイルアップロードのテスト

uploadアクションに自動化されたJUnitテストを作成することもできます

@Test
public void testFileUpload() throws IOException {
  File file = getFile();
  Http.MultipartFormData.Part<Source<ByteString, ?>> part =
      new Http.MultipartFormData.FilePart<>(
          "picture",
          "file.pdf",
          "application/pdf",
          FileIO.fromPath(file.toPath()),
          Files.size(file.toPath()));

  Http.RequestBuilder request =
      Helpers.fakeRequest()
            .uri(routes.MyController.upload().url())
          .method("POST")
          .bodyRaw(
              Collections.singletonList(part),
              play.libs.Files.singletonTemporaryFileCreator(),
              app.asScala().materializer());

  Result result = Helpers.route(app, request);
  String content = Helpers.contentAsString(result);
    assertThat(content, CoreMatchers.equalTo("File uploaded"));
}

基本的に、RequestBuilderメソッドbodyMultipartに必要なHttp.MultipartFormData.FilePartを作成しています。それ以外では、すべてコントローラの単体テストと同じです。

§直接ファイルアップロード

サーバーにファイルを送信するもう1つの方法は、Ajaxを使用してフォームから非同期的にファイルをアップロードすることです。この場合、リクエスト本文はmultipart/form-dataとしてエンコードされず、プレーンなファイルの内容のみが含まれます。

public Result upload(Http.Request request) {
  File file = request.body().asRaw().asFile();
  return ok("File uploaded");
}

§カスタムマルチパートファイルパートボディパーサーの作成

MultipartFormDataで指定されたマルチパートアップロードは、リクエストからアップロードされたデータを取得し、TemporaryFileオブジェクトに入れます。この動作をオーバーライドして、DelegatingMultipartFormDataBodyParserクラスを使用して、Multipart.FileInfo情報を別のクラスにストリーミングすることができます。

public static class MultipartFormDataWithFileBodyParser
    extends BodyParser.DelegatingMultipartFormDataBodyParser<File> {

  @Inject
  public MultipartFormDataWithFileBodyParser(
      Materializer materializer,
      play.api.http.HttpConfiguration config,
      HttpErrorHandler errorHandler) {
    super(
        materializer,
        config.parser().maxMemoryBuffer(), // Small buffer used for parsing the body
        config.parser().maxDiskBuffer(), // Maximum allowed length of the request body
        config.parser().allowEmptyFiles(),
        errorHandler);
  }

  /** Creates a file part handler that uses a custom accumulator. */
  @Override
  public Function<Multipart.FileInfo, Accumulator<ByteString, FilePart<File>>>
      createFilePartHandler() {
    return (Multipart.FileInfo fileInfo) -> {
      final String filename = fileInfo.fileName();
      final String partname = fileInfo.partName();
      final String contentType = fileInfo.contentType().getOrElse(null);
      final File file = generateTempFile();
      final String dispositionType = fileInfo.dispositionType();

      final Sink<ByteString, CompletionStage<IOResult>> sink = FileIO.toPath(file.toPath());
      return Accumulator.fromSink(
          sink.mapMaterializedValue(
              completionStage ->
                  completionStage.thenApplyAsync(
                      results ->
                          new Http.MultipartFormData.FilePart<>(
                              partname,
                              filename,
                              contentType,
                              file,
                              results.getCount(),
                              dispositionType))));
    };
  }

  /** Generates a temp file directly without going through TemporaryFile. */
  private File generateTempFile() {
    try {
      final Path path = Files.createTempFile("multipartBody", "tempFile");
      return path.toFile();
    } catch (IOException e) {
      throw new IllegalStateException(e);
    }
  }
}

ここでは、pekko.stream.javadsl.FileIOクラスを使用して、AccumulatorからのByteStringTemporaryFileオブジェクトではなくjava.io.Fileオブジェクトに送信するシンクを作成しています。

カスタムファイルパートハンドラーを使用すると、動作を注入できるため、アップロードされたバイト数の実行カウントをシステムの他の場所に送信できます。

§一時ファイルのクリーンアップ

ファイルのアップロードはTemporaryFile APIを使用しており、getRef()メソッドを介してアクセス可能な一時ファイルシステムにファイルを保存することに依存しています。すべてのTemporaryFile参照はTemporaryFileCreatorトレイトから取得され、実装は必要に応じて交換できます。また、利用可能な場合はStandardCopyOption.ATOMIC_MOVEを使用するatomicMoveWithFallbackメソッドもあります。

ファイルのアップロードは、無制限のファイルアップロードによってファイルシステムがいっぱいになる可能性があるため、本質的に危険な操作です。そのため、TemporaryFileの背後にある考え方は、完了時にのみスコープ内にあり、できるだけ早く一時ファイルシステムから移動する必要があるということです。移動されない一時ファイルはすべて削除されます。

しかし、特定の条件下では、ガベージコレクションがタイムリーに発生しません。そのため、ガベージコレクションメソッドとは別に、Pekkoスケジューラを使用してスケジュールされた時点で一時ファイルを削除できるplay.api.libs.Files.TemporaryFileReaperもあります。

リーパーはデフォルトで無効になっており、application.confの設定によって有効になります。

play.temporaryFile {
  reaper {
    enabled = true
    initialDelay = "5 minutes"
    interval = "30 seconds"
    olderThan = "30 minutes"
  }
}

上記の構成では、「olderThan」プロパティを使用して、30分以上経過したファイルを削除します。アプリケーションの起動後5分後にリーパーが開始され、その後30秒ごとにファイルシステムをチェックします。リーパーは既存のファイルアップロードを認識していないため、システムが適切に構成されていない場合、長時間のファイルアップロードはリーパーに遭遇する可能性があります。

次へ: SQLデータベースへのアクセス


このドキュメントに誤りを見つけましたか?このページのソースコードはこちらにあります。ドキュメントガイドラインを読んだ後、プルリクエストを送信して自由に貢献してください。質問やアドバイスを共有したいですか?コミュニティフォーラムにアクセスして、コミュニティとの会話を始めましょう。