我有一个示例下载代码,如果文件没有压缩,工作正常,因为我知道长度,当我提供时,我认为流媒体播放不必将整个文件带入内存并且它可以工作。以下代码可以使用
def downloadLocalBackup() = Action {
var pathOfFile = "/opt/mydir/backups/big/backup"
val file = new java.io.File(pathOfFile)
val path: java.nio.file.Path = file.toPath
val source: Source[ByteString, _] = FileIO.fromPath(path)
logger.info("from local backup set the length in header as "+file.length())
Ok.sendEntity(HttpEntity.Streamed(source, Some(file.length()), Some("application/zip"))).withHeaders("Content-Disposition" -> s"attachment; filename=backup")
}
我不知道上面的流媒体如何处理磁盘读取之间的速度差异(比网络更快)。即使对于大文件,这也永远不会耗尽内存。但是,当我使用下面的代码,其中有zipOutput流时,我不确定内存不足的原因。当我尝试使用zip流时,不知何故相同的3GB文件无效。
def downloadLocalBackup2() = Action {
var pathOfFile = "/opt/mydir/backups/big/backup"
val file = new java.io.File(pathOfFile)
val path: java.nio.file.Path = file.toPath
val enumerator = Enumerator.outputStream { os =>
val zipStream = new ZipOutputStream(os)
zipStream.putNextEntry(new ZipEntry("backup2"))
val is = new BufferedInputStream(new FileInputStream(pathOfFile))
val buf = new Array[Byte](1024)
var len = is.read(buf)
var totalLength = 0L;
var logged = false;
while (len >= 0) {
zipStream.write(buf, 0, len)
len = is.read(buf)
if (!logged) {
logged = true;
logger.info("logging the while loop just one time")
}
}
is.close
zipStream.close()
}
logger.info("log right before sendEntity")
val kk = Ok.sendEntity(HttpEntity.Streamed(Source.fromPublisher(Streams.enumeratorToPublisher(enumerator)).map(x => {
val kk = Writeable.wByteArray.transform(x); kk
}),
None, Some("application/zip"))
).withHeaders("Content-Disposition" -> s"attachment; filename=backupfile.zip")
kk
}
答案 0 :(得分:3)
在第一个示例中,Akka Streams会为您处理所有细节。它知道如何在不将完整文件加载到内存中的情况下读取输入流。这就是使用Akka Streams作为explained in the docs的优势:
我们今天从互联网上消费服务的方式包括许多流数据实例,包括从服务下载以及上传到服务或点对点数据传输。关于数据作为元素流而不是整体是非常有用的,因为它匹配计算机发送和接收它们的方式(例如通过TCP),但它通常也是必需的,因为数据集经常变得太大而不能作为一个整体处理。我们在大型集群上进行计算或分析并将其称为“大数据”,其中处理它们的整个原则是通过顺序提供这些数据 - 作为流通过某些CPU。
...
[Akka Streams]的目的是提供一种直观而安全的方式来制定流处理设置,以便我们可以有效地执行它们并且使用有限的资源 - 不再使用OutOfMemoryErrors。为了实现这一目标,我们的流需要能够限制他们使用的缓冲,如果消费者无法跟上,他们需要能够减慢生产者的速度。此功能称为back-pressure,是Akka作为创始成员的Reactive Streams计划的核心。
在第二个示例中,您使用标准阻塞API自行处理输入/输出流。我不是100%确定写ZipOutputStream
如何在这里工作,但它有可能不会刷新写入并在close
之前累积所有内容。
好的一点是,您不需要手动处理此问题,因为Akka Streams提供了一种方法来Source
ByteString
个import javax.inject.Inject
import akka.util.ByteString
import akka.stream.scaladsl.{Compression, FileIO, Source}
import play.api.http.HttpEntity
import play.api.mvc.{BaseController, ControllerComponents}
class FooController @Inject()(val controllerComponents: ControllerComponents) extends BaseController {
def download = Action {
val pathOfFile = "/opt/mydir/backups/big/backup"
val file = new java.io.File(pathOfFile)
val path: java.nio.file.Path = file.toPath
val source: Source[ByteString, _] = FileIO.fromPath(path)
val gzipped = source.via(Compression.gzip)
Ok.sendEntity(HttpEntity.Streamed(gzipped, Some(file.length()), Some("application/zip"))).withHeaders("Content-Disposition" -> s"attachment; filename=backup")
}
}
:
AuthenticatesUsers.php