Web socket push notifications in Akka, Netty and Socko

Posted by Jan Machacek

Find me on:

10-May-2012 20:12:00

I was looking for a solution for web socket push in my Akka system. Ultimately, I wanted to be able to simply say here's some payload instance, push it out to all clients you are aware of. In other words, to have some clever WebSocketPushActor and to have it wired up to Netty.

More detailed discussion, including wiring up of regular HTTP APIs, is in the follow-up post.

case class Push[A](payload: A)

case class MyMessage()

class MyActor extends Actor {
  protected def receive = {
    case MyMessage() =>
      // do some business logic
      val payload = Notification("This is the text to show!")

      // spam every socket
      context.actorFor("/user/webSocketPush") ! Push(payload)
  }
}

Let's take a look at how we implemented the WebSocketPushActor; the handler for the Push message is clear; the interesting portion of the code is that this actor keeps track of all open web sockets:

case class WebSocketRegistered(channel: Channel)

class WebSocketPushActor extends Actor {
  private val socketConnections = new DefaultChannelGroup()

  protected def receive = {
    case Push(payload) =>
      socketConnections.write(new TextWebSocketFrame(payload.toString()))
    case WebSocketRegistered(channel) =>
      socketConnections.add(channel)
  }
}

The main application simply boots the ActorSystem and starts the two actors:

object Main extends App {
  val actorSystem = ActorSystem("example")

  val myActor = actorSystem.actorOf(Props[MyActor])
  val webSocket = actorSystem.actorOf(Props[WebSocketPushActor],
                                      "webSocketPush")
}

What is missing, of course is a web server. We shall be lazy and use something that already exists: Netty and a nice Scala wrapper for it called Socko. Modifying the Main singleton gives:

object Main extends App {
  val actorSystem = ActorSystem("example")

  val myActor = actorSystem.actorOf(Props[MyActor])
  val webSocket = actorSystem.actorOf(Props[WebSocketPushActor],
                                      "webSocketPush")

  val webServer = new WebServer(WebServerConfig(), Routes {
    case WebSocketHandshake(wsHandshake) => wsHandshake match {
      case PathSegments("websocket" :: Nil) => {
        wsHandshake.isAllowed = true
        actorSystem.actorFor("/user/webSocketPush") !
          WebSocketRegistered(wsHandshake.channel)
      }
    }
    case WebSocketFrame(frame) =>
      throw new UnsupportedOperationException(
        "Eh? We're meant to be pushing only!")
  })

  webServer.start()
}

So, this completes the web socket server-side code. I can now write some fancy JavaScript, which creates the web socket connection and simply shows the received message in a box (I did say fancy JavaScript, right?)

<html>
<head>
    <title>WebSockets & Akka</title>
    <script type="text/javascript" src="jquery-1.7.2.js"></script>
</head>
<body>
<div id="responseText"></div>

</body>
<script type="text/javascript">
  var socket;

  if (!window.WebSocket) {
    window.WebSocket = window.MozWebSocket;
  }

  if (window.WebSocket) {
    socket = new WebSocket("ws://localhost:8888/websocket");
    socket.onmessage = function(event) {
      $("#responseText").html("Received " + event.data);
    };
    socket.onopen = function(event) {
      $("#responseText").html("Opened");
    };
    socket.onclose = function(event) {
      $("#responseText").html("Closed");
    };
  } else {
    alert("Your browser does not support Web Socket.");
  }
</script>
</html>

And, this is it! You can now run your Main application, which will start the actors MyActor and WebSocketPushActor; it will also start Netty and react on web socket connection by sending a "register" message to the WebSocketPushActor. That actor will, when it receives the Push message, send it to all the sockets that it knows about.

Yes, the application is trivial and it ignores lots and lots of useful code: for example, how to add Spray routes to the fray, how to deal with marshalling of the payload. For the answers to that, you'll have to wait a few days for another post. In the meantime, head over to https://github.com/mashupbots/socko and try the code I've shown you here.

And finally, in production, you will want to separate out the responsibilities for keeping track of the open sockets, sending and possibly the marshalling amongst separate actors!

 

Topics: Akka

Subscribe to Email Updates