Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/scala/19.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Scala Play 2.x:使用迭代对象的反应式文件上传_Scala_File Upload_Playframework 2.0_Azure Storage_Iterate - Fatal编程技术网

Scala Play 2.x:使用迭代对象的反应式文件上传

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字节,因此它太小,无法向云端

我将从这个问题开始:如何使用Scala API的
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)
  }

}