如何将Akka Streams Websocket与Actor集成以支持背压

时间:2019-02-12 06:41:23

标签: scala websocket akka akka-stream

我想在Akka Streams Websocket和支持反压的Actor之间实现集成。

我的websocket协议支持:

  • 请求/响应模式,其中每个请求精确地产生一个响应
  • 事件模式,可以在任何时间将事件发布到WS

由于事件模式,我不能使用简单的Flow,而是需要分为SinkSource,以便我可以在不通过WS接收消息的情况下发送事件至少我是这样理解的。

将来,WS上收到的请求将通过grpc转发到某个服务,并且当该服务响应时,该响应也将通过WS返回。理想情况下,我希望在整个链中都产生背压。

我目前的简单Ping / Pong协议实现看起来像这样,但

  • 我不确定这是否是实现它的最佳方法。
  • 我不确定在Actor中offer方法的错误情况下该怎么做
  • 我不确定要反序列化flatMapConcat时是否要使用TextMessage.textStream TextMessage
  • 我不确定以后在通过grpc从Actor进一步向其他服务发送消息时是否可以支持背压

如果有人可以查看我的代码并告诉我在这里我可以做得更好,我将非常感激。

def wsFlow:Flow[Message, Message, NotUsed] = {

  // the actor that is used to handle a single WS
  val ws:ActorRef = system.actorOf(WebSocketActor.props())

  // used to send messages via the WS to the client
  val wsSender:Source[Message, NotUsed] =
    Source
      .queue(bufferSize = 500, overflowStrategy = OverflowStrategy.backpressure)
      .map { response:Response =>
        response match {
          case pr:PingResponse => TextMessage(pr.toJson.compactPrint)
        }
      }
      .mapMaterializedValue { wsQueue =>
        // the wsQueue is used to send messages to the client
        ws ! WsConnect(wsQueue)
        NotUsed // dont expose the wsQueue, change materialized value to NotUsed
      }

  // used to receive messages via the WS from the client
  val wsHandler:Sink[Message, NotUsed] =
    Flow[Message]
      .flatMapConcat {
        case tm:TextMessage => tm.textStream
        case bm:BinaryMessage =>
          // ignore binary messages but drain content to avoid the stream being clogged
          bm.dataStream.runWith(Sink.ignore)
          Source.empty
      }
      .map(text => JsonParser(text).convertTo[Request])
      .to(Sink.actorRef(ws, WsDisconnect))

  Flow.fromSinkAndSource(wsHandler, wsSender)
}
class WebSocketActor(implicit ec:ExecutionContext) extends Actor {
  private var wsQueue:Option[SourceQueueWithComplete[Response]] = None

  override def receive: Receive = {
    case WsConnect(ref) => wsQueue = Some(ref)

    case WsDisconnect =>
      wsQueue.foreach(_.complete())
      wsQueue = None

    case ping:PingRequest =>
      val response = PingResponse(200, ping.clientRequestId, Version(1))
      wsOffer(response)
  }

  private def wsOffer(msg:Response):Unit = wsQueue.foreach(queue => {
    queue.offer(msg).foreach {
      case QueueOfferResult.Enqueued    ⇒ ()
      case QueueOfferResult.Dropped     ⇒ ???
      case QueueOfferResult.Failure(ex) ⇒ ???
      case QueueOfferResult.QueueClosed ⇒ ???
    }
  })
}

0 个答案:

没有答案