Lightbend Activator

SignalJ Chat Demo in Scala

Activator will be EOL-ed on May 24, 2017.

We’re making it easier and simpler for developers to get started with Lightbend technologies. This unfortunately means that future releases of Play, Akka and Scala will no longer include Activator support, and Lightbend’s Activator server will be decommissioned by the end of 2017. Instead of supporting Activator to create and set up development projects, we'll be supporting standard Giter8 templates for sbt users and Maven archetypes for Maven users. So going forward,

To create new Lightbend projects

Instead of using the Activator command, make sure you have sbt 0.13.13 (or higher), and use the “sbt new” command, providing the name of the template. For example, “$ sbt new akka/hello-akka.g8”. You can find a list of templates here.

Also, as a convenience, the Lightbend Project Starter allows you to quickly create a variety of example projects that you just unzip and run.

To create new templates

If you want to create new templates, you can now do that in Giter8.

To migrate templates from Activator to Giter8

If you created Activator templates in the past, please consider migrating them to Giter8 with this simple process.

SignalJ Chat Demo in Scala

chanan
Source
December 2, 2014
playframework signalj scala websocket sse ajax-longpolling jquery chat

A tutorial of how to use SignalJ's features.

How to get "SignalJ Chat Demo in Scala" on your computer

There are several ways to get this template.

Option 1: Choose signalj-chat-scala in the Lightbend Activator UI.

Already have Lightbend Activator (get it here)? Launch the UI then search for signalj-chat-scala in the list of templates.

Option 2: Download the signalj-chat-scala project as a zip archive

If you haven't installed Activator, you can get the code by downloading the template bundle for signalj-chat-scala.

  1. Download the Template Bundle for "SignalJ Chat Demo in Scala"
  2. Extract the downloaded zip file to your system
  3. The bundle includes a small bootstrap script that can start Activator. To start Lightbend Activator's UI:

    In your File Explorer, navigate into the directory that the template was extracted to, right-click on the file named "activator.bat", then select "Open", and if prompted with a warning, click to continue:

    Or from a command line:

     C:\Users\typesafe\signalj-chat-scala> activator ui 
    This will start Lightbend Activator and open this template in your browser.

Option 3: Create a signalj-chat-scala project from the command line

If you have Lightbend Activator, use its command line mode to create a new project from this template. Type activator new PROJECTNAME signalj-chat-scala on the command line.

Option 4: View the template source

The creator of this template maintains it at https://github.com/chanan/signalj-chat-scala#master.

Option 5: Preview the tutorial below

We've included the text of this template's tutorial below, but it may work better if you view it inside Activator on your computer. Activator tutorials are often designed to be interactive.

Preview the tutorial

SignalJ Overview

SignalJ is a port of ASP.Net SignalR to PlayFramework and Akka. SignalJ provides an abstraction for client-server and server-client message based communication. The client is usually a browser. In that case, the communication mechanism is chosen automatically based on the browser's capabilities: Websockets, ServerSentEvents, and Ajax Longpolling. For older versions of IE, SignalR provides ForeverFrames but that features has not been ported to SignalJ yet. Other clients for SignalR exist as well, such as: iOS, Andriod, and .net framework. This allows the same server code to be easily used by both browsers and native clients. In this tutorial we will be dealing with javascript/browser clients.

Another benefit of using SignalJ is that the communication is abstracted away from the developer. This allows for very easy and concise syntax both on the server and on the client. For example: to send execute client side code on all connected clients you would write this code on the server:


clients().all.myClientFunction(someParam)
        

and in your javascript you would handle it:


hub.client.userJoined = myClientFunction(param) {
    console.log("Param: " + param);
};
        

Lastly, SignalR (and therefore SignalJ) gracefully handles, client side connection problems. It can reconnect if disconnected. Handle slow connections, letting you handle an event to notify the user if you wish, and negotiate transports as mentioned above.

Although, SignalJ is not a chat server, the example most used is building one, so in the next sections of the tutorial we will see how we can build a chat server with a login screen and rooms.

Note: Some parts of the code are for demo purposes only. For example, the login method. Another example is saving state as a static variable in the Hub class. This is not thread safe. A better solution would be to call a database or to a "singleton" actor.

Server Side Setup

First, include the following lines in your build.sbt:


resolvers += "release repository" at "http://chanan.github.io/maven-repo/releases/"

resolvers += "snapshot repository" at "http://chanan.github.io/maven-repo/snapshots/"

libraryDependencies ++= Seq(
    jdbc,
    anorm,
    cache,
    ws,
    "signalJ" %% "signalj" % "0.5.0",
    "org.webjars" %% "webjars-play" % "2.3.0-2"
    "org.webjars" % "jquery" % "2.1.1"
)
        

Note: SignalJ is a JQuery plugin and requires JQuery to work. It can work with either JQuery 1.x to support older browsers, or with 2.x. This example shows including JQuery via Webjars, but you may include JQuery in your project via other means or by pointing to a CDN.

SignalJ is a play plugin therefore it needs to be registered in conf/play.plugins. Eitehr create or edit the file and add the line:


10000:signalJ.SignalJPlugin
        

Note: The number at the start of the line is the plugin load number. You can change that number if you have other plugins and want to load SignalJ before or after them.

Next you will need to add the SignalJ routes to your routes files:


->     /      signalJ.Routes
        

The next two step are optional. First, if you wish to use the ServerSentEvents or AJAX LongPolling transports you will need edit your Global object. If you plan to only support Websockets, this step is not needed. The following code should be placed in your Global object:


object Global extends GlobalSettings {
    override def onRouteRequest(request: RequestHeader): Option[Handler] = {
        val newRequest = TransportTransformer.transform(request)
        Option((Play.maybeApplication flatMap { app =>
            app.routes flatMap { router =>
                router.handlerFor(newRequest)
            }
        }).orNull)
    }
}
        

The second optional step is only required if want to load the SignalR's JQuery plugin (and therefore SignalJ's) from inside the the plugin itself. This options & benefits of different ways of loading the JQuery plugin are discussed in the next step of the tutorial (Client Side Setup). However, if you do chose to use this option then two changes need to be made. First, you will need to include sbt-web version 1.1 or above in your plugins.sbt:


addSbtPlugin("com.typesafe.sbt" % "sbt-web" % "1.1.1")
        

Second, you will need to add the following line to build.sbt:


WebKeys.directWebModules in Assets += "signalj"
        

Client Side Setup

Three script files are needed for SignalJ to work: JQuery, SignalR's JQuery plugin, and SignalJ's hubs javascript file.

First, include JQuery in your project. Use JQuery 1.x if you plan to support older browsers. In this example, I included JQuery 2 via Webjars. This can be found in /app/views/main.scala.html:

<script type='text/javascript' src='@routes.WebJarAssets.at(WebJarAssets.locate("jquery.min.js"))'></script>
        

Next you will need to include SignalR's JQuery plugin. There are 3 main options for this step:

  1. You can use a CDN to include either the un-minified version:
    <script src="//ajax.aspnetcdn.com/ajax/signalr/jquery.signalr-2.1.0.js"></script>
                
    or the minified version:
    <script src="//ajax.aspnetcdn.com/ajax/signalr/jquery.signalr-2.1.0.min.js"></script><
                
  2. You can download the plugins from the links above and save them in your project and then added them to your page
  3. Or, you can link to the /signalj/jquery.signalR.js route inside of the SignalJ plugin. The benefit of this option is that it will use the un-minified version of the file in development mode and the minified version in production mode. The draw back of this is that changes to plugins.sbt and build.sbt must be made as specified in the "Server Side Setup" step of this tutorial outlined

In this tutorial, I used the third option as can be seen in /app/views/main.scala.html:

<script src="/signalj/jquery.signalR.js"></script>

Lastly, you must include the auto-generated hub script:

<script type="text/javascript" charset="utf-8" src="/signalj/hubs.js"></script>
        

After those 3 script files are included, you application specifc script files would be included, in this example:

<script type="text/javascript" charset="utf-8" src="@routes.Assets.at("javascripts/chat.js")"></script>
        

Writing Your First Hub Class

Before looking in detail at the Chat server, lets learn some basics of using SignalJ

The hub classes are the classes that the client can invoke on the server. A hub class can be in any package, in this case I place mine in the hubs package. A hub class extends signalJ.services.Hub. This class is a generic type that requires an interface (Scala trait) as its parameter. The interface are the functions that should be present on the client and can be called from the server. In the Chat example you can see the client side functions declared in /apps/hubs/ChatPage:


trait ChatPage {
    def userJoined(username: String)
    def sendMessage(username: String, message: String)
    def userList(users: Array[String])
    def userLeftRoom(username: String)
    def userJoinedRoom(username: String)
    def roomList(rooms: Array[String])
}
        

Two things to note about the interface above. One is that there is no server side class the implements this interface. This interface is only used to declare what functions (messages) the browser will handle. Second, complex objects may be passed to the client as well. They will be serialized to Json. In this case, we see two methods have Array parameters. They will be transferred as Json arrays.

Now, we can create the hub class: /apps/hubs/ChatHub.scala. Only one method must be overridden as shown below:


class ChatHub extends Hub[ChatPage] {

    override def getInterface: Class[ChatPage] = classOf[ChatPage]
}
        

As you see, you must return the interface class in this method.

You are now ready to write server side functions that will be callable from the client.

Note: Future versions of SignalJ may allow Actors to be hubs as well.

Hub Methods

Any public method can be called from a client. The methods are called asynchronously inside of an actor. Each time a hub method is invoked, a new instance of the hub is created. The hub class can be supplied by your dependency injection framework. For more information on this check out the documentation at the SignalJ website.

Hub methods can be void or return a value. For example, a method that adds two numbers can be written in two ways that differ in the way they are handled on the client (more on that topic in the client section):


def add(a: int, b: int) {
    a + b
}
        

In this case the hub returns the the number directly to the caller (again, note the code does not block and is executed and returned via actor messages). To handle this call on the client, we can write the following javascript code:


hub.server.add(1, 2).done(function(result) {
    console.log('1 + 2 = ' + result);
});
        

When SignalJ gets a response from the server, the promise will complete and the done function will be called with the result.

The other way of writing similar code is by using a void method and calling a method on the client. While slightly more involved, this offers more flexibility as message could be sent to other clients as well. First add a method to your Hub's interface class such as public void addResult(int result);. Then define your hub method as follows:


def add(a: int, b: int) {
    clients().caller.addResult(a + b)
}
        

Now, we see the method is declared as void, and the clients() property of the hub was used with the caller (other options exist as we will see in later sections). The javascript to handle this case is slight different. This time, the result will be messaged to the client and will not return in a promise:


hub.server.add(1, 2);

hub.client.addResult = function(result) {
    console.log('1 + 2 = ' + result);
}
        

Hub Properties and Functions

Hub classes have several functions available to developers to use. We have seen some examples in the previous sections. Here is a more complete list:

  • clients().caller - executes a function on the client that called the hub
  • clients().all - executes a function on all clients connected to the hub
  • clients().others - executes a function on all clients except the caller
  • clients().client(connectionId) - executes a function on a specific connection
  • clients().AllExcept(connectionId1, connectionId2, ...) - executes a function on all clients except those listed
  • clients().Group(groupName) - executes a function on a specific group of clients (See group management below
  • clients().OthersInGroup(groupName) - executes a function to all others in a group

You can added or remove users from a group by calling the groups() method. There is no need to create first or destroy a group when it is empty. That is done automatically.

  • groups().add(connectionId, groupName) - adds a specific connection to a group
  • groups().remove(connectionId, groupName) - removes a specific connection from a group

On that note, lets see how we can get a client's connectionId:

  • context().connectionId - Retrieves the connectionId of the caller
  • context().queryString - Returns the request query string that was sent along with the connection

You can also pass state from the client to the server and back using callerState:

  • clients().callerState - returns the caller state Map[String, String]

Server Side Events

You can respond to client connects, disconnects or reconnects if you wish. for example:


override def onDisconnected {
    Logger.debug("Disconnect: " + context().connectionId)
}
        

The three events are:

  • onConnected() - Called when a client connects
  • onReconnected() - Called when a client reconnects
  • onDisconnected() - Called when a client disconnects

Two things to note:

  1. While, usually a disconnect method occurs, obviously in the case of server shutdown the method will not fire
  2. callerState will not be available in Server Side Events

Client Side - Connecting to a hub

To connect to a hub, you must first connect to a hub and then start the transport. For example:


var hub = $.connection.chatHub;
$.connection.hub.start().done(function () {
    console.log('Connected to hub');
});
        

First, in the code above, you can see that you need to make a connection to a specific hub. The name chatHub is the name of the hub class you created previously ChatHub. Note the case was change to match Json conventions. You can also assign a name to a hub using the HubName annotation.

Second, you start the hub. In this case, the hub is started in "auto transport" mode. That means it will first try to connect with Websockets, then ServerSentEvents and lastly AJAX Longpolling (A future version of SignalJ will also enable Forever Frames). You can also specify a specific transport in the start method, for example:


$.connection.hub.start().done(function ({ transport: 'serverSentEvents' }){
    console.log('Connected using ServerSentEvents!');
});
        

Note: To use transports other than Websockets, you must enable them in your Global. Please see the section on "Server Side Setup".

Lastly, a promise is redeemed when a connection is made. you can also install a fail function to catch the event of a connection not being able to be established.

Tip: You can turn on client side logging like so:


$.connection.hub.logging = true;
        

Client Side - Calling Server Methods

There are two basic basic functions to be developed on the client: server side calls and client side events. Lets look at server side method calls first. Below is an example of calling a function with expecting a return value and no parameters:


var hub = $.connection.someHub;
$.connection.hub.start().done(function() {
    hub.server.myMethod();
});

A server side method can also accept parameters:


var hub = $.connection.someHub;
$.connection.hub.start().done(function() {
    hub.server.myMethod(arg1, arg2, ..., argN);
});

A server side method can also return a value from a function as seen in the "Hub Methods" section:


var hub = $.connection.someHub;
$.connection.hub.start().done(function() {
    hub.server.myMethod().done(function(result) {
        //Do something with the result
    });
});

You can also catch server side failures like so:


var hub = $.connection.someHub;
$.connection.hub.start().done(function() {
    hub.server.myMethod().fail(function(error) {
        console.log(error);
    });
});

Client Side - Reacting to Server Side Messages

When one of the functions in the clients() is called on the server, it will fire a javascript function on the client. These are handled by assigning a function to to a named properties on hub.client.

For example, assuming an interface like the following:


trait MyPage {
    def method1()
    def method2(arg1: String)
    def method3(person: Person)
}

Using the example above, the hub class might call one of those functions like so:


clients().all.method3(new Person("John", "Smith))

So, the javascript could look like so:


var hub = $.connection.someHub;
hub.client.method1() = function() {
    console.log('method1 was called');
};

hub.client.method2 = function(arg) {
    console.log('method2 was called with the parameter: ' + arg);
);

hub.client.method3 = function(person) {
    console.log('method3 is called with this name: ' + person.firstName);
};

Any object that can be serialized to Json including lists and arrays, can be sent to the client.

The ChatPage Interface

Now that we have gone through a tour of SignalJ's features, lets go about building our chat application. First, lets create some method definition in the interface ChatPage


trait ChatPage {
    def userJoined(username: String)
    def sendMessage(username: String, message: String)
    def userList(users: Array[String])
    def userLeftRoom(username: String)
    def userJoinedRoom(username: String)
    def roomList(rooms: Array[String])
}

The Hub Class

First lets define the needed skeleton in /apps/hubs/ChatHub.scala:


class ChatHub extends Hub[ChatPage] {

    override def getInterface: Class[ChatPage] = classOf[ChatPage]
}
        

Lets create our first server side code, a (fake) login method:


1. def login(username: String): Boolean = {
2.    if (containsValue(connectionsToUsernames, username) || (username == "Robot")) return false
3.    clients.callerState.put("username", username)
4.    joinRoom("Lobby")
5.    clients.caller.roomList(getRoomList)
6.    if(!connectionsToUsernames.contains(context.connectionId))
        connectionsToUsernames = connectionsToUsernames + (context.connectionId -> clients.callerState.get("username"))
7.    return true
8. }

Much of the code in this method deals with the different chat rooms. Lets break it down by lines:

  1. public boolean login(String username) { - This (very fake) fake login method returns true if successful, false if the user already exists
  2. if (containsValue(connectionsToUsernames, username) || (username == "Robot")) return false - A static map mapping connectionIds to usernames is checked for duplicates. If a duplicate is found the method returns with a false
  3. clients.callerState.put("username", username) - Saving the username in the callerState map will make it available to subsequent calls
  4. joinRoom("Lobby") - This private method will join the user to the default room named "Lobby". Ultimately, this will call: groups().add(context().connectionId, room)
  5. clients().caller.roomList(getRoomList()) - This will call the originating browser only with the list of rooms
  6. connectionsToUsernames.putIfAbsent(context().connectionId, clients().callerState.get("username")) - This will update the map between connectionIds and usernames
  7. return true - If everything was successful, the server returns true to the client

On the client this method is called like so:


$('#loginSubmit').click(function(event) {
    event.preventDefault();
    hub.server.login($('#inputUsername').val()).done(function(result) {
        if(result) {
            $('#login').hide();
            $('#chat').show();
            $('#inputUsername').val('');
            $('#alert').hide();
        } else {
            $('#alert').show();
        }
    });
});
        

In the code above, we see that when the login method is code and is successful, the chat page is shown, if not an alert error is shown.

Chat - More examples

Lets look at how easy it is to actually send chat messages between the client -> server -> other in the room. In /public/javascripts/chat.js we send the room name and message to the server:


var sendMessage = function() {
    hub.server.sendMessage($('#roomselect').val(), $("#talk").val());
    ...
};
        

In /app/hubs/ChatHub we see that the message is received by the server and sent to everyone else in the room. We get the username we saved in callerState in the login method:


def sendMessage(room: String, message: String) {
    clients.othersInGroup(room).sendMessage(clients.callerState.get("username"), message)
}
        

Finally, back in /public/javascripts/chat.js we catch the sendMessage event:


hub.client.sendMessage = function(username, message) {
    var el = $('<div class="message"><span></span><p></p></div>');
    $("span", el).text(username + ': ');
    $("p", el).text(message);
    $(el).addClass("talk");
    $('#messages').append(el);
};

Calling into the hub from outside

Sometimes you may want to call clients connected to a hub from another class or manage its group. To do that you can get an instance of the hub from the GlobalHost. From the returned instance you can call the clients() or groups() methods but not context(). In the Chat tutorial we create a Robot that sends a message to all rooms every 30 seconds:


val hub: HubContext[ChatPage] = GlobalHost.getHub(classOf[ChatHub])
hub.clients.all.sendMessage("Robot", "I am alive!")

On the first line, we see how to get a reference to a hub class (HubContext[T]). In the next line we see reference to the HubContext can be used similarly to writing code inside the Hub.

Advanced Topics

You can learn about some other advanced topics at the SignalJ website. Some of these topics are:

  • Using your own dependency injection - Hub classes can be created via a IOC framework such as Guice or Spring
  • Configuration
  • Client side life time events

Future Enhancements

Future releases of SignalJ will continue to attempt to achieve feature parity with SignalR.

  • Forever Frames Transport - Mainly to support older versions of IE
  • Clustering - Clustering in SignalR is complex, however because SignalJ is based on Akka, SignalJ will use Akka's clustering.
  • Pluggable Authentication - The base for this work is included in 0.5.0 but will need some more rework to integrate with security projects such as SecureSocial or Deadbolt
×

Welcome to the Lightbend Enterprise Suite


You are excited about Reactive applications. And you want to build, manage and monitor them easily and reliably in your environment.
We get it. So we built Lightbend Enterprise Suite to help you do that, and more, with a range of powerful Application Management, Intelligent Monitoring, Enterprise Integration and Advanced Tooling features.