Akka Streams WebSocket发送有关任意事件的信息

时间:2018-05-13 19:38:20

标签: scala websocket akka-stream

我想实现一个服务,其中许多客户端可以使用WebSocket连接到服务器。服务器应该能够在任意内部事件上向所有连接的客户端广播消息。到目前为止,我有这段代码:

global-scripts:
  version: 1.x
  js: 
    js/scripts.js: {}
    js/responsiveTabs.js: {}
  dependencies:
    - core/jquery
    - core/jquery.once
    - core/drupalSettings

它成功地向连接的客户端广播当前的滴答声。我应该如何修改此代码,以便能够广播任意消息,而不仅仅是预定的节拍?

澄清

“任意消息”我不是指文件或数据库等其他来源,而是能够将消息发送到专门的import akka.http.scaladsl.server.RouteResult.route2HandlerFlow import akka.http.scaladsl.server.Directives._ implicit val system = ActorSystem("Server") implicit val mat = ActorMaterializer() // The source to broadcast (just ints for simplicity) val dataSource = Source(1 to 1000).throttle(1, 1.second, 1, ThrottleMode.Shaping).map(_.toString) // Go via BroadcastHub to allow multiple clients to connect val runnableGraph: RunnableGraph[Source[String, NotUsed]] = dataSource.toMat(BroadcastHub.sink(bufferSize = 256))(Keep.right) val producer: Source[String, NotUsed] = runnableGraph.run() // Optional - add sink to avoid backpressuring the original flow when no clients are attached producer.runWith(Sink.ignore) val wsHandler: Flow[Message, Message, NotUsed] = Flow[Message] .mapConcat(_ => Nil) // Ignore any data sent from the client .merge(producer) // Stream the data we want to the client .map(l => TextMessage(l.toString)) val route = path("ws") { handleWebSocketMessages(wsHandler) } val port = 8080 println("Starting up route") Http().bindAndHandle(route2HandlerFlow(route), "127.0.0.1", port) println(s"Started HTTP server on port $port") 并将其转发到当前连接的客户端。这样的消息可能是某些内部系统事件的结果,可能随时发生。

2 个答案:

答案 0 :(得分:2)

您所要做的就是更改dataSource。

从csv文件中获取数据:

val dataSource = FileIO.fromPath(Paths.get("file.csv"))
  .via(Framing.delimiter(ByteString("\n"), 256, true)
  .map(_.utf8String))

从SQS(Alpakka)获取数据:

val dataSource = SqsSource(queue, sqsSourceSettings).take(100).map(_.getBody)

使用Slick(Alpakka)从表格中获取数据:

val dataSource = Slick.source(sql"SELECT NAME FROM USERS".as[String])

基本上你需要了解三件事:

  • 来源:一个输出
  • 流程:一个输入,一个输出
  • Sink:一个输入。

了解这一点,您可以构建线性管道,如:

source.via(flow1).via(flow2).runWith(sink)

所以,你可以轻松地插上"将源存入现有管道并使用您想要的任何接收器运行它们:

val pipeline = flow1.via(flow2)

val fileSource = FileIO.fromPath(Paths.get("file.csv"))
  .via(Framing.delimiter(ByteString("\n"), 256, true)
  .map(_.utf8String))
  .via(pipeline)
  .runWith(sink)

val sqsSource = Slick
  .source(sql"SELECT NAME FROM USERS".as[String])
  .via(pipeline)
  .runWith(sink)

val slickFlow = SqsSource(queue, sqsSourceSettings).take(100)
  .map(_.getBody)
  .via(pipeline)
  .runWith(sink)

编辑:嗯,除了actorRef策略之外,你还可以使用Source.queue并通过调用queue.offer来生成你的消息:

def source = Source
  .queue(Int.MaxValue, OverflowStrategy.backpressure)
  .map { name: String => s"hello, $name" }
  .toMat(BroadcastHub.sink[String])(Keep.both)
  .run()

def wsHandler(s: Source[String, NotUsed]): Flow[Message, Message, NotUsed] = Flow[Message]
  .mapConcat(_ => Nil)
  .merge(s)
  .map(TextMessage(_))

import scala.concurrent.duration._

val websocketRoute =
  path("greeter" / Segment) { name =>
    val (queue, s) = source

    Source
      .tick(
        initialDelay = 1 second,
        interval = 1 second,
        tick = None
      )
      .map { _ =>
        queue.offer(name)
      }
      .runWith(Sink.ignore)

    handleWebSocketMessages(wsHandler(s))
  }

外部链接:

答案 1 :(得分:2)

一个想法是使用Source.actorRef

val (actor, source) = Source.actorRef[String](10, akka.stream.OverflowStrategy.dropTail)
  .toMat(BroadcastHub.sink[String])(Keep.both)
  .run()

val wsHandler: Flow[Message, Message, NotUsed] = Flow[Message]
  .mapConcat(_ => Nil)
  .merge(source)
  .map(l => TextMessage(l.toString))

如果有下游需求,则会发出发送到具体化ActorRef的消息。如果没有下游需求,则缓冲元素,如果缓冲区已满,则使用提供的溢出策略。请注意,此方法没有背压。您可以将Source的消息以及任意消息发送给此actor:

Source(1 to 1000)
  .throttle(1, 1.second, 1, ThrottleMode.Shaping)
  .map(_.toString)
  .runForeach(msg => actor ! msg)

actor ! "bacon"
actor ! "ribeye"