Experimenting with WebSockets and Iteratees in Play

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.

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

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