如何为Akka http websockets

时间:2017-07-16 19:29:22

标签: scala akka-stream akka-http

由于我无法弄清楚如何为akka http websocket流程添加错误流程,所以我已经在墙上敲了很长时间。我想要实现的是:

  • 消息来自WS客户端
  • 用json
  • 解析它
  • 如果消息格式正确,则将解析后的消息发送给演员
  • 如果邮件格式错误,则向客户端返回错误消息
  • 演员还可以向客户发送消息

没有错误处理这很容易,但我无法弄清楚如何添加错误。这就是我所拥有的:

type GameDecodeResult =
  Either[(String, io.circe.Error), GameLobby.LobbyRequest]

val errorFlow =
  Flow[GameDecodeResult]
    .mapConcat {
      case Left(err) => err :: Nil
      case Right(_) => Nil
    }
    .map { case (message, error) =>
      logger.info(s"failed to parse message $message", error)
      TextMessage(Error(error.toString).asJson.spaces2)
    }

val normalFlow = {
  val normalFlowSink =
    Flow[GameDecodeResult]
      .mapConcat {
        case Right(msg) => msg :: Nil
        case Left(_) => Nil
      }
      .map(req => GameLobby.IncomingMessage(userId, req))
      .to(Sink.actorRef[GameLobby.IncomingMessage](gameLobby, PoisonPill))

  val normalFlowSource: Source[Message, NotUsed] =
    Source.actorRef[GameLobby.OutgoingMessage](10, OverflowStrategy.fail)
      .mapMaterializedValue { outActor =>
        gameLobby ! GameLobby.UserConnected(userId, outActor)
        NotUsed
      }
      .map(outMessage => TextMessage(Ok(outMessage.message).asJson.spaces2))

  Flow.fromSinkAndSource(normalFlowSink, normalFlowSource)
}

val incomingMessageParser =
  Flow[Message]
    .flatMapConcat {
      case tm: TextMessage =>
        tm.textStream
      case bm: BinaryMessage =>
        bm.dataStream.runWith(Sink.ignore)
        Source.empty }
    .map { message =>
      decode[GameLobby.LobbyRequest](message).left.map(err => message -> err)
    }

这些是我定义的流程,我认为这应该足够好,但我不知道如何组装它们,而且akka流API的复杂性无济于事。这是我试过的:

val x: Flow[Message, Message, NotUsed] =
  GraphDSL.create(incomingMessageParser, normalFlow, errorFlow)((_, _, _)) { implicit builder =>
    (incoming, normal, error) =>
    import GraphDSL.Implicits._

    val partitioner = builder.add(Partition[GameDecodeResult](2, {
      case Right(_) => 0
      case Left(_) => 1
    }))

    val merge = builder.add(Merge[Message](2))

    incoming.in ~> partitioner ~> normal ~> merge
                   partitioner ~> error ~> merge
  }

但不可否认的是,我完全不知道GraphDSL.create是如何运作的,我可以使用~>箭头或者我在最后一部分在genreal中所做的事情。它只是不会打字检查,错误信息对我没有帮助。

1 个答案:

答案 0 :(得分:1)

在使用GraphDSL构建的Flow中需要修复的一些事项:

  1. 无需将3个子流传递给GraphDSL.create方法,因为这只需要自定义图表的具体化值。您已经确定图表的具体化值为NotUsed

  2. 使用incoming运算符连接~>时,需要将其插座(.out)连接到分区阶段。

  3. 每个GraphDSL定义块都需要返回图形的形状 - 即其外部端口。您可以返回FlowShape作为输入incoming.in作为输出,merge.out作为输出。这些将定义自定义流程的蓝图。

  4. 因为最后您想要获得Flow,所以您错过了最后一次创建调用来自您定义的图表。此致电是Flow.fromGraph(...)

  5. 下面的代码示例:

      val x: Flow[Message, Message, NotUsed] =
        Flow.fromGraph(GraphDSL.create() { implicit builder =>
          import GraphDSL.Implicits._
    
          val partitioner = builder.add(Partition[GameDecodeResult](2, {
            case Right(_) => 0
            case Left(_) => 1
          }))
    
          val merge = builder.add(Merge[Message](2))
          val incoming = builder.add(incomingMessageParser)
    
          incoming.out ~> partitioner
                          partitioner ~> normalFlow ~> merge
                          partitioner ~> errorFlow ~> merge
          FlowShape(incoming.in, merge.out)
        })