Scala 使用分块响应通过Spray从Play枚举器流式传输数据

Scala 使用分块响应通过Spray从Play枚举器流式传输数据,scala,playframework,spray,reactivemongo,Scala,Playframework,Spray,Reactivemongo,我从反应式Mongo中提取数据,需要通过Spray Rest API。我本希望通过大量的回答来做到这一点。然而,我发现,从被动Mongo返回的枚举器能够以比网络连接所能处理的更快的速度通过喷洒。结果是连接被终止 我能够在一个中间演员中使用Spray Ack功能来解决这个问题。这与阻塞等待一起允许我在枚举器上创建反压力。然而,我真的不想等待。我想找出一种以非阻塞方式通过Spray传输数据的方法 这可能吗?如果我能补上缺失的部分,我几乎没有什么想法可以奏效 1) 以非阻塞方式在枚举器上创建反压力(不

我从反应式Mongo中提取数据,需要通过Spray Rest API。我本希望通过大量的回答来做到这一点。然而,我发现,从被动Mongo返回的枚举器能够以比网络连接所能处理的更快的速度通过喷洒。结果是连接被终止

我能够在一个中间演员中使用Spray Ack功能来解决这个问题。这与阻塞等待一起允许我在枚举器上创建反压力。然而,我真的不想等待。我想找出一种以非阻塞方式通过Spray传输数据的方法

这可能吗?如果我能补上缺失的部分,我几乎没有什么想法可以奏效

1) 以非阻塞方式在枚举器上创建反压力(不知道如何做。建议?)

2) 将枚举数拆分为更小的枚举数。仅在前一个枚举数完成后才开始使用每个枚举数。我可以用演员来做这件事。我这里缺少的是一种将较大的枚举数分解为较小的枚举数的方法

3) 使用类似“Enumerate.take”的方法。我会从枚举器中获取一些记录,然后当我准备好时,再获取一些。这实际上与2)的解决方案相同,但从一个稍微不同的角度来看。然而,这需要枚举器维护状态。是否有一种方法可以对同一枚举器多次使用Enumerate.take,而无需每次从头开始重新启动

有人能提供可能有效的其他建议吗?如果不可能,请告诉我


我正在使用Play Enumerators 2.3.5

我认为其思想是实现一个
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)

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

经过大量的实验(以及stackoverflow的帮助),我终于找到了一个似乎有效的解决方案。它使用喷射式分块响应,并围绕该响应构建迭代对象

此处包含相关代码段:

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)
    }
  }
}

我同意,这似乎很有希望,在阅读你的文章之前,我一直在追求这个确切的想法。然而,我始终无法理解语法。您的示例中的“handleData”在哪里被调用?我们从哪里得到最初的“输入”值?我不确定在游戏2.3.5中如何实现这一点;我在代码中添加了一个。是的,我相信我现在已经解决了。你的帖子肯定有帮助。我不得不添加一个初始的iteratee,并修改一些其他东西来让它工作,但我想我现在有了它。感谢您的帮助。将“handleData”方法递归会有问题吗?也就是说,如果有大量数据,这最终会导致堆栈溢出吗?还是有一些优化措施可以防止这种情况发生?我希望Iteratee能对
未来[B]
做些聪明的事情,但我不知道;我只能建议你测试一下。请把你答案的重要部分放在SO中,而不是链接到你的博客(它已经不存在了),更新后包含了原来博客文章中的代码片段,现在已经不存在了。