Scala Play 2.x:使用迭代对象的反应式文件上传
我将从这个问题开始:如何使用Scala API的Scala Play 2.x:使用迭代对象的反应式文件上传,scala,file-upload,playframework-2.0,azure-storage,iterate,Scala,File Upload,Playframework 2.0,Azure Storage,Iterate,我将从这个问题开始:如何使用Scala API的Iteratee将文件上载到云存储(在我的情况下是Azure Blob存储,但我认为现在它不是最重要的) 背景: 我需要将输入分块到大约1MB的块中,以便将大型媒体文件(300MB+)存储为Azure的BlockBlobs。不幸的是,我的Scala知识仍然很差(我的项目是基于Java的,Scala在其中的唯一用途是上传控制器) 我尝试了这段代码:(作为输入迭代对象)-它工作得很好,但我可以使用的每个元素的大小都是8192字节,因此它太小,无法向云端
Iteratee
将文件上载到云存储(在我的情况下是Azure Blob存储,但我认为现在它不是最重要的)
背景:
我需要将输入分块到大约1MB的块中,以便将大型媒体文件(300MB+)存储为Azure的BlockBlobs
。不幸的是,我的Scala知识仍然很差(我的项目是基于Java的,Scala在其中的唯一用途是上传控制器)
我尝试了这段代码:(作为输入迭代对象
)-它工作得很好,但我可以使用的每个元素
的大小都是8192字节,因此它太小,无法向云端发送几百兆的文件
我必须说这对我来说是一种全新的方式,很可能我误解了什么(我不想说我误解了一切;>)
我将感谢任何提示或链接,这将有助于我的主题。如果有任何类似用法的例子,这将是我得到这个想法的最佳选择 基本上,您首先需要将输入重新压缩为更大的块,1024*1024字节
首先,让我们有一个Iteratee
,它将消耗多达1m的字节(可以让最后一个块更小)
使用它,我们可以构造一个枚举(适配器),它将使用一个名为grouped:
val rechunkAdapter:Enumeratee[Array[Byte],Array[Byte]] =
Enumeratee.grouped(consumeAMB)
这里分组使用一个Iteratee
来确定在每个块中放入多少。它使用我们的ConsumerAMB来实现这一点。这意味着结果是一个枚举
,它将输入重新压缩到1MB的数组[字节]
现在我们需要编写BodyParser
,它将使用Iteratee.foldM
方法发送每个字节块:
val writeToStore: Iteratee[Array[Byte],_] =
Iteratee.foldM[Array[Byte],_](connectionHandle){ (c,bytes) =>
// write bytes and return next handle, probable in a Future
}
foldM传递一个状态,并在其传递的函数中使用它来返回新的未来状态。foldM将不会再次调用该函数,直到Future
完成并且存在可用的输入块
主体解析器将重新缓存输入并将其推入存储:
BodyParser( rh => (rechunkAdapter &>> writeToStore).map(Right(_)))
返回右键表示在主体解析结束时返回主体(这里正好是处理程序)。对于那些也在尝试解决此流问题的人,您也可以使用中已经实现的内容,而不是编写一个全新的主体解析器。
您可以实现如下内容来覆盖默认处理程序handleFilePartAsTemporaryFile
def handleFilePartAsS3FileUpload: PartHandler[FilePart[String]] = {
handleFilePart {
case FileInfo(partName, filename, contentType) =>
(rechunkAdapter &>> writeToS3).map {
_ =>
val compRequest = new CompleteMultipartUploadRequest(...)
amazonS3Client.completeMultipartUpload(compRequest)
...
}
}
}
def multipartFormDataS3: BodyParser[MultipartFormData[String]] = multipartFormData(handleFilePartAsS3FileUpload)
我能够做到这一点,但我仍然不确定是否整个上传过程是流式的。我尝试了一些大文件,似乎只有当整个文件从客户端发送时,S3上传才会开始
我查看了上面的解析器实现,我认为所有内容都是使用Iteratee连接的,因此文件应该是流式的。
如果有人对此有所了解,这将非常有帮助。如果您的目标是流式传输到S3,那么我已经实现并测试了一个助手:
def uploadStream(bucket: String, key: String, enum: Enumerator[Array[Byte]])
(implicit ec: ExecutionContext): Future[CompleteMultipartUploadResult] = {
import scala.collection.JavaConversions._
val initRequest = new InitiateMultipartUploadRequest(bucket, key)
val initResponse = s3.initiateMultipartUpload(initRequest)
val uploadId = initResponse.getUploadId
val rechunker: Enumeratee[Array[Byte], Array[Byte]] = Enumeratee.grouped {
Traversable.takeUpTo[Array[Byte]](5 * 1024 * 1024) &>> Iteratee.consume()
}
val uploader = Iteratee.foldM[Array[Byte], Seq[PartETag]](Seq.empty) { case (etags, bytes) =>
val uploadRequest = new UploadPartRequest()
.withBucketName(bucket)
.withKey(key)
.withPartNumber(etags.length + 1)
.withUploadId(uploadId)
.withInputStream(new ByteArrayInputStream(bytes))
.withPartSize(bytes.length)
val etag = Future { s3.uploadPart(uploadRequest).getPartETag }
etag.map(etags :+ _)
}
val futETags = enum &> rechunker |>>> uploader
futETags.map { etags =>
val compRequest = new CompleteMultipartUploadRequest(bucket, key, uploadId, etags.toBuffer[PartETag])
s3.completeMultipartUpload(compRequest)
}.recoverWith { case e: Exception =>
s3.abortMultipartUpload(new AbortMultipartUploadRequest(bucket, key, uploadId))
Future.failed(e)
}
}
将以下内容添加到配置文件中
play.http.parser.maxMemoryBuffer=256K您是否希望将输入重新压缩到更大的块中?很好的解释。两个问题:(1)Iteratee.foldM
做什么?在这里的API文档中找不到:(2)为什么需要map(Right())
?如果您能在您的帖子中添加一些关于这些内容的内容,那就太好了。谢谢Sadek,我需要一些时间来测试它。@Sadache:Iteratee.foldM[E,A]似乎放在主文件中,而不是2.0.3
中,这是真的吗?我们将使用稳定版本进行此生产。你计划很快发布新版本吗?是的,但你也可以暂时复制foldM方法的代码。仅供参考,在上有更多关于这个答案的讨论。你如何在控制器上使用这种方法?
def uploadStream(bucket: String, key: String, enum: Enumerator[Array[Byte]])
(implicit ec: ExecutionContext): Future[CompleteMultipartUploadResult] = {
import scala.collection.JavaConversions._
val initRequest = new InitiateMultipartUploadRequest(bucket, key)
val initResponse = s3.initiateMultipartUpload(initRequest)
val uploadId = initResponse.getUploadId
val rechunker: Enumeratee[Array[Byte], Array[Byte]] = Enumeratee.grouped {
Traversable.takeUpTo[Array[Byte]](5 * 1024 * 1024) &>> Iteratee.consume()
}
val uploader = Iteratee.foldM[Array[Byte], Seq[PartETag]](Seq.empty) { case (etags, bytes) =>
val uploadRequest = new UploadPartRequest()
.withBucketName(bucket)
.withKey(key)
.withPartNumber(etags.length + 1)
.withUploadId(uploadId)
.withInputStream(new ByteArrayInputStream(bytes))
.withPartSize(bytes.length)
val etag = Future { s3.uploadPart(uploadRequest).getPartETag }
etag.map(etags :+ _)
}
val futETags = enum &> rechunker |>>> uploader
futETags.map { etags =>
val compRequest = new CompleteMultipartUploadRequest(bucket, key, uploadId, etags.toBuffer[PartETag])
s3.completeMultipartUpload(compRequest)
}.recoverWith { case e: Exception =>
s3.abortMultipartUpload(new AbortMultipartUploadRequest(bucket, key, uploadId))
Future.failed(e)
}
}