Using WebSockets on Heroku with Java and the Play Framework
Last updated February 09, 2022
This tutorial demonstrates how to build a Java and Play Framework application that uses a WebSocket, deployed to Heroku.
Sample code for the demo application is available on GitHub. Edits and enhancements are welcome. Just fork the repository, make your changes and send us a pull request.
Prerequisites
- Java, Play Framework 2.x, and the Heroku CLI (as described in the Heroku CLI setup article)
- A Heroku user account. Signup is free and instant.
Create WebSocket app
The sample application provides a simple example of using a WebSocket with Java and Play. You can clone the sample and follow along with the code as you read. If you’d rather write the app yourself you can add the sample code to a new Play app as you go.
Option 1. Clone the sample app
If you want to get going more quickly you can just clone the sample app:
$ git clone git@github.com:heroku-examples/play-websockets-sample.git
Cloning into 'play-websockets-sample'...
remote: Counting objects: 31, done.
remote: Compressing objects: 100% (24/24), done.
remote: Total 31 (delta 0), reused 31 (delta 0)
Receiving objects: 100% (31/31), 38.33 KiB | 0 bytes/s, done.
Checking connectivity... done
Option 2. Create a new Play app
$ play new
_
_ __ | | __ _ _ _
| '_ \| |/ _' | || |
| __/|_|\____|\__ /
|_| |__/
play 2.2.0 built with Scala 2.10.2 (running Java 1.6.0_51), http://www.playframework.com
The new application will be created in /Users/jsimone/dev/supportApps/play22test
...
Choose an application name and Java as the language.
Functionality
The sample application renders a simple web page that will open a WebSocket to the backend. The server will send a payload containing the time over the WebSocket once a second. That time will be displayed on the page.
There are 3 important pieces to the interaction that takes place here: a controller method that returns a WebSocket object, a JavaScript method that opens that WebSocket, and an Akka actor that sends the payload across that WebSocket every second. Let’s explore each.
Controller
You can return a WebSocket from a Play controller method.
There is an example in /app/controllers/Application.java
in the sample application:
public static WebSocket<String> pingWs() {
return new WebSocket<String>() {
public void onReady(WebSocket.In<String> in, WebSocket.Out<String> out) {
final ActorRef pingActor = Akka.system().actorOf(Props.create(Pinger.class, in, out));
final Cancellable cancellable = Akka.system().scheduler().schedule(Duration.create(1, SECONDS),
Duration.create(1, SECONDS),
pingActor,
"Tick",
Akka.system().dispatcher(),
null
);
in.onClose(new Callback0() {
@Override
public void invoke() throws Throwable {
cancellable.cancel();
}
});
}
};
}
public static Result pingJs() {
return ok(views.js.ping.render());
}
public static Result index() {
return ok(index.render());
}
This method returns a new WebSocket object that has a String as its payload. In the WebSocket object we define the onReady()
method to talk to an actor via the Akka scheduler. The work of sending data over the socket will occur in that actor. When the WebSocket is closed the callback registered on the input stream will be called.
The other methods will render our js
and html
templates.
We’ll also need a route to be set up for these methods in our routes
file:
# Home page
GET / controllers.Application.index()
GET /pingWs controllers.Application.pingWs()
GET /assets/javascripts/ping.js controllers.Application.pingJs()
Model
In the controller example you’ll notice that we pass around the in
and out
streams of the WebSocket. In our actor we’re able to read from and write to these streams just like any other IO stream. Here’s the code for the Pinger
actor (/app/models/Pinger.java
):
package models;
public class Pinger extends UntypedActor {
WebSocket.In<String> in;
WebSocket.Out<String> out;
public Pinger(WebSocket.In<String> in, WebSocket.Out<String> out) {
this.in = in;
this.out = out;
}
@Override
public void onReceive(Object message) {
if (message.equals("Tick")) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Calendar cal = Calendar.getInstance();
out.write(sdf.format(cal.getTime()));
} else {
unhandled(message);
}
}
}
You’ll notice that this actor counts on the schedule defined in the controller method to send it a “Tick” message every second. When that happens it sends the current date and time over the WebSocket.
Views
The final piece is the client code that will call the WebSocket. For this our sample application uses Scala templates to render JavaScript and HTML called /app/views/ping.scala.js
and /app/views/index.scala.html
respectively.
index.scala.html
provides a div
to display the data in and references the JavaScript:
@main("Welcome to Play") {
<strong>Stats</strong><br>
<div id="ping"></div>
<script type="text/javascript" charset="utf-8" src="@routes.Application.pingJs()"></script>
}
ping.scala.js
connects to the WebSocket and defines the receiveEvent
method to populate the dates into the displayed div
as they come across:
$(function() {
var WS = window['MozWebSocket'] ? MozWebSocket : WebSocket
var dateSocket = new WS("@routes.Application.pingWs().webSocketURL(request)")
var receiveEvent = function(event) {
$("#ping").html("Last ping: "+event.data);
}
dateSocket.onmessage = receiveEvent
})
Deploy
It’s time to deploy your app to Heroku. If you haven’t done so already put your application into a git repository:
$ git init
$ git add .
$ git commit -m "Ready to deploy"
Create the Heroku app to deploy to:
$ heroku create
Creating high-lightning-129... done, stack is heroku-18
http://high-lightning-129.herokuapp.com/ | git@heroku.com:high-lightning-129.git
Git remote heroku added
Deploy your code with git push
.
$ git push heroku master
Counting objects: 31, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (24/24), done.
Writing objects: 100% (31/31), 38.33 KiB | 0 bytes/s, done.
Total 31 (delta 0), reused 0 (delta 0)
-----> Play 2.x - Java app detected
-----> Installing OpenJDK 1.6...done
-----> Building app with sbt
-----> Running: sbt clean compile stage
Getting org.scala-sbt sbt 0.13.0 ...
downloading http://s3pository.heroku.com/ivy-typesafe-releases/org.scala-sbt/sbt/0.13.0/jars/sbt.jar ...
[SUCCESSFUL ] org.scala-sbt#sbt;0.13.0!sbt.jar (416ms)
...
[info] Done packaging.
[success] Total time: 7 s, completed Sep 29, 2013 7:51:08 PM
-----> Dropping ivy cache from the slug
-----> Discovering process types
Procfile declares types -> web
-----> Compiled slug size: 117.3MB
-----> Launching... done, v6
http://still-hamlet-4310.herokuapp.com deployed to Heroku
Congratulations! Your web app should now be up and running on Heroku. Visit the application to see it in action:
$ heroku open
Scaling
This app demonstrates simple usage of a WebSocket. For production use, and any app that requires more than a single web
dyno, please read about building a scalable app with WebSockets.
References
- The WebSocket documentation