ドキュメント

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

§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>
    
}

CSRFフィルターを無効にしていない限り、フォームにCSRFトークンを追加します。CSRFフィルターは、フィールドがリストされている順序でマルチパートフォームをチェックするため、ファイル入力フィールドの前にCSRFトークンを配置します。これにより、効率が向上し、ファイルサイズがplay.filters.csrf.body.bufferSizeを超えた場合のトークンが見つからないエラーが回避されます。

次に、multipartFormDataボディパーサーを使用してuploadアクションを定義します

def upload: Action[MultipartFormData[Files.TemporaryFile]] = Action(parse.multipartFormData) { request =>
  request.body
    .file("picture")
    .map { picture =>
      // only get the last part of the filename
      // otherwise someone can send a path like ../../home/foo/bar.txt to write to other files on the system
      val filename    = Paths.get(picture.filename).getFileName
      val fileSize    = picture.fileSize
      val contentType = picture.contentType

      picture.ref.copyTo(Paths.get(s"/tmp/picture/$filename"), replace = true)
      Ok("File uploaded")
    }
    .getOrElse {
      Redirect(routes.HomeController.index()).flashing("error" -> "Missing file")
    }
}

ref属性は、TemporaryFileへの参照を提供します。これは、multipartFormDataパーサーがファイルアップロードを処理するデフォルトの方法です。

注記: 常に、anyContentボディパーサーを使用し、request.body.asMultipartFormDataとして取得することもできます。

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

POST  /          democontrollers.HomeController.upload()

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

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

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

この場合、リクエストボディの内容をファイルに保存するためにボディパーサーを使用するだけで済みます。この例では、temporaryFileボディパーサーを使用しましょう。

def upload: Action[Files.TemporaryFile] = Action(parse.temporaryFile) { request =>
  request.body.moveTo(Paths.get("/tmp/picture/uploaded"), replace = true)
  Ok("File uploaded")
}

§独自のボディパーサーの作成

一時ファイルにバッファリングせずにファイルアップロードを直接処理したい場合は、独自のBodyParserを作成するだけです。この場合、好きな場所にプッシュできるデータのチャンクを受け取ります。

multipart/form-dataエンコーディングを使用する場合は、FilePartHandler[A]を提供して異なるSinkを使用してデータを蓄積することで、デフォルトのmultipartFormDataパーサーを引き続き使用できます。たとえば、Accumulator(fileSink)を指定して、TemporaryFileではなくFilePartHandler[File]を使用できます。

type FilePartHandler[A] = FileInfo => Accumulator[ByteString, FilePart[A]]

def handleFilePartAsFile: FilePartHandler[File] = {
  case FileInfo(partName, filename, contentType, dispositionType) =>
    val perms       = java.util.EnumSet.of(OWNER_READ, OWNER_WRITE)
    val attr        = PosixFilePermissions.asFileAttribute(perms)
    val path        = JFiles.createTempFile("multipartBody", "tempFile", attr)
    val file        = path.toFile
    val fileSink    = FileIO.toPath(path)
    val accumulator = Accumulator(fileSink)
    accumulator.map {
      case IOResult(count, status) =>
        FilePart(partName, filename, contentType, file, count, dispositionType)
    }(ec)
}

def uploadCustom: Action[MultipartFormData[File]] = Action(parse.multipartFormData(handleFilePartAsFile)) {
  request =>
    val fileOption = request.body.file("name").map {
      case FilePart(key, filename, contentType, file, fileSize, dispositionType, _) =>
        file.toPath
    }

    Ok(s"File uploaded: $fileOption")
}

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

ファイルのアップロードでは、TemporaryFile APIを使用します。これは、ref属性を通じてアクセスできる一時ファイルシステムにファイルを格納することに依存しています。すべての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データベースへのアクセス


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