§ファイルアップロードの処理
§multipart/form-data
を使用したフォームでのファイルアップロード
ウェブアプリケーションでファイルをアップロードする標準的な方法は、ファイル添付データと標準的なフォームデータを混ぜることができる特別なmultipart/form-data
エンコーディングを使用したフォームを使用することです。
注記: フォームを送信するために使用されるHTTPメソッドは
POST
(GET
ではない)でなければなりません。
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からのByteString
をTemporaryFile
オブジェクトではなく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データベースへのアクセス
このドキュメントに誤りを見つけましたか?このページのソースコードはこちらにあります。ドキュメントガイドラインを読んだ後、プルリクエストを送信して自由に貢献してください。質問やアドバイスを共有したいですか?コミュニティフォーラムにアクセスして、コミュニティとの会話を始めましょう。