We use websockets for our clients because we care deeply about a fast, responsive user experience. At the Play! Framework meetup based near us in Mountain View, CA (http://www.meetup.com/PlayFramework/), we presented an introduction to using Websockets with Play!. We cover some relevant background into alternatives, benchmarks, and how Websockets work within Play!.
34. What are my messages?
Here’s Your messages!
You have a new notification
from the client
to the client
35. What are my messages?
Here’s Your messages!
You have a new notification
I visited my user profile page
I visited the home page
from the client
to the client
36. What are my messages?
Here’s Your messages!
You have a new notification
I visited my user profile page
I visited the home page
Here’s a Message for Frank
Got your message
from the client
to the client
37. What are my messages?
Here’s Your messages!
You have a new notification
I visited my user profile page
I visited the home page
Here’s a Message for Frank
Got your message
Code push, reconnect please!
from the client
to the client
40. val it = Seq(1,2,3,4,5).toIterator
while(it.hasNext) {
println(it.next())
}
Iterators
41. val it = Seq(1,2,3,4,5).toIterator
while(it.hasNext) {
println(it.next())
}
val list = Seq(1,2,3,4,5)
list.foreach(println)
Iterators
42. val list = Seq(1,2,3,4,5)
list.foreach(println)
Instead of imperatively traversing containers,
apply a function to elements in a container.
Iterators
43. val list = Seq(1,2,3,4,5)
list.foreach(println)
Container Function
Iterators
44. Iterators
val list = Seq(1,2,3,4,5)
list.foreach(println)
Container Function
Producer Consumer
46. Iteratee
(ie, the consumer)
Immutably consumes chunks from a Producer.
Think: Iterates over a stream*.
Iteratees can actually do a bit more,
but we’ll save that for another day.
50. val in = Iteratee.foreach[JsArray](println)
Consumer
This iteratee will consume from an Enumerator
(the client), printing the input to the console
51. val in = Iteratee.foreach[JsArray](println)
val out = Enumerator("You connected!").andThen(Enumerator.eof)
Producer
This Enumerator will be consumed by an iteratee
(The client), sending a string then EOF
52. def index = WebSocket.using[JsArray] { request =>
val in = Iteratee.foreach[JsArray](println)
val out = Enumerator("You connected!").andThen(Enumerator.eof)
(in, out)
}
53. def index = WebSocket.using[JsArray] { request =>
val in = Iteratee.foreach[JsArray](println)
val out = Enumerator("You connected!").andThen(Enumerator.eof)
(in, out)
}
It works!
> var createSocket = function() {
var s = new WebSocket("ws://localhost:9000");
s.onopen = function() { s.send("['hey!']"); }
s.onclose = function(e) { console.log("closed!",e); }
s.onmessage = function(msg) { console.log("got: ", msg.data); }
return s;
}
undefined
> var socket = createSocket()
undefined
got: You connected!
closed!
CloseEvent {reason: "", code: 1005, wasClean: true, clipboardData:
54. def echo = WebSocket.using[JsArray] { request =>
val (out, channel) = Concurrent.broadcast[JsArray]
val in = Iteratee.foreach[JsArray](channel.push)
(in, out)
}
55. def echo = WebSocket.using[JsArray] { request =>
val (out, channel) = Concurrent.broadcast[JsArray]
val in = Iteratee.foreach[JsArray](channel.push)
(in, out)
}
feeds into
Channels let us
imperatively push data
into an enumerator
58. Simplified version of the Play! Chat room
websocket sample
https://github.com/playframework/playframework/blob/master/samples/scala/websocket-chat
59. case class Join(username: String)
case class Quit(username: String)
case class Talk(username: String, text: String)
class ChatRoom extends Actor {
var members = Set.empty[String]
val (chatEnumerator, chatChannel) = Concurrent.broadcast[JsValue]
val chatBot = "Marvin"
def receive = ???
}
60. case class Join(username: String)
case class Quit(username: String)
case class Talk(username: String, text: String)
class ChatRoom extends Actor {
var members = Set.empty[String]
val (chatEnumerator, chatChannel) = Concurrent.broadcast[JsValue]
val chatBot = "Marvin"
def receive = ???
} channel
output
lets us push data
into the enumerator
61. case class Join(username: String)
case class Quit(username: String)
case class Talk(username: String, text: String)
class ChatRoom extends Actor {
var members = Set.empty[String]
val (chatEnumerator, chatChannel) = Concurrent.broadcast[JsValue]
val chatBot = "Marvin"
def receive = {
case Join(username) => {
members = members + username
broadcastMessage(chatBot, s"$username has joined")
sender ! chatEnumerator
}
case Quit(username) =>
case Talk(username, text) =>
}
def broadcastMessage(user: String, text: String): Unit = ???
}
62. case class Join(username: String)
case class Quit(username: String)
case class Talk(username: String, text: String)
class ChatRoom extends Actor {
var members = Set.empty[String]
val (chatEnumerator, chatChannel) = Concurrent.broadcast[JsValue]
val chatBot = "Marvin"
def receive = {
case Join(username) => {
members = members + username
broadcastMessage(chatBot, s"$username has joined")
sender ! chatEnumerator
}
case Quit(username) =>
case Talk(username, text) =>
}
def broadcastMessage(user: String, text: String): Unit = ???
}
send back a reference
to the chatroom’s enumerator
63. case class Join(username: String)
case class Quit(username: String)
case class Talk(username: String, text: String)
class ChatRoom extends Actor {
var members = Set.empty[String]
val (chatEnumerator, chatChannel) = Concurrent.broadcast[JsValue]
val chatBot = "Marvin"
def receive = {
case Join(username) => {
members = members + username
broadcastMessage(chatBot, s"$username has joined")
sender ! chatEnumerator
}
case Quit(username) => {
broadcastMessage(chatBot, s"$username has left")
members = members - username
}
case Talk(username, text) => broadcastMessage(username, text)
}
def broadcastMessage(user: String, text: String): Unit = ???
}
64. case class Join(username: String)
case class Quit(username: String)
case class Talk(username: String, text: String)
class ChatRoom extends Actor {
...
def broadcastMessage(user: String, text: String): Unit = {
val msg = Json.obj("user" -> JsString(user),
"message" -> JsString(text),
"members" -> JsArray(members.toList.map(JsString)))
chatChannel.push(msg)
}
}
65. object Application extends Controller {
lazy val chatroomActor = Akka.system.actorOf(Props[ChatRoom])
def chat(username: String) = WebSocket.async[JsValue] { request =>
(chatroomActor ? Join(username)) map {
// grab the Enumerator from ChatRoom:
case out: Enumerator[JsValue] =>
val in = Iteratee.foreach[JsValue] { event =>
chatroomActor ! Talk(username, (event "text").as[String])
}.mapDone { _ =>
chatroomActor ! Quit(username)
}
(in, out)
}
}
}
66. This can be modified
to be a lightweight
pub-sub system by
creating many
channels
Play!
70. Lessons learned
weird things can happen when you’re
depending on a long-lived socket
clients lie voodoo when
packet loss is high
disconnects
happen