我将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
的来源。
我还在玩WSClient
,Source
和Flow
。我试图理解这一切。
答案 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
作为E
,type
数据在>>接收器中。
所以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怎么样?