WebSocket server using Play Framework and Akka Streams

. 6 min read

Without any doubt, Play Framework offers an intuitive approach for designing stateless and scalable web applications with asynchronous support. This is primarily enabled due to Akka Actors and Akka HTTP backing Play behind the scene. Play Framework also offers support for creating WebSocket servers for real-time communication needs using the same streaming power of Akka. This enables developers to create highly scalable, reactive, non-blocking and backpressure aware WebSocket and HTTP servers.

WebSockets offer bi-direction fully duplex communication over a persistent HTTP connection. What this means is that, rather than creating individual HTTP connections for each communication to and fro from the server, it relies on a single connection. With a persistent connection in place, subsequent communications have very little overhead and are relatively much faster. This makes it ideal for use cases where real-time communication is desired. WebSockets are not only for the client to server communication but are also used widely among mobile communication and real-time server to server communication.

Understanding design philosophies

Typical HTTP communication in Play framework is handled by the Action abstraction which provides an intuitive way to offer a one-off communication. The server receives a request and an action is invoked to respond back immediately or asynchronously. Here's a snippet.

Action { implicit request: Request[AnyContent] =>

What we are expressing above is to respond back with a status code 200 with the HTML content described in the index HTML view. This completes the cycle of HTTP connection and connection terminates. In some specific conditions, the connection can still exist due to connection-alive settings, but that has primarily to do with performance on the client side, while server behaves in the same stateless manner.

are completely different beasts, they maintain a persistent connection between the client and server. It is the responsibility of the server to expect more than one input request or a stream of input request on the same connection and respond in the same order. This is what Akka streams are good at and that is how Play Framework has supported it.

A quick look into Akka Streams

Akka Streams is one of key toolset in the Akka ecosystem that enables and powers a lot of the Lightbend reactive frameworks including Akka HTTP, Lagom and Play Framework. It provides amazing abstractions and toolkit to create streaming applications and define complex data processing pipelines. This is a natural extension of how computer systems operate like disk i/o, network i/o and even compute. Here's an example of an Akka Stream.

A very simple stream

In the above diagram, what we have is a stream of the data pipeline originating from a source and settling to a sink passing through a sequence of data operations(1, 2, 3) in between. Obviously, this is a very simple representation of a stream. In a real-world scenario, we might have complex data flow, junctions and complex aggregations and bifurcation scenarios that might look like a complex Graph. But I think, I made the point here, Akka streams provide a simple abstraction that allows us to process a stream of data in a nice intuitive manner. The good thing about Akka streams is that it is compliant with and is a founding member of Reactive Streams. which makes it non-blocking, highly performing, and with asynchronous support. Another cool thing about this is that it has an implicit support for back pressure, so if your sink is slow to consume the data, it will signal the source to slow down and you don't need to worry about handling that or buffering anything. For the purpose of this development, let's look at some keywords

Source: An event source that produces a stream of data of a particular type. A source only produces data whenever downstream data elements are ready to process the data.

Sink:  An event consumer that consumes a stream of data of a particular type. A sink optionally slows down the producer/source if it cannot process the data faster enough.

Flow: A data pipeline which has exactly one input and output, which connects its up- and downstream by transforming the data elements flowing through it.

Echo WebSocket server

With the understanding of the underlying design philosophies, let's jump into an implementation of a WebSocket server. As with any play framework application, we need a route that defines the controller action responsible for handling the request.

GET     /ws           controllers.ChatController.socket

In the above route definition, we declare to use the ChatController.socket to handle the route request for /ws.

Next, Play framework provides us abstractions to handle the persistent WebSocket connection. What it means is that the specific request/response handling required by the WebSocket connection including keeping the connection alive and persistent, any ping/pong responsible for making sure connection is alive etc. is handled by the play framework abstraction. However, we need to define the Akka streams flow to represent the stream of application specific logic we need to address. Here's an example of the play framework WebSocket API.

WebSocket.accept[String, String] { request =>
	// Define and return the Akka streams flow here

The WebSocket.accept handler creates the action handler for accepting the WebSocket connections. It accepts an Akka stream Flow which will accept String as input and provides String as output.

Defining the WebSocket flow for an Echo server

As described above, the WebSocket handler expects the Akka stream flow of type Flow[String, String, NotUsed], ignore the NotUsed type, for now, it is not significant in the current scenario. To make our lives easier here, let's use Actors with Akka Streams.

Play Framework again provides a nice abstraction for us to create an Akka stream flow that is handled by an actor. ActorFlow.actorRef creates a flow which expects you to create a handler actor. Any messages originating from the WebSocket incoming connection will be sent to this handler actor and can be processed as your application logic governs. The actor also receives another actor as an input, which can be used to send messages out as responses to the WebSocket connection. Let me explain with the following snippet.

The snippet above creates an Actor flow which expects new Actor props to instantiate an instance of the actor to handle the WebSocket connection. Play framework will create a new instance of this actor every time it receives a new WebSocket connection. In this case, it would be the ChatServiceActor.

The ChatServiceActor will receive each message sent to the WebSocket connection and can handle it appropriately. While instantiating the actor, it also receives a new ActorRef which it can use to send back any responses you might have. In our simple
"Echo Server" actor, it is as simple as sending the input message back.

One key thing about using the ActorFlow.actorRef API is that it uses Actor underneath for creating and handling the flow. While this is great and easy to use, it violates the back-pressure principle of Akka Streams and is not recommended to use in a setting where there are really high performance and reliability demand. Please check out the following section for more articles on the same.

Closing the WebSocket connection

With the support of ActorFlow.actorRef utility, play framework binds the lifecycle of the WebSocket connection with that of the defined Actor. This means that as the WebSocket is created, it will call the preStart of the underlying Actor and postStop while closing the connection. This also means that if you manually kill the underlying Actor, Play framework will close the WebSocket connection for you.

In the above snippet, we listen for any message with the text "Cancel" to close the connection manually by sending a PoisonPill message to the Actor.

Testing the WebSocket server

So for testing, I have used DWST, a chrome plugin for the WebSocket communication. I initiated the connection and then talked with it a couple of times, and the server responded pretty okay, actually dumb :). Anyways, the server closed the connection itself, when I said anything contains "Close", which is where the PoisonPill is sent to the actor, as described above. Obviously, you could have closed it yourself, which would have terminated the actor as well.

Simple WebSocket server demo

You can find the complete source code here. Checkout this article to see support of JSON payloads in WebSockets.