Websockets in Play
Join the DZone community and get the full member experience.
Join For FreeIt has been nearly 4 months since I released the first beta of the Play Framework eBook (http://www.the-play-book.co.uk), quickly followed by the final release accompanied with the paperback edition. Comments and feedback have been fantastic, so thank you all!
I released the book fully updated to the 1.1 version of Play, and now 4 months later, Play 1.2 has been released with a host of new features. As a quick snapshot these features include
- Dependency Management – using Apache Ivy
- Evolutions – for tracking and organising changes to your database schemas
- H2 in memory database – replaces the older HSQLDB, and comes with a web console accessible using the URL /@db
- Updates to the TestRunner
- Plus many bug fixes and small enhancements to the Play codebase
All of these features sound absolutely great, but there is one set of features that I have been really looking forward to, and that is WebSockets!
Using WebSockets
So, why so exciting? Well quite simply put, a few years ago, in my spare
time I used to write Java Swing based multi-player turn based games.
Usually they were clones of popular board games, but they had a real
problem! No-one really enjoyed downloading and installing a game. They
wanted it on-demand, in a browser, easily accessible. But Web
programming did not really lend itself well to this type of world. Play
made it very easy to come close using AJAX and its long-polling method
(discussed in Chapter 12) of suspending the HTTP request until there is
an update, but for me I was eager for Websockets.
I finally got round to playing with Websockets this weekend and I must say, I am impressed. The Play Dev guys have done a great job with this. The documentation is not fantastically verbose, so this post is an attempt to make life a little easier for anyone wanting to play with Websockets using my favourite framework!
What do you need
Quite simply, you need Play 1.2 and a web browser that support
websockets. So, switch off IE (even if you have IE9!), and use Chrome.
If you have Firefox or Opera, the websocket protocol has been disabled
due to some security issues, which is disappointing, but these will be
cleared up at some point, so for now I will keep learning and playing
with Websockets as they will be a great competitive advantage later.
Let’s try it out
Unlike the Long Polling method, which would check the database for
changes to the model at predefined intervals, the idea of a websocket is
to be always open, waiting for an event, and then broadcasting that
event to everyone who needs to know. Therefore, the way to achieve this
in Play is to have a Stateful object residing on the server side. I know
this breaks a lot of the stateless, RESTful ideals of Play, but it is
the only way to make this work. So, lets start with out Stateful Model.
Create a new application called websocket.
play new websocket
Now, create a new file called StatefulModel.java in the app/models directory and add the following code.
package models;
import play.libs.F;
public class StatefulModel {
public static StatefulModel instance = new StatefulModel();
public final F.EventStream event = new F.EventStream();
private StatefulModel() { }
}
So, a very simple class. It is has a private constructor and a single static instance variable to enforce the Singleton pattern, meaning only a single instance of this model can exist on the server. The only other attribute is an EventStream object. This is an important piece. The EventStream is the core of the WebSockets implementation in Play. It allows events to be published, which notifies any waiting listeners that an event has been published. In this example, we are using a standard EventStream, which just gives access to the current event. Play also provides an ArchivedEventStream (check out the chat sample application to see it in action), which gives access to all the archived messages as well. To explain this EventStream concept in more detail, lets take a look at the Controller.
Open the Application.java in the app/controllers directory, and add the following code.
package controllers;
import play.mvc.*;
import models.*;
public class Application extends Controller {
public static void index() {
render();
}
public static class WebSocket extends WebSocketController {
public static void listen() {
while(inbound.isOpen()) {
String event = await(StatefulModel.instance.event.nextEvent());
outbound.send(event);
}
}
}
}
So, not a lot going on here either. The index action will simply render the index page, and then we have a listen action inside a WebSocket static class. The WebSocket static class implements a new type of controller introduced in Play 1.2 called WebSocketController. The difference between a WebSocketController and a standard Controller is that it does away with the request/response model, and in its place has an inbound and outbound model. Here, the code simply loops while the inbound is open (which means the browser is still open, and the web socket is still connected). We then wait for a new event from our StatefulModel’s EventStream, by calling nextEvent.
The await function replaces the suspend function in play1.1. The
await function will suspend the http request, freeing up the play
framework to continue to process requests, until a new event is added to
our Stateful Model, at which point the code will continue processing
from the point where it left off. This is another important change in
Play1.2. It does not call the method from the beginning again, it
carries on where it left off, making the code more readable.
Once the code continues it sends the data from the event to the outbound, which sends the event back to the web browser.
So, how does the browser deal with it? Well, it is again really simple.
Open views/application/index.html and add the following code.
#{extends 'main.html' /}
#{set title:'Home' /}
<div id="socketout"></div>
<script type="text/javascript">
// Create a socket
var socket = new WebSocket('@@{Application.WebSocket.listen}')
// Message received on the socket
socket.onmessage = function(event) {
$('#socketout').append(event.data+"<br />");
}
</script>
So, all we have here is a DIV tag where we will output our Web Socket events, and a bit of javascript that, a) creates the websocket connection, and b) deals with the on message event by appending the data to the DIV.
One final piece before we start to add events to our websocket, is the routes file. Unlike the normal Catch-All request, this does not work, as the catch all works on Controller.action name, and we have also a static class to contend with as well. Therefore, for our routing to work, we also need to add a route. Open the routes file and add the following route next to the homepage.
WS /socket Application.WebSocket.listen
Note, the WS as the HTTP Request type, to describe a WebSocket request. The rest of the route is accessed as you would expect any other route to be configured.
That is our websocket implementation up and running. The final part however is to start adding some events. If we started our application now, we would just see a blank page. Our websocket would be open, but there would be no data coming back to us from the server. So, lets create an asynchronous job to start putting some messages on the EventStream.
Create a new directory called app/jobs, and then create a file called Startup.java. Add the following code.
package job; import play.jobs.*; import models.StatefulModel; @OnApplicationStart(async = true) public class Startup extends Job { public void doJob() throws InterruptedException { int i = 0; while (true) { i++; Thread.sleep(1000); StatefulModel.instance.event.publish("On step " + i); } } }
This is a simple startup job, that has been set to run asynchronously. It simply loops forever (or until the server is stopped) and on each iteration, it sleeps for 1 second, and then publishes an event to our StatefulModel's EventStream with a String saying "On Step " followed by the count. This will continue until the server is stopped.
To get this code to execute, we needed to specify the @OnApplicationStart was to run in async mode. Without this setting, the Bootstrap job would be configured so that it MUST complete before the first request can be executed. This makes sense, as if we are using the Bootstrap job to read runtime settings for our application, we would not want it to start until it had done all of its work. In this case however, we just want it to run in parallel.
If you now start the server with
play run websocket
Now point your browser at localhost:9000, you should see your browser start counting up from 1. But what if you open another web browser? You will see that it will start counting in time with the other browser. The event is effectively broadcasted to all listening browsers. Obviously this is by design and you could tailor the way in which you build your application, but the concept is a powerful one.
And before I sign off, another big thank you to the Play developers. This request for Websockets came directly from the very active Play community, and the Play Dev guys implemented superbly whilst staying true to the framework. A fine piece of software engineering.
From http://playframework.wordpress.com/2011/04/25/websockets-in-play/
Opinions expressed by DZone contributors are their own.
Comments