The Web has changed; HTML5 is almost there and is already implemented by all browsers. At least, the useful parts of it are available, especially the parts we'll use in this section.
It's now very familiar and it won't surprise you anymore, but Play! Framework 2 will again demonstrate that it is a real web framework by integrating things such as WebSocket or its old fallback, Comet .
Actually, Comet is not really a fallback for WebSocket since it's unidirectional while the latter is bidirectional. Nevertheless, there is another specification that does the same as Comet: Server-Sent Events (SSE). Even if an implementation of SSE is not (yet) provided by default, Play! 2's API will help us a lot in implementing it on our own really easily. This tool in hand, our application would have a really good push mechanism in place.
In this chapter, we'll focus on the most popular one, which is WebSocket. Hopefully, this is the one we'll need in our application to make it more responsive and reduce its consumption in bandwidth and resources (remember the endless loop to poll).
WebSocket is a duplex connection between a client and a server that enables bidirectional communication, like what we would love to have in our chatrum.
What we're going to do is enable our client side to listen to server messages in order to update the chatrooms that the user has configured in its dashboard. We'll continue in this great direction by re-using the connection in order to push the messages as well, in a standardized way, using asynchronous tasks, no loops, and without boilerplates!
Essentially, what will be done is the replacement of the actions talk
and contentSince
by a new single one that deals with WebSocket.
For this action, Play! 2 requires us to define an action that returns an instance of play.mvc.WebSocket<A>
. As you can see, it has a generic type, which is the class of the expected representation of the messages that are sent to the connection.
So, first of all, we remove the obsolete actions and create a new one called chatsStream
. And, of course, we can remove the route definition as well.
The following screenshot shows the resulting Chats
controller:
And the following screenshot shows the resulting routes
file:
With the noise gone, we can now look at the signature of our new action, chatStream
, that takes two parameters:
That was the parameter part, and if we look at the result type we'll see what we had expected—the WebSocket
type, with its generic type org.codehaus.jackson.JsonNode
.
As WebSocket is a connection wherein streams are involved to transfer bytes, commonly represented as strings, one would think that we'll have to slurp the messages' content and process them into JSON. But we won't, because Play! 2 knows that JSON will be used in 99 percent of use cases. So, everything will be done for us. However, they've also prepared the ground for simple strings and bytes.
So far so good; but before getting into the details of creating such an instance of WebSocket
dealing with JsonNode
, let's have a look at the preliminaries:
Our intent is to reduce the amount of traffic on the wire (graceful for mobile applications), so we'll have only one connection between the client and the server. This single connection will deal with all messages from the client (chatting) and a multiplexed wave for all chat updates.
That's why our action takes a String
parameter, which is the list of chat instances' IDs list we'll have to listen to for updates.
So we process this string to retrieve all IDs in a dedicated list, and we'll keep a reference to the connected user too. Then we start creating the real answer, which is the implementation of WebSocket
itself.
As we may have noticed, to create such an instance we'll need to implement a single method, that is, onReady
. This is the method that will be called when the connection will be set and the server will be able to deal with the client. As it's the time when communication takes place, onReady
accepts two streams as parameters:
in
: This parameter is an instance of WebSocket.In<A>
. It represents the messages' input stream, where the client is pushing messages that must be compliant with the generic type A
of WebSocket
(here JsonNode
).out
: This parameter is simply the other way around, with a dedicated class WebSocket.Out<A>
.Obviously, we return the inline implementation as the result of our action.
Having done that, we've already defined a connection between a client and this action; really, nothing more has to be done. The internals will manage the persistent connections with all connected users.
Thus, we can now move on to one of the two actions that this action might do, which are receiving a message (talk) or publishing events (update). So let's start first with the talk use case.
The use case is to take the incoming JSON-encoded messages and persist them as the Item
list of the Chat
instance. As our socket is unique for each client, the message should contain the information about the targeted chatroom.
Reacting to the incoming message is pretty easy, because of the WebSocket.In<A>
class that has a method onMessage
, which will be called whenever a message arrives. Given this semantic, it's fair enough to pass it an argument, which is a callback (a command)—so familiar when coming from the JavaScript world.
Such a callback is simply a Java workaround for a lambda function that will take in our case one parameter of type JsonNode
.
Back to our task now, we need a callback that retrieves the information about which chat is targeted and what the message is, right before adding it to the items
list of Chat
.
Actually, there is nothing really hard to understand here. It's essentially the previously created talk
action, but rather than having the targeted chat's ID available as a parameter of the action, we assumed that it's part of the JSON message itself.
Then we bind (as usual) our form to the current message; here again, we diverged a bit by calling bind
rather than bindFromRequest
, which is obvious because there is no request here! What's also interesting is the response sent back to the client, which is another JSON object that is created with the current status and then written on the out
stream. That's how messages are sent to the client. However, we're going to see a better example of such a push message.
We have reached the last server-side part of the bilateral communication for our chatrum. What we have to do now is provide the connected client information about which chat instances have been updated and what is updated.
There are several ways to accomplish this task; the one we'll choose here is probably the easiest and has the advantage of smoothly introducing the Akka library.
Akka is part of the Typesafe Stack 2 for everything related to distributed, parallel computing, and so on. It provides a fast and non-blocking API using what was initially an Erlang concept: the Actor Model that is making us rethink the way concurrent tasks might be done.
The killer features are, for instance, that our application's number of simultaneously connected users is no longer limited to the number of threads our server can handle. In short, J2EE is mainly based on servlets, where each request takes one thread in the pool and holds it until it terminates: this is called blocking.
Even if Akka provides a lot of features, we won't discuss them here (there are plenty of emerging books on it, which are worth considering reading conscientiously); however, we'll use one of them, that is, the asynchronous recurring task definition (a scheduler).
Indeed, we're going to check the updates through the usage of such an Akka scheduler, by asking it to check the database content periodically based on a timestamp.
Using the item and image's timestamp
field, the recurring task will be able to know whether they have to be sent or not. What we'll gain here over the previous implementation using contentSince
is that only events will be transferred over the wire when updates have occurred for all chatrooms.
The following screenshot shows how we can define a scheduled task with Akka, and how we can use it to send update events to the client:
In the previous screenshot, there are some key points that are worth discussing. So let's discuss them one by one.
The very first thing is the call to the system method of play.libs.Akka
. This Akka utility class is part of Play! Framework 2 and is hiding Akka's configuration part, which is handled through a Play! 2 plugin. This plugin helps us configure Akka through some properties in our application.conf
file. Then comes the Akka class that wraps some boilerplate for us and abstracts things such as retrieving an Akka's system.
In order to keep things simple, let's assume that such a system (ActorSystem
) is able to manage concurrent, asynchronous, or scheduled blocks.
So this actor system has a scheduler
accessor that itself enables us to schedule a Runnable. For that, we need to configure how this task has to be scheduled by giving it the information about the delay before the first execution, and the period between each execution—both as Duration
instances. In our example, we gave a delay of 0
milliseconds and a cadence of 1
second.
The last parameter is obviously the task to be performed, being an implementation of the traditional Runnable.
The second thing to notice is the timestamp being cached at each iteration in order to apply a valuable filter on items and images.
Then there is the send
flag, which is there to prevent us from flooding the client with empty messages. And what will be sent is simply a message holding the information about the updates that have been discovered.
How these events are discovered is part of the implementation of the checkChat
method (used in the loop). This method is rather straightforward, because it's pretty much the same as our previous implementation of the contentSince
action. That is to say, we retrieve a Chat
instance, loop over its items and images, and keep only the new ones. The only thing new is that it will only return a non-null object if at least one new event has been discovered. The following screenshot shows the implementation of the checkChat
method:
Nothing more to say...
Now that we're done with the server side, we must adapt our client-side code (CoffeeScript and JavaScript) to deal with our new chatStream
action instead of the old talk
and contentSince
ones.
As there will be only one location where the updates will be resolved, the best place to put this code is probably in the Dashboard
class (in dashboard.coffee
). So it will have the responsibility of checking all chat instances it is configured with; that's why it will now have to keep a reference to all of them.
Until now, the check and talk implementations were done in the Chatroom
class's methods fetchContent
and poll
—we can remove them both!
With the code being a bit more clean now, we can have a look at the Dashboard
part we're interested in:
The previous screenshot presents the implementation of Dashboard
cropped to what we're talking about.
First, we cover the introduction of a new property, chatIds
, which will be an array of numbers—the chat instances' IDs. They will be necessary when registering to our chatStream
action.
Still in the constructor, we have redefined the talk part by replacing the old form for AJAX submission with the creation of a message object, where we drop a property pointing to the target chat instance. Recall that the onMessage
method of WebSocket
in the action chatStream
expects such a property to get the instance back from the database. Then this message is sent over the wire using a new property of Dashboard
, @socket
, which we'll look at in a moment.
Let's jump to the method of Dashboard
named opened
, which takes the list of chat instances to be tracked and does the following tasks:
Dashboard
named @chatIds
.chatsRouter
, which we have already created earlier (containing the reverse JavaScript router). This time, we'll use its new action, chatsStream
, which takes the list of the chat instances' IDs as a string and the current timestamp as a number.webSocketURL
, which computes a specific URL to target our server-side action through a WebSocket (for instance, it uses the protocol ws://
). For that, we used the standard JavaScript WebSocket
constructor.WebSocket
object has several callbacks that might be configured; we're going to use the onmessage
one in order to handle the server-side events as JSON instances.These messages contain all the latest updates for all chatrooms being listened to, so we loop over it to update each of them. The update part is the responsibility of the related Chatroom
instance, which declared a method that accepts new items and images to be shown.
The only thing left to do is slightly adapting the way we were defining the instance of Dashboard
and those of Chatroom
in dashboard/index.scala.html
and chatroom.scala.html
, respectively.
What is done is pretty obvious: at the time the list of chat instances is known, we gave the IDs' list to the opened
method of the Dashboard
instance kept statically in a package of our own (chatrum
).
The other part is quite the same, as we only store the Chatroom
instances in another static reference (which is used in the opened
method of Dashboard
, though).
This closes the client-side part of the activation of real-time features to our application. We can now open several browsers and chat in several rooms in real time, with enhanced performance.