使用akka流和喷雾将csv文件流式传输到浏览器

时间:2015-11-05 07:16:57

标签: scala akka spray akka-stream

如何将Source[String, Unit]连接到流媒体演员?

我认为来自https://gist.github.com/whysoserious/96050c6b4bd5fedb6e33StreamingActor的修改版本效果很好,但我很难连接各个部分。

鉴于source: Source[String, Unit]ctx: RequestContext,我认为修改后的StreamingActor应与actorRefFactory.actorOf(fromSource(source, ctx))相符。

供参考,上面的要点:

import akka.actor._
import akka.util.ByteString
import spray.http.HttpEntity.Empty
import spray.http.MediaTypes._
import spray.http._
import spray.routing.{HttpService, RequestContext, SimpleRoutingApp}

object StreamingActor {

  // helper methods

  def fromString(iterable: Iterable[String], ctx: RequestContext): Props = {
    fromHttpData(iterable.map(HttpData.apply), ctx)
  }
  def fromStringAndCharset(iterable: Iterable[String], ctx: RequestContext, charset: HttpCharset): Props = {
    fromHttpData(iterable.map(HttpData.apply), ctx)
  }
  def fromByteArray(iterable: Iterable[Array[Byte]], ctx: RequestContext): Props = {
    fromHttpData(iterable.map(HttpData.apply), ctx)
  }
  def fromByteString(iterable: Iterable[ByteString], ctx: RequestContext): Props = {
    fromHttpData(iterable.map(HttpData.apply), ctx)
  }
  def fromHttpData(iterable: Iterable[HttpData], ctx: RequestContext): Props = {
    Props(new StreamingActor(iterable, ctx))
  }

  // initial message sent by StreamingActor to itself
  private case object FirstChunk

  // confirmation that given chunk was sent to client
  private case object ChunkAck

}

class StreamingActor(chunks: Iterable[HttpData], ctx: RequestContext) extends Actor with HttpService with ActorLogging {

  import StreamingActor._

  def actorRefFactory = context

  val chunkIterator: Iterator[HttpData] = chunks.iterator

  self ! FirstChunk

  def receive = {

    // send first chunk to client
    case FirstChunk if chunkIterator.hasNext =>
      val responseStart = HttpResponse(entity = HttpEntity(`text/html`, chunkIterator.next()))
      ctx.responder ! ChunkedResponseStart(responseStart).withAck(ChunkAck)

    // data stream is empty. Respond with Content-Length: 0 and stop
    case FirstChunk =>
      ctx.responder ! HttpResponse(entity = Empty)
      context.stop(self)

    // send next chunk to client  
    case ChunkAck if chunkIterator.hasNext =>
      val nextChunk = MessageChunk(chunkIterator.next())
      ctx.responder ! nextChunk.withAck(ChunkAck)

    // all chunks were sent. stop.  
    case ChunkAck =>
      ctx.responder ! ChunkedMessageEnd
      context.stop(self)

    //   
    case x => unhandled(x)
  }

}

1 个答案:

答案 0 :(得分:2)

我认为你使用StreamingActor过分复杂了你试图解决的根本问题。此外,问题中的StreamingActor将为单个HttpResponse生成多个HttpRequest值,每个Chunk为1。这是低效的,因为您只需返回1个HttpReponse,其中HttpEntity.Chunked作为数据流源的实体。

通用并发设计

演员是为州,例如保持连接之间的运行计数器。即便如此,Agent还有很多基础,还有类型检查的额外好处(与Actor.receive不同,后者在运行时将死信邮箱转换为唯一的类型检查器)。

并发计算,而不是状态,应该用(按顺序)处理:

  1. 期货作为首要考虑因素:可组合,编译时安全检查,以及大多数情况下的最佳选择。

  2. akka Streams:可组合,编译时间类型安全检查,非常有用但是由于方便的反压功能而产生了很多overhead。 Steams也是HttpResponse实体的形成方式,如下所示。

  3. 流式传输CSV文件

    您的基本问题是如何使用Streams将csv文件流式传输到http客户端。您可以从创建数据源并将其嵌入HttpResponse开始:

    def lines() = scala.io.Source.fromFile("DataFile.csv").getLines()
    
    import akka.util.ByteString
    import akka.http.model.HttpEntity
    
    def chunkSource : Source[HttpEntity.ChunkStreamPart, Unit] = 
      akka.stream.scaladsl.Source(lines)
                          .map(ByteString.apply)
                          .map(HttpEntity.ChunkStreamPart.apply)
    
    def httpFileResponse = 
      HttpResponse(entity = HttpEntity.Chunked(ContentTypes.`text/plain`, chunkSource))
    

    然后,您可以为任何请求提供此响应:

    val fileRequestHandler = {
      case HttpRequest(GET, Uri.Path("/csvFile"), _, _, _) => httpFileResponse
    }   
    

    然后将fileRequestHandler嵌入到服务器路由逻辑中。