Real time (advanced)

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

Adding WebSocket

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:

Adding WebSocket

And the following screenshot shows the resulting routes file:

Adding WebSocket

With the noise gone, we can now look at the signature of our new action, chatStream, that takes two parameters:

  • chatIds: The chat instances' ID that the user has selected for his/her dashboard
  • timestamp: The time at which the client will start listening for incoming events

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:

Adding WebSocket

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.

Note

I recommend you to think how we could have done this using splat parameters in the routing definition rather than a simple string that we split explicitly in the action.

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.

Receiving messages

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.

Receiving messages

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.

Multiplexing events to the browser

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.

Note

This is not yet the most efficient way to do it, but I tried to KISS. For those interested in a better one, a tip is to use messages and actors when items or images are persisted.

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:

Multiplexing events to the browser

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.

Note

It would have been overkill to talk about the plugin mechanism that Play! 2 is providing to extend its server capabilities. However, the documentation is evolving on this topic.

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.

Note

This method could induce the loss of some events due to some latencies, for instance. But it'll do the job for now.

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:

Multiplexing events to the browser

Nothing more to say...

Live multichatting

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:

Live multichatting

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:

  • It stores the list in the dedicated property of Dashboard named @chatIds.
  • It uses 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.
  • On this reverse JavaScript action, we can use the function 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.
  • The created 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.

Note

We used a static reference to the rooms, which is not a good practice; but again, it'll be worth considering something like require.js to deal with such use cases.

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.

Live multichatting

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.

Note

Note that this is with a delay of a maximum of 1 second due to the checking period. But, as mentioned earlier, we could have used advanced techniques of Akka and gotten rid of this small latency pretty easily. Unfortunately, it would be beyond the scope of this book.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset