In addition to the traditional pull model of getting HTML displayed in a browser when querying a service, most web browsers now support bidirectional communication via WebSockets so that servers can push data without the user having to query for it first. Once a socket is established between client and server, the communication can stay open for further interaction, unlike the HTTP protocol. Modern web apps are using this feature more and more to push data from streams reactively.
As a reminder, a WebSocket is a protocol providing bidirectional communication over a single TCP connection, in contrast to the traditional one-way, stateless communication of HTTP (either a request or a response). Let's look at the support that Play provides in this area and demonstrate in a short example how to establish a WebSocket communication between the Play server and a client browser.
As we have already created a
ch9samples
Play project at the beginning of this chapter to experiment with Iteratees
in the REPL, we can just reuse it. We will start by opening the tiny controllers/Application.scala
server-side class that is available by default. We can add a new connect
method to it to create a WebSocket interaction. In a regular Play controller, a method would normally use an Action
class, as we have seen previously. In this example, we use the WebSocket
class instead, illustrated in the controller as follows:
package controllers import play.api._ import play.api.mvc._ import play.api.libs.iteratee._ import scala.concurrent.ExecutionContext.Implicits.global object Application extends Controller { def index = Action { Ok(views.html.index("Your new application is ready.")) } def connect = WebSocket.using[String] { request => // Concurrent.broadcast returns (Enumerator, Concurrent.Channel) val (out,channel) = Concurrent.broadcast[String] // log message to stdout and send response back to client val in = Iteratee.foreach[String] { msg => println(msg) //the channel will push to the Enumerator channel push("RESPONSE: " + msg) } (in,out) } }
In the server-side controller seen in the preceding code, the in
variable contains the logic to handle messages coming from the client, and it will produce an Enumerator
to assemble some response data that will be pushed through the channel to each client.
On the client side, the views/main.scala.html
view is where we are going to add the WebSocket support, as a part of a JavaScript script, whose role is to open a web socket and react to incoming messages. as follows:
@(title: String)(content: Html) <!DOCTYPE html> <html> <head> <title>@title</title> <link rel="stylesheet" media="screen" href="@routes.Assets.at("stylesheets/main.css")"> <link rel="shortcut icon" type="image/png" href="@routes.Assets.at("images/favicon.png")"> <script src="@routes.Assets.at("javascripts/jquery-1.9.0.min.js")" type="text/javascript"></script> <script type="text/javascript"> function WebSocketTest() { if ("WebSocket" in window) { alert("WebSocket is supported by your Browser!"); // Let us open a web socket var ws = new WebSocket("ws://localhost:9000/connect"); ws.onopen = function() { // Web Socket is connected, send data var msg = "Hello Websocket!" ws.send(msg); alert("Message is sent..."+msg); }; ws.onmessage = function (evt) { var received_msg = evt.data; alert("Message is received..."+received_msg); }; ws.onclose = function() { // websocket is closed. alert("Connection is closed..."); }; } else { // The browser doesn't support WebSocket alert("WebSocket NOT supported by your Browser!"); } } </script> </head> <body> <div id="sse"> <a href="javascript:WebSocketTest()">Run WebSocket</a> </div> </body> </html>
Now that we have both ends, the only remaining step is to define a route for the controller's connect
method. Edit the conf/routes
file to make it look like the following:
# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~
# Home page
GET / controllers.Application.index
GET /connect controllers.Application.connect
# Map static resources from the /public folder to the /assets URL path
GET /assets/*file controllers.Assets.at(path="/public", file)
Now, we are ready to try the demo by starting the play server from the command prompt:
> play run
Opening a browser at http://localhost:9000/
(preferably one that supports WebSockets) and clicking on the Run WebSocket link should first confirm that the browser is indeed supporting WebSockets. Clicking on OK a couple of times will first show you that a message has been sent, and then show that the roundtrip has been achieved by receiving a message from the server. You should also see the Message to send
log message on the play server prompt.