§處理檔案上傳
§使用 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>
}
將 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
編碼,您仍然可以使用預設的 multipartFormData
剖析器,方法是提供 FilePartHandler[A]
並使用不同的 Sink 來累積資料。例如,您可以指定 Accumulator(fileSink)
來使用 FilePartHandler[File]
而不是 TemporaryFile
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
特質,實作可以視需要交換,現在有一個 atomicMoveWithFallback
方法,如果可用,它會使用 StandardCopyOption.ATOMIC_MOVE
。
上傳檔案本質上是一個危險的作業,因為無限制的上傳檔案會導致檔案系統滿載 – 因此,TemporaryFile
背後的想法是,它只會在完成時出現在範圍中,並且應該盡快從暫存檔案系統中移出。任何未移動的暫存檔案都會被刪除。
然而,在 某些情況下,垃圾回收並未及時發生。因此,還有一個 play.api.libs.Files.TemporaryFileReaper
可以啟用,以使用 Pekko 排程器在排程的基礎上刪除暫存檔案,這與垃圾回收方法不同。
收割器在預設情況下會停用,並透過設定檔 application.conf
啟用
play.temporaryFile {
reaper {
enabled = true
initialDelay = "5 minutes"
interval = "30 seconds"
olderThan = "30 minutes"
}
}
上述設定檔會刪除超過 30 分鐘的檔案,使用「olderThan」屬性。它會在應用程式啟動後五分鐘啟動收割器,並在之後每 30 秒檢查一次檔案系統。收割器不會知道任何現有的檔案上傳,因此如果系統未仔細設定,可能會在收割器執行時發生延宕檔案上傳。
下一頁:存取 SQL 資料庫
在此文件發現錯誤?此頁面的原始程式碼可以在 此處 找到。在閱讀 文件指南 後,請隨時提出拉取請求。有問題或建議要分享?前往 我們的社群論壇 與社群展開對話。