Websockets talk at Rubyconf Uruguay 2010

40
WebSockets in Ruby (and things you can do with them) Ismael Celis @ismasan github.com/ismasan new bamboo new-bamboo.co.uk pusherapp.com

Transcript of Websockets talk at Rubyconf Uruguay 2010

Page 1: Websockets talk at Rubyconf Uruguay 2010

WebSockets in Ruby(and things you can do with them)

Ismael Celis @ismasan github.com/ismasan

new bamboo

new-bamboo.co.uk pusherapp.com

Page 2: Websockets talk at Rubyconf Uruguay 2010

Polling

Long-polling

Web socket

websockets.org/about.html

HTML5 Web sockets

Page 3: Websockets talk at Rubyconf Uruguay 2010

Use cases

•Chat (lame)

•Stocks (lame but challenging)

•Games

•Presence

•Collaboration

•Real-time notifications

Page 4: Websockets talk at Rubyconf Uruguay 2010

WebSockets DOM API<script type="text/javascript" charset="utf-8"> // Socket object var socket = new WebSocket('ws://some.host.com'); // Callbacks socket.onopen = function (evt) { alert('Socket connected: ' + evt.data) }; // Incoming server message socket.onmessage = function (evt) { alert( evt.data ) }; socket.onclose = function (evt) { alert('Connection terminated') // reconnect, etc. };</script>

Page 5: Websockets talk at Rubyconf Uruguay 2010

WebSockets handshakeRequest

Response

GET /demo HTTP/1.1Host: example.comConnection: UpgradeSec-WebSocket-Key2: 12998 5 Y3 1 .P00Sec-WebSocket-Protocol: sampleUpgrade: WebSocketSec-WebSocket-Key1: 4 @1 46546xW%0l 1 5Origin: http://example.com

^n:ds[4U

HTTP/1.1 101 WebSocket Protocol HandshakeUpgrade: WebSocketConnection: UpgradeSec-WebSocket-Origin: http://example.comSec-WebSocket-Location: ws://example.com/demoSec-WebSocket-Protocol: sample

8jKS'y:G*Co,Wxa-

(draft 76)

Page 6: Websockets talk at Rubyconf Uruguay 2010

"\x00Hello World\xff"

Messages

// Incoming server messagesocket.onmessage = function (evt) { alert( evt.data )};

// Send message to serversocket.send("Hello server, this is a new client");

Page 7: Websockets talk at Rubyconf Uruguay 2010

(Ruby) WebSockets server

•Speak WebSockets (handshake)

•Keep connections alive

•Performant!

•Scalable

• Threads? Evented?

Page 8: Websockets talk at Rubyconf Uruguay 2010

EventMachine rubyeventmachine.com

require 'eventmachine'

module EchoServer def receive_data(data) send_data data end end

EventMachine::run { EventMachine::start_server 'localhost', 8080, EchoServer puts 'running echo server on 8080'}

Page 9: Websockets talk at Rubyconf Uruguay 2010

EM-WebSocket github.com/igrigorik/em-websocket

EventMachine.run { EventMachine::WebSocket.start(:host=>'0.0.0.0',:port=>8080) do |socket| socket.onopen { # publish message to the client socket.send 'Websocket connection open' } socket.onmessage {|msg| # echo message to client socket.send "Received message: #{msg}" } end}

Page 10: Websockets talk at Rubyconf Uruguay 2010

Multicast - subscribers...@channel = Channel.new...

socket.onopen { @channel.subscribe socket}

socket.onmessage { |msg| @channel.send_message msg}

socket.onclose { @channel.unsubscribe socket}

Page 11: Websockets talk at Rubyconf Uruguay 2010

Multicast - subscribers# Serversocket.onopen { @channel.subscribe socket}

class Channel def initialize @sockets = [] end def subscribe( socket ) @sockets << socket end ...end

Page 12: Websockets talk at Rubyconf Uruguay 2010

Multicast - channelclass Channel ... def subscribe( socket ) @sockets << socket end def send_message( msg ) @sockets.each do |socket| socket.send msg end end def unsubscribe( socket ) @sockets.delete socket endend

Page 13: Websockets talk at Rubyconf Uruguay 2010

Multicast - example

var socket = new WebSocket('ws://localhost:8080');

socket.onmessage = function( evt ) {$('<li>')

.text(evt.data) .appendTo('#messages');}

<ul id="messages"> </ul>

HTML

Javascript

Page 14: Websockets talk at Rubyconf Uruguay 2010

Multicast - example

github.com/ismasan/websockets_examples

Page 15: Websockets talk at Rubyconf Uruguay 2010

Pick your protocol

STOMP CONNECTlogin: <username>passcode: <passcode>

<message from=”[email protected]/ruby” to=”[email protected]/ruby”><body>Hey Jane!</body>

</message>

XMPP

Your own (JSON?)

Page 16: Websockets talk at Rubyconf Uruguay 2010

Custom message format

JSON

Page 17: Websockets talk at Rubyconf Uruguay 2010

Custom JSON format

{“event”: “user_connected”,

{“name” : “Ismael”,“total_users” : 10

}

}

Event name

Custom data

“data”:

Page 18: Websockets talk at Rubyconf Uruguay 2010

Custom JSON format

{“event”: “user_message”,

{“message” : “Hello!”,“date” : “23 2010 21:12:28”

}

}

“data”:

Page 19: Websockets talk at Rubyconf Uruguay 2010

Javascript wrapper

var socket = new FancyWebSocket('ws://localhost:8080');

...

socket.bind( 'user_connected', function (user_data) { // Add user to screen $('#connected_users').append('<li>' + user_data.name + '</li>');});

socket.bind( 'user_message', function (msg_data) { // Add message to screen $('#messages').append('<li>' + msg_data.message + '</li>');});

Page 20: Websockets talk at Rubyconf Uruguay 2010

// Broadcast message - jQuery example

$('form#user_input').submit(function () { var msg = $(this).find('input[name=message]').val(); socket.send( 'user_message', {name: 'Ismael', message: msg} ); return false;});

Javascript wrapper

{“event” : “user_message”,

{“name” : “Ismael”,“message” : “hello!”

}}

“data” :

Page 21: Websockets talk at Rubyconf Uruguay 2010

Implementation gist.github.com/299789var FancyWebSocket = function(url){ var conn = new WebSocket(url);

var callbacks = {};

this.bind = function(event_name, callback){ callbacks[event_name] = callbacks[event_name] || []; callbacks[event_name].push(callback); };

this.send = function(event_name, event_data){ var payload = JSON.stringify({event:event_name, data: event_data}); conn.send( payload ); };

// dispatch to the right handlers conn.onmessage = function(evt){ var json = JSON.parse(evt.data) dispatch(json.event, json.data) };

conn.onclose = function(){dispatch('close',null)} conn.onopen = function(){dispatch('open',null)}

var dispatch = function(event_name, message){ var chain = callbacks[event_name]; if(typeof chain == 'undefined') return; // no callbacks for this event for(var i = 0; i < chain.length; i++){ chain[i]( message ) } }};

Page 22: Websockets talk at Rubyconf Uruguay 2010

Implementation gist.github.com/299789

var FancyWebSocket = function(url){

var conn = new WebSocket(url);

var callbacks = {};

this.bind = function(event_name, callback){ callbacks[event_name] = callbacks[event_name] || []; callbacks[event_name].push(callback); };

this.send = function(event_name, event_data){ var payload = JSON.stringify({event:event_name, data: event_data}); conn.send( payload ); };

// dispatch to the right handlers conn.onmessage = function(evt){ var json = JSON.parse(evt.data) dispatch(json.event, json.data) };

};

Page 23: Websockets talk at Rubyconf Uruguay 2010

Ismael Celis

Implementation gist.github.com/299789

var FancyWebSocket = function(url){ var conn = new WebSocket(url);

var callbacks = {};

this.bind = function(event_name, callback){ callbacks[event_name] = callbacks[event_name] || []; callbacks[event_name].push(callback); };

this.send = function(event_name, event_data){ var payload = JSON.stringify({event:event_name, data: event_data}); conn.send( payload ); };

// dispatch to the right handlers conn.onmessage = function(evt){ var json = JSON.parse(evt.data) dispatch(json.event, json.data) };

conn.onclose = function(){dispatch('close',null)} conn.onopen = function(){dispatch('open',null)}

var dispatch = function(event_name, message){ var chain = callbacks[event_name]; if(typeof chain == 'undefined') return; // no callbacks for this event for(var i = 0; i < chain.length; i++){ chain[i]( message ) } }};

Page 24: Websockets talk at Rubyconf Uruguay 2010

// dispatch to the right handlers conn.onmessage = function(evt){ var json = JSON.parse(evt.data) dispatch(json.event, json.data) };

conn.onclose = function(){dispatch('close',null)} conn.onopen = function(){dispatch('open',null)}

var dispatch = function(event_name, message){ var chain = callbacks[event_name]; if(typeof chain == 'undefined') return; // no callbacks for this event for(var i = 0; i < chain.length; i++){ chain[i]( message ) } }};

Implementation

WebSocket

gist.github.com/299789

Page 25: Websockets talk at Rubyconf Uruguay 2010

var FancyWebSocket = function(url){

// dispatch to the right handlers conn.onmessage = function(evt){ var json = JSON.parse(evt.data) dispatch(json.event, json.data) };

conn.onclose = function(){dispatch('close',null)} conn.onopen = function(){dispatch('open',null)}

var dispatch = function(event_name, message){ var chain = callbacks[event_name]; // no callbacks for this event if(typeof chain == 'undefined') return; for(var i = 0; i < chain.length; i++){ chain[i]( message ) } }};

Implementation gist.github.com/299789

Page 26: Websockets talk at Rubyconf Uruguay 2010

socket.send( 'user_message', {name: 'Ismael', message: msg} );

Implementation gist.github.com/299789

this.send = function(event_name, event_data){ var payload = JSON.stringify({event:event_name, data: event_data}); conn.send( payload ); // <= send JSON data to socket server return this;};

Page 27: Websockets talk at Rubyconf Uruguay 2010

Multicast - JSON data

github.com/ismasan/websockets_examples

Page 28: Websockets talk at Rubyconf Uruguay 2010

Multicast - JSON data

$('#canvas').mousedown(function () { drawing = true;}).mouseup(function () { drawing = false;}).mousemove(function (evt) { if(drawing) { var point = [evt.pageX, evt.pageY]; socket.send('mousemove', point); }});

Page 29: Websockets talk at Rubyconf Uruguay 2010

Multicast - JSON datavar ctx = document.getElementById('canvas').getContext('2d');ctx.lineWidth = 1;ctx.strokeStyle = '#ffffff';ctx.beginPath();ctx.moveTo(0, 0);

// Listen to other user's movessocket.bind('mousemove', function (point) { ctx.lineTo(point[0],point[1]); ctx.stroke();});

Page 30: Websockets talk at Rubyconf Uruguay 2010

Activity dashboard

Page 31: Websockets talk at Rubyconf Uruguay 2010

Rails Rumble dashboard

Page 32: Websockets talk at Rubyconf Uruguay 2010

Scaling

?

Page 33: Websockets talk at Rubyconf Uruguay 2010

pusherapp.com

Page 34: Websockets talk at Rubyconf Uruguay 2010

pusherapp.com

Your appREST

Browsers

Websockets

Existing HTTP

Page 35: Websockets talk at Rubyconf Uruguay 2010

pusherapp.com

Page 36: Websockets talk at Rubyconf Uruguay 2010

pusherapp.com

<script src="http://js.pusherapp.com/1.6/pusher.min.js"></script>

<script type="text/javascript" charset="utf-8"> var socket = new Pusher('293d8ae77496e1fc053b'); </script>

Page 37: Websockets talk at Rubyconf Uruguay 2010

pusherapp.com

var socket = new Pusher('293d8ae77496e1fc053b'); // Subscribe to channelsvar channel = socket.subscribe( 'test' ); channel.bind( 'new_message', function (data) { alert( data.message );})

Page 38: Websockets talk at Rubyconf Uruguay 2010

pusherapp.com

// Subscribe to PRESENCE channelvar chat = socket.subscribe( 'presence-chat' ); // Listen to new memberschat.bind( 'pusher:member_added', function (member) { alert( member.user_data.name );})

Page 39: Websockets talk at Rubyconf Uruguay 2010

require 'pusher'require 'sinatra'

post '/messages' do message = Message.create(params[:message]) Pusher['presence-chat'].trigger(:new_message, message.attributes) redirect '/messages'end

pusherapp.com

$ gem install pusher

Page 40: Websockets talk at Rubyconf Uruguay 2010

pusherapp.com

github.com/

lifo/cramp

ismasan/websockets_examplesnewbamboo/rumbledash

blog.new-bamboo.co.ukblog.pusherapp.com