WebSockets Echo Using Play, Scala, And Actors, Part II

The first part of this tutorial went into detail on Play’s support for WebSockets based on iteratees. You don’t need to use actors to handle WebSockets, but in a lot of real-world situations you’ll want to use actors because you’ll want to keep some longer-lived state associated with the connection.

Let’s start with a “naive” implementation of the code. This is a direct translation of our initial simpleIterateeWebSocket to use an actor:

  def naiveActorWebSocket = WebSocket.using[String] {
    val actor = Akka.system.actorOf(Props[NaiveEchoActor])
    val out = Enumerator.imperative[String]()
    actor ! NaiveStart(out)
    val in = Iteratee.foreach[String] {
      msg =>
        actor ! Message(msg)
    }
    (in, out)
  }
...
// Actor messages
case class NaiveStart(out: PushEnumerator[String])
case class Message(msg: String)

class NaiveEchoActor extends Actor {
  var out: PushEnumerator[String] = _
  
  override def receive = {
    case NaiveStart(out) => this.out = out
    case Message(msg) => this.out.push(msg)
  }
}

(You can test the examples in this tutorial by cloning the github repo. Use the echo test at websocket.org and point it to ws://localhost:9000/wsNaiveActor. Each example in this tutorial has its own URL that you can see in the routes file.)

In this implementation, we pass the out enumerator to the actor using the NaiveStart message. The NaiveStart message acts like an initializer for the actor. It takes the out enumerator and stores it in a member variable. The in iteratee now simply passes each message to the actor. The actor then handles that message by doing what the old iteratee did, pushing the message back to the client via the enumerator. Notice we haven’t used WebSocket.async yet: While the actor tell (!) invocation is asynchronous, the overall method is synchronous.

This code more or less works but it has one important bug: There’s no guarantee that the out member on NaiveEchoActor will actually be set before the first invocation of actor ! Message(msg). If it’s not set, this would result in an NPE. If you look at the WebSocket chat example that comes with Play, you’ll notice they take a slightly more sophisticated approach using the ask method on actor (an implicit requiring import akka.pattern.ask). We can fix our code by doing the same thing:

  import akka.pattern.ask
  import akka.util.duration._
  
  implicit val timeout = akka.util.Timeout(1 second)
  
  def actorWebSocket = WebSocket.async[String] {
    val actor = Akka.system.actorOf(Props[EchoActor])
    (actor ? Start()).asPromise map {
      case Connected(out) =>
        val in = Iteratee.foreach[String] {
          event => actor ! Message(event)
        }
        (in, out)
    }
  }
...
case class Start()
case class Connected(out: PushEnumerator[String])
class EchoActor extends Actor {
  var out: PushEnumerator[String] = _
  override def receive = {
    case Start() =>
      this.out = Enumerator.imperative[String]()
      sender ! Connected(out)
    case Message(msg) => this.out.push(msg)
  }
}

The ? operator is a synonym for ask. ask sends a reply back to the caller as a Future, which we immediately convert to a Promise. (This conversion will become unnecessary with Scala 2.10 and Play 2.1). Now we can wait for the Connected reply message before creating our iteratee. We also make the Start message behave more like a real constructor and let it create the enumerator for itself. The out enumerator then gets passed back in the Connected reply. The Promise.map method invoked on (echoActor ? Start()) is like an “onReply” event handler. This approach guarantees that the enumerator is set on the actor before the iteratee is created. Since ask is asynchronous it is wrapped in a Future/Promise, as is the result of map. Hence, we can use the WebSocket.async call and everything works as before except now we have actors in play!

One thought on “WebSockets Echo Using Play, Scala, And Actors, Part II

  1. Miguel

    I am new to scala and akka and I do not understand why do you say “There’s no guarantee that the out member on NaiveEchoActor will actually be set before the first invocation of actor ! Message(msg).”

    Is it because the “NaiveStart” may not be delivered to NaiveEchoActor as per the the rule “at most once” rule (http://doc.akka.io/docs/akka/snapshot/general/message-delivery-guarantees.html#message-ordering)
    The message coming from the same sender are guaranteed to be delivered in order, so a Start message so come always befure a “Message”.

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s