使用Chunked Responses通过Spray从Play Enumerator流式传输数据

时间:2014-10-27 22:05:48

标签: scala playframework spray reactivemongo

我从Reactive Mongo中提取数据,我需要通过Spray Rest API。我曾希望通过Chunked Responses做到这一点。但是我发现从Reactive Mongo返回的Enumerator能够比网络连接更快地通过Spray。结果是连接终止。

我能够使用中间actor中的Spray Ack功能解决此问题。这与Blocking Await一起允许我在Enumerator上创建背压。但是,我真的不想要Await。我想找出一种通过Spray以非阻塞方式流式传输数据的方法。

这可能吗?如果我能填补缺失的部分,我几乎没有什么想法可以解决。

1)以非阻塞方式在枚举器上创建背压(不知道如何做到这一点。建议?)

2)将枚举器分解为较小的枚举器。只有在前一个枚举器完成后才开始使用每个枚举器。我可以使用演员来做到这一点。我在这里缺少的是一种将较大的枚举器分解为较小的枚举器的方法。

3)使用" Enumeratee.take"方法。我会从枚举器中获取一些记录,然后在我准备好的时候再拿一些。这与2)实际上是相同的解决方案,但是从略微不同的角度来看。这将要求枚举器维持状态。有没有办法在同一个枚举器中多次使用Enumeratee.take,而不是每次从头重新开始?

任何人都可以提供可能有效的替代建议吗?或者如果不可能,请告诉我。

我正在使用Play Enumerators 2.3.5

2 个答案:

答案 0 :(得分:1)

我认为您的想法是实现Iteratee fold方法仅在接收到Spray Ack后调用提供的回调。类似的东西:

def handleData(input: Input[String]) = new Iteratee[String] {
  def fold[B](folder: Step[Error, String] => Future[B]): Future[B] = {
    (sprayActor ? input).flatMap {
      case success => folder(Cont(handleData))
      case error => folder(Error(...))
      case done => ...
    }
  }
}

val initialIteratee = new Iteratee[String] {
  def fold[B](folder: Step[Error, String] => Future[B]) = folder(Cont(handleData))
}

enumerator.run(initialIteratee)

这应该是非阻塞的,但确保只在上一个块成功后才发送下一个块。

答案 1 :(得分:0)

经过大量的实验(以及stackoverflow的帮助),我能够找到一个似乎有效的解决方案。它使用Spray Chunked Responses并围绕它建立一个迭代。

此处包含相关的代码段:

ChunkedResponder.scala

package chunkedresponses

import akka.actor.{Actor, ActorRef}
import spray.http.HttpHeaders.RawHeader
import spray.http._

object ChunkedResponder {
  case class Chunk(data: HttpData)
  case object Shutdown
  case object Ack
}

class ChunkedResponder(contentType: ContentType, responder: ActorRef) extends Actor {
  import ChunkedResponder._
  def receive:Receive = {
    case chunk: Chunk =>
      responder.forward(ChunkedResponseStart(HttpResponse(entity = HttpEntity(contentType, chunk.data))).withAck(Ack))
      context.become(chunking)
    case Shutdown =>
      responder.forward(HttpResponse(headers = List(RawHeader("Content-Type", contentType.value))).withAck(Ack))
      context.stop(self)
  }

  def chunking:Receive = {
    case chunk: Chunk =>
      responder.forward(MessageChunk(chunk.data).withAck(Ack))
    case Shutdown =>
      responder.forward(ChunkedMessageEnd().withAck(Ack))
      context.stop(self)
  }
}

ChunkIteratee.scala

package chunkedresponses

import akka.actor.ActorRef
import akka.util.Timeout
import akka.pattern.ask
import play.api.libs.iteratee.{Done, Step, Input, Iteratee}
import spray.http.HttpData
import scala.concurrent.duration._

import scala.concurrent.{ExecutionContext, Future}

class ChunkIteratee(chunkedResponder: ActorRef) extends Iteratee[HttpData, Unit] {
  import ChunkedResponder._
  private implicit val timeout = Timeout(30.seconds)

  def fold[B](folder: (Step[HttpData, Unit]) => Future[B])(implicit ec: ExecutionContext): Future[B] = {
    def waitForAck(future: Future[Any]):Iteratee[HttpData, Unit] = Iteratee.flatten(future.map(_ => this))

    def step(input: Input[HttpData]):Iteratee[HttpData, Unit] = input match {
      case Input.El(e) => waitForAck(chunkedResponder ? Chunk(e))
      case Input.Empty => waitForAck(Future.successful(Unit))
      case Input.EOF =>
        chunkedResponder ! Shutdown
        Done(Unit, Input.EOF)
    }

    folder(Step.Cont(step))
  }
}

package.scala

import akka.actor.{ActorContext, ActorRefFactory, Props}
import play.api.libs.iteratee.Enumerator
import spray.http.{HttpData, ContentType}
import spray.routing.RequestContext

import scala.concurrent.ExecutionContext

package object chunkedresponses {
  implicit class ChunkedRequestContext(requestContext: RequestContext) {
    def completeChunked(contentType: ContentType, enumerator: Enumerator[HttpData])
                       (implicit executionContext: ExecutionContext, actorRefFactory: ActorRefFactory) {
      val chunkedResponder = actorRefFactory.actorOf(Props(new ChunkedResponder(contentType, requestContext.responder)))
      val iteratee = new ChunkIteratee(chunkedResponder)
      enumerator.run(iteratee)
    }
  }
}