§处理文件上传
§使用 multipart/form-data
在表单中上传文件
在 Web 应用程序中上传文件的标准方法是使用带有特殊 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 来累积数据。例如,您可以使用 `FilePartHandler[File]` 而不是 `TemporaryFile`,方法是指定一个 `Accumulator(fileSink)`
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,该 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"
}
}
上面的配置将使用“olderThan”属性删除超过 30 分钟的文件。它将在应用程序启动后五分钟启动清理程序,并此后每 30 秒检查一次文件系统。清理程序不知道任何现有的文件上传,因此如果系统配置不当,长时间的文件上传可能会遇到清理程序。
下一步: 访问 SQL 数据库
发现此文档中的错误?此页面的源代码可以在 此处 找到。在阅读 文档指南 后,请随时贡献拉取请求。有疑问或建议要分享?转到 我们的社区论坛 开始与社区对话。