Make it work.
This blog will be about building practical stuff with Scala. I’m far from expert in any of those technologies. It’s more of a way for me to memorize something new I’m learning at the time. If you find it useful that will be great. Comments are always welcome.
Today we will build obligatory realtime chat application using Scala’s Play Framework, AngularJS and websockets starting from zero.
Roadmap:
Prerequisites:
After that is installed you can follow the steps listed in Play Framework guide.
Make sure the above tools are added to your $PATH.
Next step is actually much easier and consists of single console command:
$ activator new my-play-chat play-scala
It will create complete Play Application from play-scala template in my-play-chat directory that you can run using commands listed in console output or simply with SBT.
$ cd my-play-chat
$ activator run
--- (Running the application, auto-reloading is enabled) ---
[info] p.c.s.NettyServer - Listening for HTTP on /0:0:0:0:0:0:0:0:9000
(Server started, use Ctrl+D to stop and go back to the console...)
You can now navigate to http://localhost:9000 and see your running application.
Play uses more or less usual MVC workflow:
We will build chat UI and client functionality with AngularJS, which is a Javascript MVW Framework from Google. Interaction with the server will be managed through websockets, which is a more convenient realtime alternative to ajax calls.
We will not dive deep into AngularJS here, which is a fascinating topic in itself, just use as little of it as we can to make our UI implementation clean.
Let’s first add a reference to AngularJS in app/views/main.scala.html:
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.min.js">
</script>
Make sure it comes before the reference to javascripts/hello.js.
Now let’s describe our javascript data model and UI interactions in public/javascripts/hello.js file:
angular.module("ChatApp", []).controller("ChatController", function($scope){
// connect to websockets endpoint of our server
var ws = new WebSocket("ws://localhost:9000/socket");
// binding model for the UI
var chat = this;
chat.messages = [];
chat.currentMessage = "";
chat.username = "";
// what happens when user enters message
chat.sendMessage = function() {
var text = chat.username + ": " + chat.currentMessage;
chat.messages.push(text);
chat.currentMessage = "";
// send it to the server through websockets
ws.send(text);
};
// what to do when we receive message from the webserver
ws.onmessage = function(msg) {
chat.messages.push(msg.data);
$scope.$digest();
};
});
That’s it. That’s the whole client-side logic.
Now we need to do is bind it to the UI with the magic of Angular in app/views/index.scala.html:
@(message: String)
@main("Welcome to Play") {
<div ng-app="ChatApp">
<div ng-controller="ChatController as chat">
<form>
<input type="text" placeholder="Enter name..."
ng-model="chat.username">
<input type="text" placeholder="Enter message..."
ng-model="chat.currentMessage">
<input type="submit" value="Send"
ng-click="chat.sendMessage()">
</form>
<ul>
<li ng-repeat="msg in chat.messages track by $index">
{{msg}}
</li>
</ul>
</div>
</div>
}
ng-* attributes specify bindings to our angular controller specified in javascript above.
Everything should be more or less self-explanatory besides maybe a track by $index, which is just a workaround to allow duplicate elements in ng-repeat.
Server-side functionality will be based on akka actors in context of Play application.
Entities of our domain will be represented as simultaneously existing actors that communicate through message passing.
Actors are objects implementing akka.actor.Actor and they are never created or operated on directly, but only through sending messages to corresponding ActorRef wrappers. That is part of what enables location transparency in actors.
Let’s define Chat actor and corresponding message protocol in the app/models/Chat.scala file:
package models
import akka.actor._
// our domain message protocol
case object Join
case object Leave
final case class ClientSentMessage(text: String)
// Chat actor
class Chat extends Actor {
// initial message-handling behavior
def receive = process(Set.empty)
def process(subscribers: Set[ActorRef]): Receive = {
case Join =>
// replaces message-handling behavior by the new one
context become process(subscribers + sender)
case Leave =>
context become process(subscribers - sender)
case msg: ClientSentMessage =>
// send messages to all subscribers except sender
(subscribers - sender).foreach { _ ! msg }
}
}
According to wikipedia:
An actor is a computational entity that, in response to a message it receives, can concurrently:
- send a finite number of messages to other actors;
- create a finite number of new actors;
- designate the behavior to be used for the next message it receives.
Sending a finite number of messages to other actors is handled by ! method on ActorRef.
We will not be spawning new actors inside of other actors for this example.
The behavior to be used for the next message is described by receive method and context.become calls.
Play provides a functionality to represent WebSocket connection as an actor. Let’s describe a /socket endpoint in our controller that will serve that role.
conf/routes
GET / controllers.Application.index
GET /socket controllers.Application.socket
Now we will describe how to create an actor that handles websocket connection:
app/controllers/Application.scala
package controllers
import javax.inject._
import akka.actor._
import play.api._
import play.api.mvc._
import play.api.Play.current
import models._
// injects Play's default ActorSystem into our controller
@Singleton
class Application @Inject()(actorSystem: ActorSystem) extends Controller {
// creates actor of type chat described above
val chat = actorSystem.actorOf(Props[Chat], "chat")
/*
Specifies how to wrap an out-actor that will represent
WebSocket connection for a given request.
*/
def socket = WebSocket.acceptWithActor[String, String] {
(request: RequestHeader) =>
(out: ActorRef) =>
Props(new ClientActor(out, chat))
}
def index = Action {
Ok(views.html.index("Hello."))
}
}
Now for the last piece of a puzzle. Our ClientActor class that will represent communication between WebSocket actor and Chat “server”:
app/models/ClientActor.scala
package models
import akka.actor._
class ClientActor(out: ActorRef, chat: ActorRef) extends Actor {
chat ! Join
override def postStop() = chat ! Leave
def receive = {
// this handles messages from the websocket
case text: String =>
chat ! ClientSentMessage(text)
case ClientSentMessage(text) =>
out ! text
}
}
That’s it. The whole application is done.
You can refresh the page in your browser, open several of them and see how each receives the messages sent from the other.
The whole code is available on github.
Diff between Play template and final implementation can be seen here.
It’s less than 100 added lines of HTML+JS+Scala code combined (excluding comments).