Picture this:
A moviegoer is trying to purchase movie tickets online. He or she has selected the seats, entered the payment details, and submitted. He or she gets an error message saying that the tickets they tried to book have sold out.
Consider an application, which gives detailed information about the stock market and allows purchasing/selling stocks. When someone enters payment details and submits these details, they get an error saying that the purchase has been rejected as the price of the stock has now changed.
Initially, in applications where real-time data was required over HTTP, developers realized that they needed bidirectional communication between the client side and server side. It was generally implemented using one of the following approaches:
WebSockets cannot be defined using Action since they should be bidirectional. Play provides a helper to assist with WebSockets, which is documented at https://www.playframework.com/documentation/2.3.x/api/scala/index.html#play.api.mvc.WebSocket$.
WebSockets can be defined similarly to Actions in Play applications. Starting from Play 2.3, a WebSocket helper finds a method to define WebSocket interactions using an Actor. However, before we learn more about the methods provided by the helper, let's take a small detour and get a little familiar with the Actor Model and Akka Actors.
Concurrency in programming can be achieved by using Threads which may include the risk of a lost update or a deadlock. The Actor Model facilitates concurrency by utilizing asynchronous communication.
According to the Actor Model, an actor is the fundamental unit of computation. It cannot exist independently, that is, it is always part of a specific actor system. An actor can send messages to one or more actors within its actor system if it knows the address of the other actor. It can also send messages to itself. The order in which the messages are sent or received cannot be guaranteed since the communication is asynchronous.
When an actor receives a message, it can do the following:
Forward it to another actor whose address is known to it
Create more actors
Designate the action it will take for the next message
Akka is a part of the Typesafe Reactive Platform, which is similar to the Play Framework. According to their website:
Akka is a toolkit and runtime used to build highly concurrent, distributed, and fault-tolerant event-driven applications on the JVM.
Akka implements a version of the Actor Model, which is commonly called Akka Actors and is available for both Java and Scala. According to the Akka documentation, Actors give you:
Simple and high-level abstractions for concurrency and parallelism
Asynchronous, nonblocking, and highly performant event-driven programming model
Very lightweight event-driven processes (several million actors per GB of heap memory)
Akka Actors are available as a library and can be used within a project by adding them into the dependencies:
libraryDependencies ++= Seq( "com.typesafe.akka" %% "akka-actor" % "2.3.4" )
Adding a dependency in Akka explicitly is not required in a Play project as Play uses Akka internally.
We can then define an actor...
Let's define a WebSocket connection, which accepts strings and sends back the reverse of a string using Iteratee:
def websocketBroadcast = WebSocket.using[String] { request => val (out, channel) = Concurrent.broadcast[String] val in = Iteratee.foreach[String] { word => channel.push(word.reverse) } (in, out) }
The WebSocket.using
method creates a WebSocket of a specific type using an Iteratee (inbound channel) and its corresponding enumerator (outbound channel). In the preceding code snippet, we return a tuple of the Iteratee in and the Enumerator out.
The Concurrent
object is also a helper, which provides utilities to use Iteratees, Enumerators, and Enumeratees concurrently. The broadcast[E]
method creates an Enumerator and a channel and returns a (Enumerator[E], Channel[E])
tuple. The Enumerator and channel, thus obtained, can be used to broadcast data to multiple Iteratees.
After this, we need to bind it to a path in the...
The Play WebSocket API allows the use of Actors to define the behavior. Let's build the WebSocket application that replies with the reverse of a given String once it's connected. We can do this by slightly modifying our Reverser Actor to have an argument as the reference of the Actor to which it can/must send messages, as shown here:
class Reverser(outChannel: ActorRef) extends Actor { def receive = { case s: String => outChannel ! s.reverse } } object Reverser { def props(outChannel: ActorRef) = Props(classOf[Reverser], outChannel) }
The websocket
can then be defined in a controller as follows:
def websocket = WebSocket.acceptWithActor[String, String] { request => out => Reverser.props(out) }
Finally, we make an entry in the routes file:
GET /wsActor controllers.Application.websocket
We can now send messages through the WebSocket when the application is running using a browser plugin.
Now, lets...
When the WebSocket is closed, Play automatically stops the actor bound to it. This binding works in two ways: the WebSocket connection is closed when the underlying actor is killed. If there is a need to free any resources once the connection is closed, we can do so by overriding the actor's postStop
method. In our example, we have initialized a DBActor within WebSocketChannel
. We will need to ensure that it's killed once the WebSocket is closed, since each connection to the WebSocket will lead to the initialization of a DBActor. We can do so by sending it a poison pill, as shown here:
override def postStop() = { backend ! PoisonPill }
Suppose that an incoming JSON has the same fields for every request, instead of parsing it every time; we can define an equivalent class in this way:
case class WebsocketRequest(reqType:String, message:String)
Now, we can define our WebSocket to translate the JSON message to a WebSocketRequest
automatically. This is possible...
What is the equivalent of interrupting Actions
in GlobalSettings
for WebSockets
? What if we want to refuse a WebSocket connection when certain headers are missing? Something similar to the following code snippet didn't work as expected:
override def onRouteRequest(request: RequestHeader): Option[Handler] = { if(request.path.startsWith("/ws")){ Option(controllers.Default.error) } else super.onRouteRequest(request) }
Interrupting WebSocket from the global object does not work as it does for Actions. However, there are other means of doing so: by using the tryAccept
and tryAcceptWithActor
methods. A WebSocket definition can be replaced by the following code:
def wsWithHeader = WebSocket.tryAccept[String] { rh => Future.successful(rh.headers.get("token") match { case Some(x) => var channel: Concurrent.Channel[String] = null val out = Concurrent.unicast[String] { ch => channel = ch ...
We have learned a couple of things in this chapter. This chapter briefly covered the Actor Model and usage of Akka Actors in an application. In addition to this, we defined a WebSocket connection in a Play application with various constraints and requirements using two different approaches: the first one where we use Iteratees and Enumerators, and the second where we use Akka Actors.
In the next chapter, we will see the different ways in which we can test a Play application using Specs2 and ScalaTest.