简体   繁体   中英

Play Framework WebSocket Actor Filtering

I'm currently considering an implementation with which I can stream some events from my Play framework web application. There is a set of IoT devices that emit events and alerts. These devices are identified by their id. I have a HTTP endpoint with which I can get the telemetry signals for these devices. Now I want to do the same thing for the alerts and events. So I started with this simple approach by first defining my end point in my controller like this:

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

My 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))
}

Right now, I'm not doing much with my EventsActor, but later on this Actor is going to get Events and Alert messages being pushed into which, this will then be routed to the WebSocket endpoint.

Now me requirement is that in the WebSocket endpoint, when the client makes a connection, he should be able to specify an id for the IoT device that he wishes to connect to and I should be able to pass this id to the EventsActor where I can filter for events that contain the passed in id.

Any clues on how to do this?

I did a quick example of how you might go about this. It leaves much to be desired, but I hope it is of some inspiration to you!

What you effectively want is a coordinator/router which can keep track of which websocket actors are listening to which event types. You can inject that hub into all of the created actors, and dispatch events to it to register those websocket actors to sets of events.

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")
      }
  }

}

Now that we have the above structure, your websocket endpoint could look like the following:

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)
  }}
}

Alternatively you could use the internal event bus that Akka provides to achieve the same thing with much less hassle!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM