如何在Scala中通过Play Framework 2.5流式传输压缩文件? [英] How to stream zipped file (on the fly) via Play Framework 2.5 in scala?

查看:110
本文介绍了如何在Scala中通过Play Framework 2.5流式传输压缩文件?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想流式传输一些文件并即时对其进行压缩,因此用户可以将多个文件下载到一个压缩文件中,而无需将任何内容写入本地磁盘.但是,我当前的实现将所有内容保存在内存中,并且对于大文件将不起作用.有什么办法可以解决?

I want to stream some files and zip them on the fly, so users can download multiple files into a single zipped file without writing anything to the local disk. However, my current implementation holds everything in the memory, and will no work for large files. Is there any way to fix it?

我正在查看此实现: https://gist.github.com/kirked/03c7f111de0e9a1f74377bf60d3f /a>,但不知道如何使用它.

I was looking at this implementation: https://gist.github.com/kirked/03c7f111de0e9a1f74377bf95d3f0f60, but couldn't figure out how to use it.

 import java.io.{BufferedOutputStream, ByteArrayInputStream, ByteArrayOutputStream}
import java.util.zip.{ZipEntry, ZipOutputStream}
import akka.stream.scaladsl.{StreamConverters}
import org.apache.commons.io.FileUtils
import play.api.mvc.{Action, Controller}

class HomeController extends Controller {
  def single() = Action {
                         Ok.sendFile(
                           content = new java.io.File("C:\\Users\\a.csv"),
                           fileName = _ => "a.csv"
                         )
                       }

  def zip() = Action {
                     Ok.chunked(StreamConverters.fromInputStream(fileByteData)).withHeaders(
                       CONTENT_TYPE -> "application/zip",
                       CONTENT_DISPOSITION -> s"attachment; filename = test.zip"
                     )
                   }

  def fileByteData(): ByteArrayInputStream = {
    val fileList = List(
      new java.io.File("C:\\Users\\a.csv"),
      new java.io.File("C:\\Users\\b.csv")
    )

    val baos = new ByteArrayOutputStream()
    val zos = new ZipOutputStream(new BufferedOutputStream(baos))

    try {
      fileList.map(file => {
        zos.putNextEntry(new ZipEntry(file.toPath.getFileName.toString))
        zos.write(FileUtils.readFileToByteArray(file))
        zos.closeEntry()
      })
    } finally {
      zos.close()
    }

    new ByteArrayInputStream(baos.toByteArray)
  }
}

推荐答案

您可以使用Java的管道机制来代替使用ByteArrayOutputStream在数组中缓冲内容,然后将它们放入ByteArrayInputStream中.

Instead of using a ByteArrayOutputStream to buffer the contents in an array then putting them into a ByteArrayInputStream you could use Java's piping mechanism.

这是一个草图解决方案:

Here's a sketch solution:

def zip() = Action {
  // Create Source that listens to an OutputStream
  // and pass it to `fileByteData` method.
  val zipSource: Source[ByteString, Unit] =
    StreamConverters
      .asOutputStream()
      .mapMaterializedValue(fileByteData)
  Ok.chunked(zipSource).withHeaders(
    CONTENT_TYPE -> "application/zip",
    CONTENT_DISPOSITION -> s"attachment; filename = test.zip")
}

// Send the file data, given an OutputStream to write to.
def fileByteData(os: OutputStream): Unit = {
  val fileList = List(
    new java.io.File("C:\\Users\\a.csv"),
    new java.io.File("C:\\Users\\b.csv")
  )

  val zos = new ZipOutputStream(os)
  val buffer: Array[Byte] = new Array[Byte](2048)
  try {
    for (file <- fileList) {
      zos.putNextEntry(new ZipEntry(file.toPath.getFileName.toString))
      val fis = new Files.newInputStream(file.toPath)
      try {
        @tailrec
        def zipFile(): Unit = {
          val bytesRead = fis.read(buffer)
          if (bytesRead == -1) () else {
            zos.write(buffer, 0, bytesRead)
            zipFile()
          }
        }
        zipFile()
      } finally fis.close()
      zos.closeEntry()
    }
  } finally {
    zos.close()
  }
}

这只是方法的概述.您还需要确保: -线程正常-fileByteData有望在与发送线程不同的线程上运行 -错误处理正常-例如如果服务器(例如找不到文件)或客户端(早期断开连接)出现错误,则所有流都将正确关闭

This is just an outline of an approach. You'll also want to make sure: - the threading is OK - the fileByteData will hopefully run on a different thread to the sending thread - the error handling is OK - e.g. all streams are closed properly if there's an error on either the server (e.g. file not found) or client side (early disconnect)

这篇关于如何在Scala中通过Play Framework 2.5流式传输压缩文件?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆