Play Framework WebSocket Actor Filtering

时间:2018-02-20 20:38:12

标签: scala websocket stream playframework-2.0

我正在考虑一个实现,我可以从Play框架Web应用程序中流式传输一些事件。有一组物联网设备可以发出事件和警报。这些设备由其ID标识。我有一个HTTP端点,我可以通过它获取这些设备的遥测信号。现在我想为警报和事件做同样的事情。所以我从这个简单的方法开始,首先在我的控制器中定义我的终点,如下所示:

  def events = WebSocket.accept[String, String] { request =>
    ActorFlow.actorRef { out =>
      EventsActor.props(out)
    }
  }

我的EventsActor:

class EventsActor(out: ActorRef) extends Actor {

  def receive = {
    case msg: String =>
      out ! ("I received your message: " + msg)
  }
}
object EventsActor {

  def props(out: ActorRef) =
    Props(new EventsActor(out))
}

现在,我对我的EventsActor做的不多,但是稍后这个Actor会将事件和警报消息推送到其中,然后将其路由到WebSocket端点。

现在我的要求是在WebSocket端点,当客户端建立连接时,他应该能够为他希望连接的IoT设备指定一个id,我应该能够将这个id传递给EventsActor我可以在哪里筛选包含传入ID的事件。

有关如何执行此操作的任何线索?

1 个答案:

答案 0 :(得分:0)

我做了一个快速的例子,告诉你如何解决这个问题。它还有很多不足之处,但我希望它对你有所启发!

你真正想要的是一个协调器/路由器,它可以跟踪哪些websocket actor正在监听哪些事件类型。您可以将该集线器注入所有已创建的actor,并将事件分派给它以将这些websocket actor注册到事件集。

object TelemetryHub {
    /** This is the message external sensors could use to stream the data in to the hub **/
    case class FreshData(eventId: UUID, data: Int)
    def props = Props(new TelemetryHub)
}

class TelemetryHub extends Actor {

  type EventID = UUID

  private var subscriptions =
    mutable.Map.empty[EventID, Set[ActorRef]].withDefault(_ => Set())

  override def receive = {
    /** we can use the sender ref to add the requesting actor to the set of subscribers **/
    case SubscribeTo(topic) => subscriptions(topic) = subscriptions(topic) + sender()

    /** Now when the hub receives data, it can send a message to all subscribers
     * of that particular topic
     */
    case FreshData(incomingDataTopicID, data) =>
      subscriptions.find { case (topic, _) => topic == incomingDataTopicID } match {
        case Some((_, subscribers)) => subscribers foreach { _ ! EventData(data) }
        case None => println("This topic was not yet subscribed to")
      }
  }

}

现在我们有了上面的结构,你的websocket端点可能如下所示:

object WebsocketClient {

  /**
   * Some messages with which we can subscribe to a topic
   * These messages could be streamed through the websocket from the client
   */
  case class SubscribeTo(eventID: UUID)
  /** This is an example of some data we want to go back to the client. Uses int for simplicity **/
  case class EventData(data: Int)

  def props(out: ActorRef, telemetryHub: ActorRef) = Props(new WebsocketClient(out, telemetryHub))
}

/** Every client will own one of these actors. **/
class WebsocketClient(out: ActorRef, telemetryHub: ActorRef) extends Actor {
  def receive = {
    /** We can send these subscription request to a hub **/
    case subscriptionRequest: SubscribeTo => telemetryHub ! subscriptionRequest
    /** When we get data back, we can send it right to the client **/
    case EventData(data) => out ! data
  }
}

/** We can make a single hub which will be shared between all the connections **/
val telemetryHub = actorSys actorOf TelemetryHub.props

def events = WebSocket.accept[String, String] { _ =>
  ActorFlow.actorRef { out => {
    WebsocketClient.props(out, telemetryHub)
  }}
}

或者你可以使用Akka提供的内部事件总线来实现同样的目标而不那么麻烦!