Server-side Push
Comes of Age
by Brian Sam-Bodden
http://www.integrallis.com
HTTP
HTTP
Unsuspecting Programmers
HTTP
Tim Berners-Lee
Unsuspecting Programmers
... and it was good
...for documents
it scales!
Web Applications
Uhmm, yeah...
Remember that client-server desktop app?
It should be easy to port it to the web, right?
Client Server
Request
Response
C
C
Well, hello let me make
you a page good friend
... don’t forget the images
page-by-page...
Client Server
Request
Response
C
C
Well, hello let me make
you a page good friend
... don’t forget the images
page-by-page...
Client Server
Request
Response
C
C
Well, hello let me make
you a page good friend
... don’t forget the images
page-by-page...
ask for it
background
and change the
relevant bits
Client Server
Load the “application”
C Here’s the initial page load
ajax model
C ... just what’s changedUser Action #1
C ....
The Web as a
Platform
So things got
better, until ...
Push
Uhmm, yeah...
Remember that cool web app I underpaid you to build?
It should be easy to notify the user when somethin...
PUSH
Why?
Collaboration
Chat
Comments
Notifications
Bidding Platforms
Monitoring
Stocks
Scores
Games
Bi-directional
Asynchronous
Near Real-Time
Server Initiated*
Communications
But...
until fairly recently
Browsers Sucked!
So we had (have)
to Hack it
Java
Applets
Polling
iFrame
Streaming
Long
Polling
Flash
Streaming
XHR
Streaming
Java
Applets
Polling
iFrame
Streaming
Long
Polling
Flash
Streaming
XHR
Streaming
Java
Applets
Java
Applets
Polling
Java
Applets
iFrame
Streaming
Long
Polling
Flash
Streaming
XHR
Streaming
Polling
Polling
are we there yet?
setInterval(function() {
areWeThereYet();
}, 1000);
setInterval(function() {
areWeThereYet();
}, 1000);
setInterval(function() {
areWeThereYet();
}, 1000);
setInterval(function() {
areWeThereYet();
}, 1000);
Client Server
Request
Response
Request
Response
C
C
No Soup for you!
Ok, here you go
Event
polling
chatty / high traffic
Self-inflicted DDOS Attack!
iFrame
Streaming
Java
Applets
Polling
Long
Polling
Flash
Streaming
XHR
Streaming
iFrame
Streaming
iFrame
Streaming
Oh yes, it involves
an iFrame
#sadpanda
iFrame
Streaming
Demo
first you embed an
invisible iFrame
$('<iframe />', {
name: 'hidden-iframe',
id: 'hidden-iframe',
src: '/long-running',
css: { 'display': 'none' }
}).appendTo...
$('<iframe />', {
name: 'hidden-iframe',
id: 'hidden-iframe',
src: '/long-running',
css: { 'display': 'none' }
}).appendTo...
$('<iframe />', {
name: 'hidden-iframe',
id: 'hidden-iframe',
src: '/long-running',
css: { 'display': 'none' }
}).appendTo...
$('<iframe />', {
name: 'hidden-iframe',
id: 'hidden-iframe',
src: '/long-running',
css: { 'display': 'none' }
}).appendTo...
$('<iframe />', {
name: 'hidden-iframe',
id: 'hidden-iframe',
src: '/long-running',
css: { 'display': 'none' }
}).appendTo...
$('<iframe />', {
name: 'hidden-iframe',
id: 'hidden-iframe',
src: '/long-running',
css: { 'display': 'none' }
}).appendTo...
on the server
you need
streaming
capabilities
get '/long-running' do
stream do |out|
(10..100).step(10) do |n|
word = gimme_funny_word
out << update_progress(n, word.ke...
get '/long-running' do
stream do |out|
(10..100).step(10) do |n|
word = gimme_funny_word
out << update_progress(n, word.ke...
get '/long-running' do
stream do |out|
(10..100).step(10) do |n|
word = gimme_funny_word
out << update_progress(n, word.ke...
get '/long-running' do
stream do |out|
(10..100).step(10) do |n|
word = gimme_funny_word
out << update_progress(n, word.ke...
get '/long-running' do
stream do |out|
(10..100).step(10) do |n|
word = gimme_funny_word
out << update_progress(n, word.ke...
get '/long-running' do
stream do |out|
(10..100).step(10) do |n|
word = gimme_funny_word
out << update_progress(n, word.ke...
def update_progress(percent, word, meaning)
%[<script type="text/javascript">
parent.updatePage(#{percent}, '#{word}', '#{...
def update_progress(percent, word, meaning)
%[<script type="text/javascript">
parent.updatePage(#{percent}, '#{word}', '#{...
// called by the streamed server-sent script
function updatePage(percent, word, meaning) {
$kickItButton.text(percent + '%...
// called by the streamed server-sent script
function updatePage(percent, word, meaning) {
$kickItButton.text(percent + '%...
// called by the streamed server-sent script
function updatePage(percent, word, meaning) {
$kickItButton.text(percent + '%...
// called by the streamed server-sent script
function updatePage(percent, word, meaning) {
$kickItButton.text(percent + '%...
// called by the streamed server-sent script
function updatePage(percent, word, meaning) {
$kickItButton.text(percent + '%...
Drawback:
Page is ‘forever’
loading
iFrame
Streaming
Java
Applets
Polling
Long
Polling
Flash
Streaming
XHR
Streaming
XHR
Streaming
XHR
Streaming
XHR
Streaming
Demo
better than iframes
use AJAX call
send JSON
get '/stream' do
stream do |out|
(10..100).step(2) do |n|
out << gimme_funny_word.as_json
sleep 1.5
end
end
end
get '/stream' do
stream do |out|
(10..100).step(2) do |n|
out << gimme_funny_word.as_json
sleep 1.5
end
end
end
get '/stream' do
stream do |out|
(10..100).step(2) do |n|
out << gimme_funny_word.as_json
sleep 1.5
end
end
end
polling the stream
parse = function() {
// parse the xhr.responseText and update the UI
};
xhr = new XMLHttpRequest();
url = "/stream";
xhr.o...
parse = function() {
// parse the xhr.responseText and update the UI
};
xhr = new XMLHttpRequest();
url = "/stream";
xhr.o...
parse = function() {
// parse the xhr.responseText and update the UI
};
xhr = new XMLHttpRequest();
url = "/stream";
xhr.o...
parse = function() {
// parse the xhr.responseText and update the UI
};
xhr = new XMLHttpRequest();
url = "/stream";
xhr.o...
parse = function() {
// parse the xhr.responseText and update the UI
};
xhr = new XMLHttpRequest();
url = "/stream";
xhr.o...
frequency of polling the stream
>=
server serving rate
No Throbber
Freakout
Long
Polling
iFrame
Streaming
Java
Applets
Polling
Flash
Streaming
XHR
Streaming
Long
Polling
Long
Polling
most commonly
used
response is
blocked...
...until server event
occurs
Client Server
Request
Response
C
C
Nothing here, but hang on...
... and there you go, good day!
Event
polling
Long
Polling
Demo
get '/read' do
content_type :json
filename = 'data.txt'
last = params[:timestamp] == 'null' ? 0 : params[:timestamp].to_i
...
get '/read' do
content_type :json
filename = 'data.txt'
last = params[:timestamp] == 'null' ? 0 : params[:timestamp].to_i
...
get '/read' do
content_type :json
filename = 'data.txt'
last = params[:timestamp] == 'null' ? 0 : params[:timestamp].to_i
...
get '/read' do
content_type :json
filename = 'data.txt'
last = params[:timestamp] == 'null' ? 0 : params[:timestamp].to_i
...
get '/read' do
content_type :json
filename = 'data.txt'
last = params[:timestamp] == 'null' ? 0 : params[:timestamp].to_i
...
get '/read' do
content_type :json
filename = 'data.txt'
last = params[:timestamp] == 'null' ? 0 : params[:timestamp].to_i
...
get '/read' do
content_type :json
filename = 'data.txt'
last = params[:timestamp] == 'null' ? 0 : params[:timestamp].to_i
...
function longPoll() {
$.ajax({
type : 'get',
url : '/read?timestamp=' + timestamp,
async : true,
cache : false,
timeout : ...
function longPoll() {
$.ajax({
type : 'get',
url : '/read?timestamp=' + timestamp,
async : true,
cache : false,
timeout : ...
function longPoll() {
$.ajax({
type : 'get',
url : '/read?timestamp=' + timestamp,
async : true,
cache : false,
timeout : ...
function longPoll() {
$.ajax({
type : 'get',
url : '/read?timestamp=' + timestamp,
async : true,
cache : false,
timeout : ...
Naive Long polling w/ 10sec timeout
{
Requests that
returned data
Current polling
request
Requests in RED
are timed out
lo...
There is a big issue with
the previous example...
There is a big issue with
the previous example...
Besides using a Text
File as a database
The server doesn’t support
async responses...
The busy IO checking loop
will block
aget '/read' do
content_type :json
filename = 'data.txt'
last = params[:timestamp] == 'null' ? 0 : params[:timestamp].to_i...
aget '/read' do
content_type :json
filename = 'data.txt'
last = params[:timestamp] == 'null' ? 0 : params[:timestamp].to_i...
Difficult to
Implement
Flash
Streaming
Long
Polling
iFrame
Streaming
Java
Applets
Polling
XHR
Streaming
Flash
Streaming
Flash
Streaming
XML Socket
Single Pixel
Flash Movie
Go Away
Flash!
Push
Frameworks
Comet
!=
Just Long Polling
Amalgamation of
Techniques
Provide both Client
and Server
Components
Many use a
Pub-Sub Protocol
Bayeaux
Comet
Demo
with
http://faye.jcoglan.com
var client = new Faye.Client('/faye');
var subscription = client.subscribe('/<%= room %>', function(message) {
$messages =...
var client = new Faye.Client('/faye');
var subscription = client.subscribe('/<%= room %>', function(message) {
$messages =...
var client = new Faye.Client('/faye');
var subscription = client.subscribe('/<%= room %>', function(message) {
$messages =...
var client = new Faye.Client('/faye');
var subscription = client.subscribe('/<%= room %>', function(message) {
$messages =...
Now we can create a room ... and have a conversation
Web Sockets
Two Way
Communications
Over a dedicated
socket
in simple way
with security,
proxies & firewalls
in mind
Web
Sockets
Demo
EventMachine.run do
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8080) do |ws|
ws.onopen do
end
ws.onmessage...
EventMachine.run do
@channel = EM::Channel.new
@users = {}
@messages = []
...
ws.onopen do
new_user = @channel.subscribe {...
ws.onmessage do |msg|
@messages << msg
@messages.shift if @messages.length > 10
@channel.push msg
end
add the new message ...
ws.onclose do
@channel.unsubscribe(@users[ws.object_id])
@users.delete(ws.object_id)
end
we unsubscribe them from the chan...
EventMachine.run do
EventMachine::WebSocket.start(...) do |ws|
...
end
class App < Sinatra::Base
get '/' do
erb :index
end...
<div class="container">
<h1 class="visible-desktop">WebSockets Sinatra Draw</h1>
<legend>Draw Something</legend>
<div id="...
var currentX = 0;
var currentY = 0;
var lastX, lastY, lastReceivedX, lastReceivedY;
var drawing = false;
var ctx = $('#dra...
$canvas.bind('tapstart',function(ev) {
drawing = true
});
$canvas.bind('tapend',function(ev) {
drawing = false
});
tapstar...
ws.onopen = function(event) {
setInterval(function() {
if ((currentX !== lastX || currentY !== lastY) && drawing) {
lastX ...
ws.onmessage = function(event) {
var msg = $.parseJSON(event.data);
ctx.beginPath();
ctx.moveTo(lastReceivedX, lastReceive...
On Firefox
On Safari Desktop
... and on my almost out of
batteries iPhone
What are we
missing?
Server-sent
Events
BOSH
WebRTC
What should
you do?
Use a
Framework!
That plays well
with your
framework
Thanks
All example code available at:
https://github.com/integrallis/server-side-push
Watch out for an upcoming article at...
Server-Side Push: Comet, Web Sockets come of age (OSCON 2013)
Server-Side Push: Comet, Web Sockets come of age (OSCON 2013)
Server-Side Push: Comet, Web Sockets come of age (OSCON 2013)
Server-Side Push: Comet, Web Sockets come of age (OSCON 2013)
Upcoming SlideShare
Loading in …5
×

Server-Side Push: Comet, Web Sockets come of age (OSCON 2013)

6,249 views

Published on

Server-side browser push technologies have been around for a while in one way or another, ranging from from crude browser polling to Flash enabled frameworks. In this session you’ll get a code-driven walk-through on the evolution and mechanics of server-push technologies, including:

Server streaming
Polling and long Polling
Comet
Web Sockets

Published in: Technology, Education
0 Comments
5 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
6,249
On SlideShare
0
From Embeds
0
Number of Embeds
51
Actions
Shares
0
Downloads
57
Comments
0
Likes
5
Embeds 0
No embeds

No notes for slide

Server-Side Push: Comet, Web Sockets come of age (OSCON 2013)

  1. 1. Server-side Push Comes of Age by Brian Sam-Bodden http://www.integrallis.com
  2. 2. HTTP
  3. 3. HTTP Unsuspecting Programmers
  4. 4. HTTP Tim Berners-Lee Unsuspecting Programmers
  5. 5. ... and it was good
  6. 6. ...for documents
  7. 7. it scales!
  8. 8. Web Applications Uhmm, yeah... Remember that client-server desktop app? It should be easy to port it to the web, right?
  9. 9. Client Server Request Response C C Well, hello let me make you a page good friend ... don’t forget the images page-by-page model C ... hold on, I got hit the DB C ... and cook up some HTML C ... and all other assets
  10. 10. Client Server Request Response C C Well, hello let me make you a page good friend ... don’t forget the images page-by-page model C ... hold on, I got hit the DB C ... and cook up some HTML C ... and all other assets
  11. 11. Client Server Request Response C C Well, hello let me make you a page good friend ... don’t forget the images page-by-page model C ... hold on, I got hit the DB C ... and cook up some HTML C ... and all other assets
  12. 12. ask for it background
  13. 13. and change the relevant bits
  14. 14. Client Server Load the “application” C Here’s the initial page load ajax model C ... just what’s changedUser Action #1 C ... and againUser Action #2
  15. 15. The Web as a Platform
  16. 16. So things got better, until ...
  17. 17. Push Uhmm, yeah... Remember that cool web app I underpaid you to build? It should be easy to notify the user when something important happens, right?
  18. 18. PUSH
  19. 19. Why?
  20. 20. Collaboration
  21. 21. Chat
  22. 22. Comments
  23. 23. Notifications
  24. 24. Bidding Platforms
  25. 25. Monitoring
  26. 26. Stocks
  27. 27. Scores
  28. 28. Games
  29. 29. Bi-directional
  30. 30. Asynchronous
  31. 31. Near Real-Time
  32. 32. Server Initiated*
  33. 33. Communications
  34. 34. But...
  35. 35. until fairly recently
  36. 36. Browsers Sucked!
  37. 37. So we had (have) to Hack it
  38. 38. Java Applets Polling iFrame Streaming Long Polling Flash Streaming XHR Streaming
  39. 39. Java Applets Polling iFrame Streaming Long Polling Flash Streaming XHR Streaming
  40. 40. Java Applets
  41. 41. Java Applets
  42. 42. Polling Java Applets iFrame Streaming Long Polling Flash Streaming XHR Streaming
  43. 43. Polling
  44. 44. Polling
  45. 45. are we there yet?
  46. 46. setInterval(function() { areWeThereYet(); }, 1000);
  47. 47. setInterval(function() { areWeThereYet(); }, 1000);
  48. 48. setInterval(function() { areWeThereYet(); }, 1000);
  49. 49. setInterval(function() { areWeThereYet(); }, 1000);
  50. 50. Client Server Request Response Request Response C C No Soup for you! Ok, here you go Event polling
  51. 51. chatty / high traffic
  52. 52. Self-inflicted DDOS Attack!
  53. 53. iFrame Streaming Java Applets Polling Long Polling Flash Streaming XHR Streaming
  54. 54. iFrame Streaming
  55. 55. iFrame Streaming
  56. 56. Oh yes, it involves an iFrame #sadpanda
  57. 57. iFrame Streaming Demo
  58. 58. first you embed an invisible iFrame
  59. 59. $('<iframe />', { name: 'hidden-iframe', id: 'hidden-iframe', src: '/long-running', css: { 'display': 'none' } }).appendTo('body');
  60. 60. $('<iframe />', { name: 'hidden-iframe', id: 'hidden-iframe', src: '/long-running', css: { 'display': 'none' } }).appendTo('body');
  61. 61. $('<iframe />', { name: 'hidden-iframe', id: 'hidden-iframe', src: '/long-running', css: { 'display': 'none' } }).appendTo('body');
  62. 62. $('<iframe />', { name: 'hidden-iframe', id: 'hidden-iframe', src: '/long-running', css: { 'display': 'none' } }).appendTo('body');
  63. 63. $('<iframe />', { name: 'hidden-iframe', id: 'hidden-iframe', src: '/long-running', css: { 'display': 'none' } }).appendTo('body');
  64. 64. $('<iframe />', { name: 'hidden-iframe', id: 'hidden-iframe', src: '/long-running', css: { 'display': 'none' } }).appendTo('body');
  65. 65. on the server you need streaming capabilities
  66. 66. get '/long-running' do stream do |out| (10..100).step(10) do |n| word = gimme_funny_word out << update_progress(n, word.keys.first, word.values.first) sleep 1.5 end end end
  67. 67. get '/long-running' do stream do |out| (10..100).step(10) do |n| word = gimme_funny_word out << update_progress(n, word.keys.first, word.values.first) sleep 1.5 end end end
  68. 68. get '/long-running' do stream do |out| (10..100).step(10) do |n| word = gimme_funny_word out << update_progress(n, word.keys.first, word.values.first) sleep 1.5 end end end
  69. 69. get '/long-running' do stream do |out| (10..100).step(10) do |n| word = gimme_funny_word out << update_progress(n, word.keys.first, word.values.first) sleep 1.5 end end end
  70. 70. get '/long-running' do stream do |out| (10..100).step(10) do |n| word = gimme_funny_word out << update_progress(n, word.keys.first, word.values.first) sleep 1.5 end end end
  71. 71. get '/long-running' do stream do |out| (10..100).step(10) do |n| word = gimme_funny_word out << update_progress(n, word.keys.first, word.values.first) sleep 1.5 end end end
  72. 72. def update_progress(percent, word, meaning) %[<script type="text/javascript"> parent.updatePage(#{percent}, '#{word}', '#{meaning}'); </script>] end
  73. 73. def update_progress(percent, word, meaning) %[<script type="text/javascript"> parent.updatePage(#{percent}, '#{word}', '#{meaning}'); </script>] end
  74. 74. // called by the streamed server-sent script function updatePage(percent, word, meaning) { $kickItButton.text(percent + '%'); $theWord.text(word); $theMeaning.text(meaning); if (percent == 100) { $kickItButton.attr('disabled', false); $kickItButton.text('Kick it again!'); } }
  75. 75. // called by the streamed server-sent script function updatePage(percent, word, meaning) { $kickItButton.text(percent + '%'); $theWord.text(word); $theMeaning.text(meaning); if (percent == 100) { $kickItButton.attr('disabled', false); $kickItButton.text('Kick it again!'); } }
  76. 76. // called by the streamed server-sent script function updatePage(percent, word, meaning) { $kickItButton.text(percent + '%'); $theWord.text(word); $theMeaning.text(meaning); if (percent == 100) { $kickItButton.attr('disabled', false); $kickItButton.text('Kick it again!'); } }
  77. 77. // called by the streamed server-sent script function updatePage(percent, word, meaning) { $kickItButton.text(percent + '%'); $theWord.text(word); $theMeaning.text(meaning); if (percent == 100) { $kickItButton.attr('disabled', false); $kickItButton.text('Kick it again!'); } }
  78. 78. // called by the streamed server-sent script function updatePage(percent, word, meaning) { $kickItButton.text(percent + '%'); $theWord.text(word); $theMeaning.text(meaning); if (percent == 100) { $kickItButton.attr('disabled', false); $kickItButton.text('Kick it again!'); } }
  79. 79. Drawback: Page is ‘forever’ loading
  80. 80. iFrame Streaming Java Applets Polling Long Polling Flash Streaming XHR Streaming
  81. 81. XHR Streaming
  82. 82. XHR Streaming
  83. 83. XHR Streaming Demo
  84. 84. better than iframes
  85. 85. use AJAX call
  86. 86. send JSON
  87. 87. get '/stream' do stream do |out| (10..100).step(2) do |n| out << gimme_funny_word.as_json sleep 1.5 end end end
  88. 88. get '/stream' do stream do |out| (10..100).step(2) do |n| out << gimme_funny_word.as_json sleep 1.5 end end end
  89. 89. get '/stream' do stream do |out| (10..100).step(2) do |n| out << gimme_funny_word.as_json sleep 1.5 end end end
  90. 90. polling the stream
  91. 91. parse = function() { // parse the xhr.responseText and update the UI }; xhr = new XMLHttpRequest(); url = "/stream"; xhr.open("GET", url, true); xhr.send(); last_index = 0; interval = setInterval(parse, 500); setTimeout((function() { clearInterval(interval); parse(); xhr.abort(); }), 20000);
  92. 92. parse = function() { // parse the xhr.responseText and update the UI }; xhr = new XMLHttpRequest(); url = "/stream"; xhr.open("GET", url, true); xhr.send(); last_index = 0; interval = setInterval(parse, 500); setTimeout((function() { clearInterval(interval); parse(); xhr.abort(); }), 20000);
  93. 93. parse = function() { // parse the xhr.responseText and update the UI }; xhr = new XMLHttpRequest(); url = "/stream"; xhr.open("GET", url, true); xhr.send(); last_index = 0; interval = setInterval(parse, 500); setTimeout((function() { clearInterval(interval); parse(); xhr.abort(); }), 20000);
  94. 94. parse = function() { // parse the xhr.responseText and update the UI }; xhr = new XMLHttpRequest(); url = "/stream"; xhr.open("GET", url, true); xhr.send(); last_index = 0; interval = setInterval(parse, 500); setTimeout((function() { clearInterval(interval); parse(); xhr.abort(); }), 20000);
  95. 95. parse = function() { // parse the xhr.responseText and update the UI }; xhr = new XMLHttpRequest(); url = "/stream"; xhr.open("GET", url, true); xhr.send(); last_index = 0; interval = setInterval(parse, 500); setTimeout((function() { clearInterval(interval); parse(); xhr.abort(); }), 20000);
  96. 96. frequency of polling the stream >= server serving rate
  97. 97. No Throbber Freakout
  98. 98. Long Polling iFrame Streaming Java Applets Polling Flash Streaming XHR Streaming
  99. 99. Long Polling
  100. 100. Long Polling
  101. 101. most commonly used
  102. 102. response is blocked...
  103. 103. ...until server event occurs
  104. 104. Client Server Request Response C C Nothing here, but hang on... ... and there you go, good day! Event polling
  105. 105. Long Polling Demo
  106. 106. get '/read' do content_type :json filename = 'data.txt' last = params[:timestamp] == 'null' ? 0 : params[:timestamp].to_i current = last_modification(filename) not_changed_or_emtpy = true while (not_changed_or_emtpy) do sleep 0.1 not_changed_or_emtpy = File.zero?(filename) || (current <= last) current = last_modification(filename) end { :messages => File.read(filename), :timestamp => current }.to_json end
  107. 107. get '/read' do content_type :json filename = 'data.txt' last = params[:timestamp] == 'null' ? 0 : params[:timestamp].to_i current = last_modification(filename) not_changed_or_emtpy = true while (not_changed_or_emtpy) do sleep 0.1 not_changed_or_emtpy = File.zero?(filename) || (current <= last) current = last_modification(filename) end { :messages => File.read(filename), :timestamp => current }.to_json end
  108. 108. get '/read' do content_type :json filename = 'data.txt' last = params[:timestamp] == 'null' ? 0 : params[:timestamp].to_i current = last_modification(filename) not_changed_or_emtpy = true while (not_changed_or_emtpy) do sleep 0.1 not_changed_or_emtpy = File.zero?(filename) || (current <= last) current = last_modification(filename) end { :messages => File.read(filename), :timestamp => current }.to_json end
  109. 109. get '/read' do content_type :json filename = 'data.txt' last = params[:timestamp] == 'null' ? 0 : params[:timestamp].to_i current = last_modification(filename) not_changed_or_emtpy = true while (not_changed_or_emtpy) do sleep 0.1 not_changed_or_emtpy = File.zero?(filename) || (current <= last) current = last_modification(filename) end { :messages => File.read(filename), :timestamp => current }.to_json end
  110. 110. get '/read' do content_type :json filename = 'data.txt' last = params[:timestamp] == 'null' ? 0 : params[:timestamp].to_i current = last_modification(filename) not_changed_or_emtpy = true while (not_changed_or_emtpy) do sleep 0.1 not_changed_or_emtpy = File.zero?(filename) || (current <= last) current = last_modification(filename) end { :messages => File.read(filename), :timestamp => current }.to_json end
  111. 111. get '/read' do content_type :json filename = 'data.txt' last = params[:timestamp] == 'null' ? 0 : params[:timestamp].to_i current = last_modification(filename) not_changed_or_emtpy = true while (not_changed_or_emtpy) do sleep 0.1 not_changed_or_emtpy = File.zero?(filename) || (current <= last) current = last_modification(filename) end { :messages => File.read(filename), :timestamp => current }.to_json end
  112. 112. get '/read' do content_type :json filename = 'data.txt' last = params[:timestamp] == 'null' ? 0 : params[:timestamp].to_i current = last_modification(filename) not_changed_or_emtpy = true while (not_changed_or_emtpy) do sleep 0.1 not_changed_or_emtpy = File.zero?(filename) || (current <= last) current = last_modification(filename) end { :messages => File.read(filename), :timestamp => current }.to_json end
  113. 113. function longPoll() { $.ajax({ type : 'get', url : '/read?timestamp=' + timestamp, async : true, cache : false, timeout : 10000, success : function(json) { var messages = json['messages'].split("n") var last = messages[messages.length-1]; if (last) { $('#msg').append('<div>'+last+'</div>'); timestamp = json['timestamp']; } setTimeout(longPoll, 1000); }, error : function(xhr, textStatus, error) { setTimeout(longPoll, 2000); } }); }
  114. 114. function longPoll() { $.ajax({ type : 'get', url : '/read?timestamp=' + timestamp, async : true, cache : false, timeout : 10000, success : function(json) { var messages = json['messages'].split("n") var last = messages[messages.length-1]; if (last) { $('#msg').append('<div>'+last+'</div>'); timestamp = json['timestamp']; } setTimeout(longPoll, 1000); }, error : function(xhr, textStatus, error) { setTimeout(longPoll, 2000); } }); }
  115. 115. function longPoll() { $.ajax({ type : 'get', url : '/read?timestamp=' + timestamp, async : true, cache : false, timeout : 10000, success : function(json) { var messages = json['messages'].split("n") var last = messages[messages.length-1]; if (last) { $('#msg').append('<div>'+last+'</div>'); timestamp = json['timestamp']; } setTimeout(longPoll, 1000); }, error : function(xhr, textStatus, error) { setTimeout(longPoll, 2000); } }); }
  116. 116. function longPoll() { $.ajax({ type : 'get', url : '/read?timestamp=' + timestamp, async : true, cache : false, timeout : 10000, success : function(json) { var messages = json['messages'].split("n") var last = messages[messages.length-1]; if (last) { $('#msg').append('<div>'+last+'</div>'); timestamp = json['timestamp']; } setTimeout(longPoll, 1000); }, error : function(xhr, textStatus, error) { setTimeout(longPoll, 2000); } }); }
  117. 117. Naive Long polling w/ 10sec timeout { Requests that returned data Current polling request Requests in RED are timed out long polls
  118. 118. There is a big issue with the previous example...
  119. 119. There is a big issue with the previous example... Besides using a Text File as a database
  120. 120. The server doesn’t support async responses...
  121. 121. The busy IO checking loop will block
  122. 122. aget '/read' do content_type :json filename = 'data.txt' last = params[:timestamp] == 'null' ? 0 : params[:timestamp].to_i current = last_modification(filename) EM.defer do check_file_changes = proc do if File.zero?(filename) || (current <= last) current = last_modification(filename) EM.next_tick(&check_file_changes) else body({ :messages => File.read(filename), :timestamp => current }.to_json) end end EM.next_tick(&check_file_changes) end end
  123. 123. aget '/read' do content_type :json filename = 'data.txt' last = params[:timestamp] == 'null' ? 0 : params[:timestamp].to_i current = last_modification(filename) EM.defer do check_file_changes = proc do if File.zero?(filename) || (current <= last) current = last_modification(filename) EM.next_tick(&check_file_changes) else body({ :messages => File.read(filename), :timestamp => current }.to_json) end end EM.next_tick(&check_file_changes) end end
  124. 124. Difficult to Implement
  125. 125. Flash Streaming Long Polling iFrame Streaming Java Applets Polling XHR Streaming
  126. 126. Flash Streaming
  127. 127. Flash Streaming
  128. 128. XML Socket
  129. 129. Single Pixel Flash Movie
  130. 130. Go Away Flash!
  131. 131. Push Frameworks
  132. 132. Comet != Just Long Polling
  133. 133. Amalgamation of Techniques
  134. 134. Provide both Client and Server Components
  135. 135. Many use a Pub-Sub Protocol
  136. 136. Bayeaux
  137. 137. Comet Demo with http://faye.jcoglan.com
  138. 138. var client = new Faye.Client('/faye'); var subscription = client.subscribe('/<%= room %>', function(message) { $messages = $('#messages'); $message = $('<div>' + message['user'] + ' : ' + message['text'] +'</div>') $messages.append($message); }); $("#chat-form").submit(function(e){ e.preventDefault(); var message = $('#message').val(); client.publish('/<%= room %>', {user: '<%= username %>', text: message}); $('#message').val(''); });
  139. 139. var client = new Faye.Client('/faye'); var subscription = client.subscribe('/<%= room %>', function(message) { $messages = $('#messages'); $message = $('<div>' + message['user'] + ' : ' + message['text'] +'</div>') $messages.append($message); }); $("#chat-form").submit(function(e){ e.preventDefault(); var message = $('#message').val(); client.publish('/<%= room %>', {user: '<%= username %>', text: message}); $('#message').val(''); });
  140. 140. var client = new Faye.Client('/faye'); var subscription = client.subscribe('/<%= room %>', function(message) { $messages = $('#messages'); $message = $('<div>' + message['user'] + ' : ' + message['text'] +'</div>') $messages.append($message); }); $("#chat-form").submit(function(e){ e.preventDefault(); var message = $('#message').val(); client.publish('/<%= room %>', {user: '<%= username %>', text: message}); $('#message').val(''); });
  141. 141. var client = new Faye.Client('/faye'); var subscription = client.subscribe('/<%= room %>', function(message) { $messages = $('#messages'); $message = $('<div>' + message['user'] + ' : ' + message['text'] +'</div>') $messages.append($message); }); $("#chat-form").submit(function(e){ e.preventDefault(); var message = $('#message').val(); client.publish('/<%= room %>', {user: '<%= username %>', text: message}); $('#message').val(''); });
  142. 142. Now we can create a room ... and have a conversation
  143. 143. Web Sockets
  144. 144. Two Way Communications
  145. 145. Over a dedicated socket
  146. 146. in simple way
  147. 147. with security, proxies & firewalls in mind
  148. 148. Web Sockets Demo
  149. 149. EventMachine.run do EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8080) do |ws| ws.onopen do end ws.onmessage do |msg| end ws.onclose do end end end em-websocket provides an easy to use WebSocket class On the server we’ll implement some WebSocket event handlers
  150. 150. EventMachine.run do @channel = EM::Channel.new @users = {} @messages = [] ... ws.onopen do new_user = @channel.subscribe { |msg| ws.send msg } @users[ws.object_id] = new_user @messages.each do |message| ws.send message end end subscribe a new user to the channel passing the callback to our push action we’ll keep a list of users in a Hash keyed by the object_id of the incoming ws connection push the last batch of messages to the user
  151. 151. ws.onmessage do |msg| @messages << msg @messages.shift if @messages.length > 10 @channel.push msg end add the new message to the end of the queue broadcast the message to all users connected to the channel we’ll keep the last 10 messages
  152. 152. ws.onclose do @channel.unsubscribe(@users[ws.object_id]) @users.delete(ws.object_id) end we unsubscribe them from the channel remove them from the Hash of users
  153. 153. EventMachine.run do EventMachine::WebSocket.start(...) do |ws| ... end class App < Sinatra::Base get '/' do erb :index end end App.run! end our single page application is contained in /public/views/index.erb The Sinatra app runs as part of the EV “Reactor Loop”
  154. 154. <div class="container"> <h1 class="visible-desktop">WebSockets Sinatra Draw</h1> <legend>Draw Something</legend> <div id="whiteboard" class="well well-small"> <canvas id="draw-canvas"></canvas> </div> </div> We’ll nest the canvas in a div in order to resize it correctly $(document).ready(function() { var $canvas = $('#draw-canvas'); var ws = new WebSocket("ws://" + location.hostname + ":8080"); When the document is ready we’ll connect to the EM Websocket server running on :8080
  155. 155. var currentX = 0; var currentY = 0; var lastX, lastY, lastReceivedX, lastReceivedY; var drawing = false; var ctx = $('#draw-canvas')[0].getContext('2d'); We’ll grab the 2D canvas context in order to draw on it $canvas.bind('mousemove',function(ev){ ev = ev || window.event; currentX = ev.pageX - $canvas.offset().left; currentY = ev.pageY - $canvas.offset().top; }); $canvas.bind('touchmove',function(ev){ var touch = ev.originalEvent.touches[0] || ev.originalEvent.changedTouches[0]; currentX = touch.pageX - $canvas.offset().left; currentY = touch.pageY - $canvas.offset().top; }); We’ll update the currentX and currentY coordinates of the mouse over the canvas both for desktop and mobile browsers touchmove is provided by jQuery-Mobile-Events plugin
  156. 156. $canvas.bind('tapstart',function(ev) { drawing = true }); $canvas.bind('tapend',function(ev) { drawing = false }); tapstart and tapend are also provided by the jQuery-Mobile-Events
  157. 157. ws.onopen = function(event) { setInterval(function() { if ((currentX !== lastX || currentY !== lastY) && drawing) { lastX = currentX; lastY = currentY; ws.send(JSON.stringify({ x: currentX, y: currentY})); } }, 30); }
  158. 158. ws.onmessage = function(event) { var msg = $.parseJSON(event.data); ctx.beginPath(); ctx.moveTo(lastReceivedX, lastReceivedY); ctx.lineTo(msg.x, msg.y); ctx.closePath(); ctx.stroke(); lastReceivedX = msg.x; lastReceivedY = msg.y; }; We’ll only draw indirectly when we receive a message (even when we are the ones doing the drawing)
  159. 159. On Firefox On Safari Desktop ... and on my almost out of batteries iPhone
  160. 160. What are we missing?
  161. 161. Server-sent Events
  162. 162. BOSH
  163. 163. WebRTC
  164. 164. What should you do?
  165. 165. Use a Framework!
  166. 166. That plays well with your framework
  167. 167. Thanks All example code available at: https://github.com/integrallis/server-side-push Watch out for an upcoming article at http://integrallis.com by Brian Sam-Bodden http://www.integrallis.com http://www.slideshare.net/bsbodden/ssp-oscon

×