Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Creating a Scalable WebSocket Application in an Hour With Scala

DZone's Guide to

Creating a Scalable WebSocket Application in an Hour With Scala

Learn all about making a scalable WebSocket app with Scala, in just an hour!

Free Resource

Learn how to build modern digital experience apps with Crafter CMS. Download this eBook now. Brought to you in partnership with Crafter Software

In general, a web browser does not keep the HTTP connection open when it requests a resource from a server. Of course, there are several exceptions when the web browser keeps the connection open for a period of time such as long polling (which is kind of hacky way to do a push notifications), Pushlet, SSE, web browser plugins, and WebSocket. Some of them take advantage of HTTP keep-alive while some use different protocols.

WebSocket is a technology that comes with the HTML5 specification. It’s a full-duplex connection that means a web browser can send and receive data from a web server and vise versa over a single TCP connection. It sounds really good because we can do many cool things with it; however, this feature comes at a cost since the number of connections in a single server has a limit. When you want to scale it out, you have to figure out how to correlate the connections across a cluster.

Good news! You can implement a scalable WebSocket solution in less than an hour using Play! frameworkAkka, and Redis. The language that I’m going to demonstrate is Scala. You can use Java, but it’s going to be a bit harder because Rediscala plugin doesn’t have a Java version (actually, you can guess that from its name). This solution is surely not the best solution since it still has some scalability issues, and one of the issues is from Redis. You can find a better publisher/subscriber toolkit that is more scalable and robust than Redis. The reason that I decided to use Redis here is because it’s very simple to setup. You can get it up and running with default configurations in the blink of an eye. I’m not kidding. You can use this solution to create a Minimum Viable Product (MVP) in less than an hour. It’s really nice, isn’t it?

I’m not going to show you how to setup Play! project nor will I show you how to configure an sbt project because this is not a tutorial for that, so I’ll skip the boring parts and go right to the fun part.

Image titleFigure 1 — The architecture of Scalable WebSocket

Figure 1 — The architecture of Scalable WebSocket

Each web browser page has its own connection. When the web browser initiates a connection, it will ask the load balancer which web server it should talk to. After getting the specific server address, the web browser will create a persistent TCP connection to that web server. From figure 1, you might wonder why there is only one Redis server. Actually, it’s not necessary because you can set up a Redis cluster but Redis pubsub model is not that robust and you may have traffic congestion from the synchronization of messages across a cluster. Of course, I might be wrong since I didn’t test it out (I didn’t even look into it), please share your thoughts if you think otherwise and I will really appreciate :). You can replace Redis with either pure Akka solution or other middlewares such as Kafka, RabbitMQ, and ActiveMQ to get more scalability and reliability.

Let’s start from the Controller part.
You may want to read this tutorial from Playframework website before you get going.

class Application extends Controller {

  ... 

  implicit val akkaSystem = akka.actor.ActorSystem()

  val redis = RedisClient()

  def index = Action { implicit request =>
    Ok(views.html.pubsub())
  }

  def subscribe = WebSocket.tryAcceptWithActor[String, String] { request =>
    def props(channel: String)(out: ActorRef) = Props(classOf[SubscribeActor], redis, 
      out, Seq(channel), Nil).withDispatcher("rediscala-worker-dispatcher")
    Future.successful(request.getQueryString("channel") match {
      case None => Left(Forbidden)
      case Some(channel) => Right(props(channel))
    })
  }

  def publish(channel: String) = Action.async { implicit request =>
    request.body.asFormUrlEncoded.flatMap{ params =>
      params.get("message").map{ message =>
        redis.publish(channel, message.head).map{ n =>
          if(n > 0) {
            log.debug(s"Number of subscriber: $n s")
            Ok(n.toString)
          }else {
            log.debug("No recipient")
            Gone
          }
        }
      }
    }.getOrElse(Future.successful(BadRequest("No content received")))
  }
  ...
}

There are 3 important controller’s actions that you have to create. The first one is index() action for loading resources such as HTML, CSS and JavaScript. The second one is subscribe() action, this action will bind the WebSocket’s connection to Akka’s actor. Rediscala is powered by Akka’s actor, so it’s very easy to bind WebSocket to Redis pubsub because the code that does the actual work is already there. What you have to do is just introduce all parties to each other and they will figure out how to collaborate by themselves. Uhmmm…I feel like I’m a manager even though I’m actually coding. The third action is publish() which is not necessary in this case but it was there for a reason. The publish() action is just regular web action that processes the form and delegates the message to Redis server. Why did I say it’s not necessary when it sounds really important? That’s because we can implement it in JavaScript. Remember that WebSocket is a full-duplex protocol; you don’t need a separate connection to send data back to the server! So, the reason why it is there is because we may want to add other push technologies such as Comet or SSE as a fallback solution in the future.

What we are missing in the server side is SubscribeActor which is the dispatcher for Redis pubsub service. Let’s implement it.

class SubscribeActor(redis: RedisClient, out: ActorRef, 
    channels: Seq[String] = Nil, patterns: Seq[String] = Nil) extends RedisSubscriberActor(
    new InetSocketAddress(redis.host, redis.port), channels, patterns,
    onConnectStatus = connected => { println(s"connected: $connected") }) {

  def onMessage(message: Message) {
    out ! message.data.decodeString("UTF-8")
  }

  def onPMessage(pmessage: PMessage) {}
  ...
}

That’s pretty simple, isn’t it? It doesn’t do anything much here because what we want is just to delegate the message receiving from Redis pubsub to the Actor that is bound to a WebSocket’s connection.

Let’s skip the configuration, how to wire plugins, HTML, and their cosmetic stuff to the JavaScript code that is working as a subscriber of our publisher.

var socket = null
function subscribe(channel) {
    var subscribeUrl = jsRoutes.controllers.Application.subscribe().webSocketURL() + "?channel=" + channel
    console.log("Subscribed to: " + subscribeUrl)
    socket = new WebSocket(subscribeUrl)
    socket.onmessage = function(event) {
        $("#messages" ).prepend("<div>" + event.data + "</div>")
    }
    socket.onclose = function(event) {
        console.log(event)
    }
}
$(document).ready(function(){
    subscribe($("#channel" ).val())
    $("#channel" ).change(function(){
        if(socket) socket.close()
        subscribe($(this ).val())
    })
    $("#submit" ).click(function(){
        var publishUrl = jsRoutes.controllers.Application.publish($("#channel" ).val() ).url
        jQuery.post(publishUrl, "message="+$("#message" ).val() ).success(function(){
            $("#message" ).val("")
        })
    })
})


I’m pretty sure you can write the better code than my PoC code. You may have already guessed what the program does. Yes, it is a chatroom where you can change the channel. The subscriber will be labelled by channel name. At this point, I have shown you the server side code and the client side code. What about Redis, our message coordinator? I don’t need to show you because you just have to run the server and that’s it! That’s another reason why I picked Redis.

You think creating a scalable WebSocket chatroom in an hour is a bit too long? Clone the this repository and you will get it in a minute.

If you would like to read other articles like this one check out https://medium.com/zappos-engineering.

Crafter is a modern CMS platform for building modern websites and content-rich digital experiences. Download this eBook now. Brought to you in partnership with Crafter Software.

Topics:
scala ,websocket ,scalability

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}