使用akka-stream两次使用source

时间:2017-03-20 00:51:29

标签: playframework streaming akka-stream

我将Play框架用于我构建的Web应用程序。 Play 2.5使用Akka Stream API来允许流式传输请求/响应。

我有一个端点,传入的文件直接流式传输到Google云端硬盘。

我定义了BodyParser看起来像这样:

BodyParser("toDrive") { request =>
  Accumulator.source[ByteString].mapFuture { source =>
    Future.successful(Right("Done"))
  }
}

我使用来源(Source[ByteString, _])并将其提供给我使用Play提供的StreamedBody的{​​{1}}。

我想使用给定的WSClient并使用Source进行两次不同的HTTP调用。

我通过将相同的WSClient传递给两个不同的Source调用来尝试天真的方法,但它失败了。我认为我的问题的解决方案是广播。

我想从源代码中找出要创建2个来源WSClient的来源。

我还在玩WSClientSourceFlow。我试图理解这一切。

2 个答案:

答案 0 :(得分:0)

我想.alsoTo()的{​​{1}}方法就是你要找的。在内部,它只是广播。

答案 1 :(得分:0)

更新了解决方案:

  Accumulator[ByteString, Either[Result, String]] {
    val s1 = Sink
      .asPublisher[ByteString](fanout = false)
      .mapMaterializedValue(Source.fromPublisher)
      .mapMaterializedValue { source =>
        //do what you need with source
        Future.successful(Right("result 1"))
      }
    val s2 = Sink
      .asPublisher[ByteString](fanout = false)
      .mapMaterializedValue(Source.fromPublisher)
      .mapMaterializedValue { source =>
        //do what you need with source
        Future.successful(Right("result 2"))
      }

    def combine(val1: Future[Either[Result, String]],
                val2: Future[Either[Result, String]]): Future[Either[Result, String]] = {
      for {
        res1 <- val1
        res2 <- val2
      } yield {
        // do something with your result
        res1.right.flatMap(val1 => res2.right.map(val2 => val1 + val2))
      }
    }

    Sink.fromGraph(GraphDSL.create(s1, s2)(combine) { implicit b => (sink, sink2) =>
      import GraphDSL.Implicits._

      val broadcast = b.add(Broadcast[ByteString](2))

      broadcast ~> sink
      broadcast ~> sink2

      SinkShape(broadcast.in)
    })
  }

给出一点解释(AFAIK)。我创建了2个接收器并将它们组合在一个接收器后面。 Accumulator.apply需要1 Sink[E, Future[A]]BodyParser强制我使用ByteString作为Etype数据在>接收器中

所以2个接收器需要ByteString并实现为Future[String]。我将Sink转换为Source,因为我使用的API(WsClient)可以将Source作为正文。这个API为我提供了Future[HttpResponse](为了解决方案,我已将此简化为Future[String],但您可以在此处执行任何操作。

现在这是akka-streams API发挥作用的地方。我强烈建议您查看documentation以便更好地理解。话虽如此,我在这里使用GraphDSL API将我的2个水槽组合在一个后面。进入暴露的接收器的任何ByteString都会被发送到2个内部接收器中。

注意:有一个方便的Sink.combine函数可以获取n个流并将它们组合在一个流后面。但使用此功能意味着loosing the materialized value(在本例中为Future[String]

下面提出的原始解决方案无法正常运行。它只向其中一个源发送数据。

播放Accumulator也可以通过提供Sink来创建。

我使用了这种方法,到目前为止这似乎有效:

BodyParser("toDrive") { request =>
  def sourceToFut(src: Source): Future[T] = ???

  Accumulator[ByteString, Either[Result, T]] {
    Sink
      .asPublisher[ByteString](fanout = true)
      .mapMaterializedValue(Source.fromPublisher)
      .mapMaterializedValue { source =>
        val upload1Fut = sourceToFut(source)
        val upload2Fut = sourceToFut(source)
        for {
          file1 <- upload1Fut
          file2 <- upload2Fut
        } yield {
          (file1, file2)
        }
      }
  }
} 

与我最初的方法相比,唯一有效的更改是我自己创建了Sink并允许fanout,因此我可以在两个不同的WSClient调用中使用两次来源。< / p>

你觉得@expert怎么样?