Get Real: Adventures in realtime web apps
- 2. Real time?
• In a real time application, all participants
receive content as soon as it is authored.
- 8. Real time web
• Any real time application that is accessed
via a web browser
- 12. • Gmail and Chat
• Google docs
• Any real-time feed
(news, stocks, weather,
class availability in registration system,
Lady Gaga’s Twitter feed)
• WebEx, GoTo Meeting, Adobe Connect,
etc. (??)
Others
- 13. Building real time web
apps is challenging
• HTTP is stateless
• Request / Response is its favorite song
• Simulating ongoing connection is expensive
• People still use IE
- 15. Hello,Wall!
• Anything typed by anyone is seen by
everyone.
• Simply showing up on the page allows you
to start participating.
• Very low barrier to entry.
- 16. • Participants’ messages go to the dispatcher
• Participants need to get new messages
• Everyone has their own copy of the wall
- 17. How do people receive
changes to the wall?
• They could ask for them
• They could wait until someone calls
• But how do you call a browser?
• or appear to call
- 19. • Polling
• Long polling
• Hanging GET / infinite (forever) iFrame
• Flash Real Time Messaging Protocol (RTMP)
• Web sockets
• Server Sent Events (SSE)
- 20. • If all you’ve got is HTTP . . .
• Everything looks like a Request / Response
• This (anti)pattern is probably the most
common unexamined choice for keeping up
with status.
Polling
- 22. setInterval( function ( ) {
var server = ‘http://www.highedwebgetsreal.com’;
$.ajax({ url: server, data: {action: ‘getMessages’},
success: function(data) {
//Update the wall
chatFunctions.writeWall(data.value);
}, dataType: ‘json’}
);
}, 30000);
Polling
- 23. • AKAThe long-held connection
• Server programming waits to answer until
it has a meaningful answer
• The client either receives an event or times
out
• A new connection is established immdiately
Long Polling
- 25. (function poll() {
var server = ‘http://www.highedwebgetsreal.com’;
$.ajax({ url: server, data: {action: ‘getMessages’},
success: function(data) {
//Update the wall
chatFunctions.writeWall(data.value);
}, dataType: "json", complete: poll }
);
})();
Long polling
- 26. • The closest thing to a continuous
connection that HTTP may have to offer
• Leverages the fact that HTTP requests that
return chunked data do not need to
accurately report their size
• Unreliable support in IE
• A HACK!
Hanging GET or
infinite iFrame
infinite iFrame
- 29. • Client-side Flash object establishes stateful
and persistent connection with server
• Web page communicates via Javascript to
and from the Flash object
• Proprietary but fairly robust
Flash RTMP
- 30. script type='text/javascript' src='realTimeLib.js'></script>
div id=”wall”></div>
form>
<input type=”text” id='msg' />
<input type=”submit” id=”send” value=”Send” />
/form>
script type="text/javascript">
rtl = new realTimeLib();
rtl.setup({'RTMPrelay': 'RTMPrelay.swf'});
$(“#send”).click( function() {
rtl.send($(“#msg”).value());
});
function postToWall(data) {
// This function is called by rtl object’s swf when it receives a new post
$(“#wall”).append(data);
}
/script>
Flash RTMP
- 31. The story so far
• All of these techniques are work-arounds
and compromises
• Leverage side-effects of HTTP protocol and
browser implementations
• I.E. - not based on standards
• (except Flash RMTP, which has been
partially open-sourced by Adobe)
- 32. Web Sockets
• Web sockets is a proposal for a standard
that strives to solve the problem of server-
initiated messages to clients
• IETF RFC 6455
• In standards process at W3C
• Already old hat
- 33. Another channel
• Web Sockets work by requesting an
upgrade via a standard HTTP request
• The protocol gets changed to Web Socket,
which is a stateful TCP connection over
another port (often 443)
• Both client and server code then listens for
events on the channel and responds
accordingly.
- 34. Web sockets
are good listeners
are good listenersvar ws = new WebSocket("ws://www.highedwebgetsreal.com/chat");
ws.onopen = function() {
ws.send("{‘user’:‘David’}");
}
ws.onNewWallPost = function (evt) {
var received_msg = chatFunctions.parseServerEvt(evt.data);
chatFunctions.writeWall(received_msg.post);
}
$(“#sendBtn”).click(function() {
ws.send($(“#msgField”).value());
}
- 37. Ecapsulated and abstracted
graceful degradation
• Several libraries exist that enable
abstracted real time messaging
• They use the best technology available to
the client and fail over to the next best if
its missing
• Use Web Sockets, Flash, long polling,
or forever iFrame, in that order
- 38. Server side
• Node.js has become a very popular server
side implementation language
• It’s non-blocking and has a native event-
driven processing model
• It also lends itself to elegant symmetries
between server and client side code
- 39. Server side
• Juggernaut,WebSocket-Node and Socket.IO are
well tested Node libraries
• Socket.IO seems most mature and well maintained
• Nodejitsu.com hosts Node that you write
• Any framework that offers request routing works
well
• Ruby has EM-WebSocket, Socket.IO-rack, Cramp,
or just plain old Sinatra
- 40. Server side
• There are also services that take care of
the server for you and let you get started
building apps right away.
• Pusher.com is probably the most visible,
and maybe the most mature.
• peerbind.com is an interesting new-comer
- 42. /app.js
ar app = require('http').createServer(handler)
, io = require('socket.io').listen(app)
, fs = require('fs')
pp.listen(80);
unction handler (req, res) {
fs.readFile(__dirname + '/index.html',
function (err, data) {
if (err) {
res.writeHead(500);
return res.end('Error loading index.html');
}
res.writeHead(200);
res.end(data);
});
o.sockets.on('connection', function (socket) {
socket.on('set_nickname', function (data) {
socket.set('nickname', data, function () {
socket.emit('ready', data);
});
});
socket.on('post', function (data) {
var tmpName = "";
socket.get('nickname', function (err, data) {
tmpName = data;
});
socket.broadcast.emit('share', '<p><strong>' + tmpName + ':</strong> ' + data.msg + '</p>');
socket.emit('share', '<p><strong>(me):</strong> ' + data.msg + '</p>');
});
);
- 43. !doctype html>
html>
<head>
<meta charset="utf-8">
<meta charset=utf-8>
<title>Real time chat example</title>
</head>
<body>
<h1>Hello Chat!</h1>
<form id="getNickName">
<label for="nickname">Choose a nickname </label>
<input type="text" name="nickname">
<input type="submit" name="setNickname" value="Set it!">
</form>
</body>
<script type="text/javascript" src="/socket.io/socket.io.js"></script>
<script type="text/javascript"
src="//ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js"></script>
<script type="text/javascript"
src="//cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.0.0.beta6/handlebars.min.js"></script>
<script id="wallControl" type="text/x-handlebars-template">
<p>User Nickname: {{userNickName}}</p>
<div class="wall" />
<form>
<input type="text" name="msg" />
<input type="submit" name="send" value="send">
</form>
</script>
- 44. <script type="text/javascript">
$(document).ready( function() {
var socket = io.connect('/');
$("[name='setNickname']").click(function(event) {
event.preventDefault();
socket.emit("set_nickname", $("[name='nickname']").val());
$("body").append(theWall);
});
socket.on('ready', function (data) {
var theWall = Handlebars.compile($("#wallControl").html());
theWall = theWall({userNickName: data});
$("#getNickName").remove();
$("body").append(theWall);
});
$("[name='send']").live("click", function(event) {
socket.emit("post", { msg: $("[name='msg']").val() });
$("[name='msg']").val("");
event.preventDefault();
});
socket.on('share', function (data) {
$(".wall").append(data);
});
});
</script>
</html>
- 45. div class="image">
<div id="canvasDiv"></div>
/div>
p class="demoToolList"><button id="clearCanvas" type="button">Clear</button></p>
unction addClick(x, y, dragging, broadcast) {
clickX.push(x);
clickY.push(y);
clickDrag.push(dragging);
if (broadcast) {
socket.emit("addClick", {x: x, y: y, dragging: dragging});
}
ocket.on('reAddClick', function (data) {
addClick(data.x, data.y, data.dragging, false);
);
unction redraw(click_x, click_y, click_drag, broadcast) {
clickX = click_x;
clickY = click_y;
clickDrag = clickDrag;
if (broadcast) {
socket.emit("redraw", {clickX: click_x, clickY: click_y, clickDrag: click_drag});
}
canvas.width = canvas.width; // Clears the canvas
context.strokeStyle = "#df4b26";
context.lineJoin = "round";
context.lineWidth = 5;
for(var i=0; i < click_x.length; i++) {
context.beginPath();
if(click_drag[i] && i){
context.moveTo(click_x[i-1], click_y[i-1]);
}else{
context.moveTo(click_x[i]-1, click_y[i]);
}
context.lineTo(click_x[i], click_y[i]);
context.closePath();
context.stroke();
}
ocket.on('reredraw', function (data) {
redraw(data.clickX, data.clickY, data.clickDrag, false);
);
- 46. More feature ideas easily
supported by Socket.IO
• Person to person IM sessions
• Chat rooms
• “Volatile” messages
• Blocking users
• Namespaces to multiplex single connections
- 48. Server-Sent Events
(SSE)
• Web Hypertext Application Technology Working Group
(WHATWG)
• Opera was first to provide support in 2006
• Simpler messaging protocol
• No need to upgrade connection - Runs on HTTP/80
• All browsers now support SSE in their current versions
• Except IE (except with EventSource.js polyfill)
- 49. // Client-side Javascript
var source = new EventSource('/stream');
source.addEventListener('message', function(e){
console.log('Received a message:', e.data);
});
# Server-side solution using Sinatra
get '/stream', :provides => 'text/event-stream' do
stream :keep_open do |out|
connections << out
out.callback { connections.delete(out) }
end
end
post '/' do
connections.each { |out| out << "data: #{params[:msg]}nn" }
204 # response without entity body
end