在 Play2 / Scala 中从内存中的 MultipartFormData 中提取文件
Posted
技术标签:
【中文标题】在 Play2 / Scala 中从内存中的 MultipartFormData 中提取文件【英文标题】:Pulling files from MultipartFormData in memory in Play2 / Scala 【发布时间】:2013-02-08 18:37:05 【问题描述】:我目前在 Play2/Scala 中使用 FileUploader javascript 实用程序将文件上传到我的服务器:
def fileUploader = Action(parse.multipartFormData) request =>
request.body.file("qqfile").map picture =>
import java.io.File
val filename = picture.filename
val contentType = picture.contentType
picture.ref.moveTo(new File("/tmp",filename))
Ok(Json.toJson(Map( "success" -> "true" )))
.getOrElse
Ok(Json.toJson(Map( "error" -> "error occured")))
我只处理小文件 (
此处的播放文档建议编写自定义 BodyParser (http://www.playframework.com/documentation/2.1.0/ScalaFileUpload),但似乎没有任何文档说明如何编写。目前尚不清楚 Scaladocs 中的 API/实现是如何工作的。我尝试查找 MultiPartFormData 源代码以了解它是如何工作的,但我似乎无法在他们的 Git 存储库中找到它:
https://github.com/playframework/Play20/tree/master/framework/src/play/src/main/scala/play/api/mvc
我已经搜索了很多,但似乎找不到一个好的例子。
【问题讨论】:
multipartFormData
正文解析器可以在这里找到:github.com/playframework/Play20/blob/2.1.0/framework/src/play/…
【参考方案1】:
未经测试
BodyParsers
的 Multipart
对象为我们做了很多工作。我们需要做的第一件事是为FilePart
编写一个处理程序。我在这里假设您希望文件部分为Array[Byte]
。
def handleFilePartAsByteArray: PartHandler[FilePart[Array[Byte]]] =
handleFilePart
case FileInfo(partName, filename, contentType) =>
// simply write the data to the a ByteArrayOutputStream
Iteratee.fold[Array[Byte], ByteArrayOutputStream](
new ByteArrayOutputStream()) (os, data) =>
os.write(data)
os
.mapDone os =>
os.close()
os.toByteArray
下一步是定义你的正文解析器:
def multipartFormDataAsBytes:BodyParser[MultipartFormData[Array[Byte]]] =
multipartFormData(handleFilePartAsByteArray)
然后,为了使用它,在你指定它Action
:
def fileUploader = Action(multipartFormDataAsBytes) request =>
request.body.files foreach
case FilePart(key, filename, contentType, bytes) => // do something
Ok("done")
以上代码中的一些类型和方法有点难找。以下是完整的导入列表,以备不时之需:
import play.api.mvc.BodyParsers.parse.Multipart.PartHandler
import play.api.mvc.BodyParsers.parse.Multipart.handleFilePart
import play.api.mvc.BodyParsers.parse.Multipart.FileInfo
import play.api.mvc.BodyParsers.parse.multipartFormData
import play.api.mvc.MultipartFormData.FilePart
import play.api.libs.iteratee.Iteratee
import java.io.ByteArrayOutputStream
import play.api.mvc.BodyParser
import play.api.mvc.MultipartFormData
【讨论】:
【参考方案2】:自发布以来,Play API 发生了相当大的变化。我有一个类似的用例,我不想要临时文件并将上面的内容翻译成以下内容,这似乎适用于 Play 2.6,以防有人需要:
def byteStringFilePartHandler: FilePartHandler[ByteString] =
case FileInfo(partName, filename, contentType) =>
Accumulator(Sink.fold[ByteString, ByteString](ByteString()) (accumulator, data) =>
accumulator ++ data
.mapMaterializedValue(fbs => fbs.map(bs =>
FilePart(partName, filename, contentType, bs)
)))
def multipartFormDataAsBytes: BodyParser[MultipartFormData[ByteString]] =
playBodyParsers.multipartFormData(byteStringFilePartHandler)
在控制器中使用它,请确保您注入 PlayBodyParsers
并在下面提供 ExecutionContext
、导入等:
import akka.stream.scaladsl.Sink
import akka.util.ByteString
import javax.inject._
import play.api.libs.streams.Accumulator
import play.api.mvc.MultipartFormData.FilePart
import play.api.mvc._
import play.core.parsers.Multipart.FileInfo, FilePartHandler
import scala.concurrent.ExecutionContext
@Singleton
class HomeController @Inject()(cc: ControllerComponents, playBodyParsers: PlayBodyParsers)
(implicit ec: ExecutionContext) extends AbstractController(cc)
def index = Action(multipartFormDataAsBytes) request =>
request.body.file("image").foreach((image) =>
val arr = image.ref.toByteBuffer.array()
println(arr)
)
Ok("got bytes!")
【讨论】:
【参考方案3】:根据 Matt 的回答(嗨,Matt!),我最终需要为 Play 2.8 稍微调整一下(我怀疑 API 会进一步发展):
def byteStringFilePartHandler: FilePartHandler[ByteString] =
case FileInfo(partName, filename, contentType, dispositionType) =>
Accumulator(Sink.fold[ByteString, ByteString](ByteString()) (accumulator, data) =>
accumulator ++ data
.mapMaterializedValue(fbs => fbs.map(bs =>
FilePart(partName, filename, contentType, bs)
)))
def multipartFormDataAsBytes: BodyParser[MultipartFormData[ByteString]] =
controllerComponents.parsers.multipartFormData(byteStringFilePartHandler)
由于我的用例是上传文本文件,因此我从生成的 request
中提取该文件:
val body: String = request.body.files.head.ref.utf8String
(为了安全起见,一些不那么快速和肮脏的代码会在那里使用headOption
。)
【讨论】:
请问可以分享导入类吗?我面临Accumulator(Sink.fold[ByteString, ByteString](ByteString())
方法未指定值参数的问题:f: Function2[Any, Any, Any]
@Saurabh47g 抱歉 -- 我不再从事该工作,因此我无法再访问代码...以上是关于在 Play2 / Scala 中从内存中的 MultipartFormData 中提取文件的主要内容,如果未能解决你的问题,请参考以下文章