77
Building real-time web apps using WEBSOcKeTS WITH PLAY! [email protected] @connerdelights

Using Websockets with Play!

Embed Size (px)

DESCRIPTION

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!.

Citation preview

Page 1: Using Websockets with Play!

Building real-time web apps using WEBSOcKeTS WITH PLAY!

[email protected] @connerdelights

Page 2: Using Websockets with Play!

How we used to do it

request

Page 3: Using Websockets with Play!

How we used to do it

requestresponse

Page 4: Using Websockets with Play!

How we used to do it

requestresponse

new message!

Page 5: Using Websockets with Play!

How we used to do it

requestresponse

new message!

Page 6: Using Websockets with Play!

The web has changed (quite a bit)

Page 7: Using Websockets with Play!

The dynamic web needs real-time communication

Page 8: Using Websockets with Play!

The dynamic web needs real-time communication

Page 9: Using Websockets with Play!

short polling

Page 10: Using Websockets with Play!

short polling

Page 11: Using Websockets with Play!

short polling

Page 12: Using Websockets with Play!

short polling

Resource intensive, slow, limited to one response

Page 13: Using Websockets with Play!

Chunked Responses

Page 14: Using Websockets with Play!

Chunked Responses

Hacky,no error handling, half-duplex

Page 15: Using Websockets with Play!

Long Polling

Page 16: Using Websockets with Play!

Long Polling

Page 17: Using Websockets with Play!

Long Polling

Page 18: Using Websockets with Play!

Long Polling

well supported, still half-duplex,single req/resp

Page 19: Using Websockets with Play!

These do not handle high bursts of messages

Page 20: Using Websockets with Play!

These do not handle high bursts of messages

Stuck with the single request → response

model

Page 21: Using Websockets with Play!

GET / HTTP/1.1Host: www.google.comConnection: keep-aliveCache-Control: max-age=0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.71 Safari/537.36DNT: 1Accept-Encoding: gzip,deflate,sdchAccept-Language: en-US,en;q=0.8Cookie: PREF=ID=e248d326d84eb3dc:FF=0:TM=1372622071:LM=1274622070:S=2bERIaHgSKRjWeC8; NID=47=CHAZZVkq40TcovIu-FuXlU0pF2UPfqqSEhNqx8hqUKnZ7-s4uxGjtBEFK7kRtTSVEu4fzJ00vhB4OrLRxw8JfV5EuiKczEC2_EHkBqr1kNwn_NdZ73XRl2umFybXYoiVD_

HTTP/1.1 200 OKDate: Tue, 23 Jul 2013 23:28:02 GMTExpires: -1Cache-Control: private, max-age=0Content-Type: text/html; charset=UTF-8Set-Cookie: PREF=ID=e248d326d84eb3dc:FF=1997f24999d1d9ef:FF=0:TM=1334622374:LM=1374622082:S=ynynJppwL64C6VMRU; expires=Thu, 23-Jul-2015 23:28:02 GMT; path=/; domain=.google.comContent-Encoding: gzipServer: gwsX-XSS-Protection: 1; mode=blockX-Frame-Options: SAMEORIGINTransfer-Encoding: chunked

Inefficient

Over 1kb in headers

Page 22: Using Websockets with Play!

Can Play! do these?

Page 23: Using Websockets with Play!

Can Play! do these?

Yes!

Page 24: Using Websockets with Play!

Can Play! do these?

In fact, if you’re supporting older browsers, consider them!

Yes!

Page 25: Using Websockets with Play!

Websockets

Page 26: Using Websockets with Play!

Websockets

Full duplex,efficient, fast,only newer* browsers

Page 27: Using Websockets with Play!

How fast are websockets?

our office

EC2US West

Page 28: Using Websockets with Play!

How fast are websockets?

our office

EC2US West

Average 10ms ping

~13ms mean improvement

Full benchmarks:http://eng.42go.com/

Page 29: Using Websockets with Play!

GET / HTTP/1.1Host: www.google.comConnection: keep-aliveCache-Control: max-age=0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.71 Safari/537.36DNT: 1Accept-Encoding: gzip,deflate,sdchAccept-Language: en-US,en;q=0.8Cookie: PREF=ID=e248d326d84eb3dc:FF=0:TM=1372622071:LM=1274622070:S=2bERIaHgSKRjWeC8; NID=47=CHAZZVkq40TcovIu-FuXlU0pF2UPfqqSEhNqx8hqUKnZ7-s4uxGjtBEFK7kRtTSVEu4fzJ00vhB4OrLRxw8JfV5EuiKczEC2_EHkBqr1kNwn_NdZ73XRl2umFybXYoiVD_

HTTP/1.1 200 OKDate: Tue, 23 Jul 2013 23:28:02 GMTExpires: -1Cache-Control: private, max-age=0Content-Type: text/html; charset=UTF-8Set-Cookie: PREF=ID=e248d326d84eb3dc:FF=1997f24999d1d9ef:FF=0:TM=1334622374:LM=1374622082:S=ynynJppwL64C6VMRU; expires=Thu, 23-Jul-2015 23:28:02 GMT; path=/; domain=.google.comContent-Encoding: gzipServer: gwsX-XSS-Protection: 1; mode=blockX-Frame-Options: SAMEORIGINTransfer-Encoding: chunked Over 1kb in headers

Page 30: Using Websockets with Play!

How fast are websockets?

Initial Websockettransmission

WebsocketClient

HTTPClient

Page 31: Using Websockets with Play!

How fast are websockets?

EC2US West

TCP Websocket

0.002ms 0.02ms

Websockets arenot magical :)

Full benchmarks:http://eng.42go.com/

Page 32: Using Websockets with Play!

How fast are websockets?

EC2US West

TCP Websocket

0.002ms 0.02ms

Still quite fast

Full benchmarks:http://eng.42go.com/

Page 33: Using Websockets with Play!

Websockets deal with streams of data

from the client

to the client

Page 34: Using Websockets with Play!

What are my messages?Here’s Your messages!You have a new notification

from the client

to the client

Page 35: Using Websockets with Play!

What are my messages?Here’s Your messages!You have a new notificationI visited my user profile pageI visited the home page

from the client

to the client

Page 36: Using Websockets with Play!

What are my messages?Here’s Your messages!You have a new notificationI visited my user profile pageI visited the home pageHere’s a Message for FrankGot your message

from the client

to the client

Page 37: Using Websockets with Play!

What are my messages?Here’s Your messages!You have a new notificationI visited my user profile pageI visited the home pageHere’s a Message for FrankGot your messageCode push, reconnect please!

from the client

to the client

Page 38: Using Websockets with Play!

How does Play! handle websockets?

Page 39: Using Websockets with Play!

How does Play! handle websockets?

let’s step back a bit...

Page 40: Using Websockets with Play!

val it = Seq(1,2,3,4,5).toIterator while(it.hasNext) { println(it.next())}

Iterators

Page 41: Using Websockets with Play!

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

Page 42: Using Websockets with Play!

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

Page 43: Using Websockets with Play!

val list = Seq(1,2,3,4,5)

list.foreach(println)

Container Function

Iterators

Page 44: Using Websockets with Play!

Iteratorsval list = Seq(1,2,3,4,5)

list.foreach(println)

Container FunctionProducer Consumer

Page 45: Using Websockets with Play!

Iteratee(ie, the consumer)

Immutably consumes chunks from a Producer. Think: Iterates over a stream*.

Page 46: Using Websockets with Play!

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.

Page 47: Using Websockets with Play!

Enumerator(ie, the PRODUCER)

Produces typed chunks.Only produces when there is a CONSUMER.

Page 48: Using Websockets with Play!

Play! needs a producer and consumer for a Websocket stream

Page 49: Using Websockets with Play!

val in = Iteratee.foreach[JsArray](println)

Page 50: Using Websockets with Play!

val in = Iteratee.foreach[JsArray](println)

ConsumerThis iteratee will consume from an Enumerator (the client), printing the input to the console

Page 51: Using Websockets with Play!

val in = Iteratee.foreach[JsArray](println) val out = Enumerator("You connected!").andThen(Enumerator.eof)

ProducerThis Enumerator will be consumed by an iteratee

(The client), sending a string then EOF

Page 52: Using Websockets with Play!

def index = WebSocket.using[JsArray] { request => val in = Iteratee.foreach[JsArray](println) val out = Enumerator("You connected!").andThen(Enumerator.eof) (in, out) }

Page 53: Using Websockets with Play!

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()undefinedgot: You connected!closed! CloseEvent {reason: "", code: 1005, wasClean: true, clipboardData:

Page 54: Using Websockets with Play!

def echo = WebSocket.using[JsArray] { request =>

val (out, channel) = Concurrent.broadcast[JsArray] val in = Iteratee.foreach[JsArray](channel.push) (in, out) }

Page 55: Using Websockets with Play!

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

Page 56: Using Websockets with Play!

Channels

Page 57: Using Websockets with Play!

Channels...

how about a chat room?super simple^

Page 58: Using Websockets with Play!

Simplified version of the Play! Chat room websocket sample

https://github.com/playframework/playframework/blob/master/samples/scala/websocket-chat

Page 59: Using Websockets with Play!

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 = ??? }

Page 60: Using Websockets with Play!

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

outputlets us push data

into the enumerator

Page 61: Using Websockets with Play!

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 = ???}

Page 62: Using Websockets with Play!

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

Page 63: Using Websockets with Play!

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 = ???}

Page 64: Using Websockets with Play!

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) }}

Page 65: Using Websockets with Play!

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) } }}

Page 66: Using Websockets with Play!

This can be modified to be a lightweight pub-sub system by

creating many channels

Play!

Page 67: Using Websockets with Play!

Lessons learneduse debugging tools!

Page 68: Using Websockets with Play!

Lessons learneduse debugging tools!

Page 69: Using Websockets with Play!

Lessons learnedweird things can happen when you’re

depending on a long-lived socket

Page 70: Using Websockets with Play!

Lessons learnedweird things can happen when you’re

depending on a long-lived socket

clients lie voodoo when packet loss is high

disconnects happen

Page 71: Using Websockets with Play!

Lessons learned

babysit connections in your application

ping / pong

Page 72: Using Websockets with Play!

Lessons learned

your clients need to auto-reconnect(we’ll be open sourcing our

reconnecting WS library soon!)

Page 73: Using Websockets with Play!

Lessons learned

be careful about thread safety

val in = Iteratee.foreach[JsArray](/* handler */)

Page 74: Using Websockets with Play!

Lessons learned

make sure this guy is fast

val in = Iteratee.foreach[JsArray](/* handler */)

Page 75: Using Websockets with Play!

http://caniuse.com/websockets

Page 76: Using Websockets with Play!

http://caniuse.com/websockets