SlideShare a Scribd company logo
1 of 84
Download to read offline
WHAT just happened?
Reacting to Events on the Server
Administrative Notes
• @pilif on twitter
• pilif on github
• working at Sensational AG
• @pilif on twitter
• pilif on github
• working at Sensational AG
• strongly dislike shirts
"💩".length() is still 2
Though there is a lot of good news with ES6
There will be some ☕
Demo first™
Server-Side Events
• Inform users about stuff happening while they are
using the site
• Edits made to the current resource by other people
• Chat Messages
• Mobile Devices interacting with the Account
Additional Constraints
• Must not lose events
• Events must be unique
• Must work with shared sessions
• Separate channels per user
• Must work* even when hand-written daemons are down
• Must work* in development without massaging daemons
Not losing events
• Race condition between event happening and
infrastructure coming up on page load
• Need to persist events
• Using a database
• Using a sequence (auto increment ID) to identify last
sent event
• Falling back to timestamps if not available (initial page
load)
But back to the topic
TIMTWWTDI
*
• Short Polling
• Long Polling
• EventSource
• Web Sockets
* yes. I’m that old
Short Polling
• Are we there yet?
• Are we there yet?
• Are we there yet?
• And now?
Long Polling
• Send a Query to the Server
• Have the server only* reply when an event is available
• Keep the connection open otherwise
• Response means: event has happened
• Have the client reconnect immediately
Server-Sent Events
• http://www.w3.org/TR/eventsource/
• Keeping a connection open to the server
• Server is sending data as text/event-stream
• Colon-separated key-value data.
• Empty line separates events.
WebSockets
• «TCP/IP in your browser»*
• Full Duplex
• Message passing
• Persistent connection between Browser and Server
Let’s try them
Surprise Demo™also Demo first™
index.html
<script>
(function(){
var channel = new EventChannel();
var log_ws = $('#websockets');
$(channel.bind('cheese_created', function(e){
log_ws.prepend($(‘<li>').text(
e.pieces + ' pieces of ' + e.cheese_type
));
});
})();
</script>
index.html
<script>
(function(){
var channel = new EventChannel();
var log_ws = $('#websockets');
$(channel.bind('cheese_created', function(e){
log_ws.prepend($(‘<li>').text(
e.pieces + ' pieces of ' + e.cheese_type
));
});
})();
</script>
index.html
<script>
(function(){
var channel = new EventChannel();
var log_ws = $('#websockets');
$(channel.bind('cheese_created', function(e){
log_ws.prepend($(‘<li>').text(
e.pieces + ' pieces of ' + e.cheese_type
));
});
})();
</script>
index.html
<script>
(function(){
var channel = new EventChannel();
var log_ws = $('#websockets');
$(channel.bind('cheese_created', function(e){
log_ws.prepend($(‘<li>').text(
e.pieces + ' pieces of ' + e.cheese_type
));
});
})();
</script>
publish.js
• Creates between 5 and 120
pieces of random Swiss cheese
• Publishes an event about this
• We’re using redis as our Pub/Sub
mechanism, but you could use
other solutions too
• Sorry for the indentation, but
code had to fit the slide
var cheese_types = ['Emmentaler',
'Appenzeller', 'Gruyère',
'Vacherin', ‘Sprinz'
];
function create_cheese(){
return {
pieces: Math.floor(Math.random()
* 115) + 5,
cheese_type:
cheese_types[Math.floor(
Math.random()
*cheese_types.length
)]
}
}
var cheese_delivery =
create_cheese();
publish(cheese_delivery);
Web Sockets
Server
• Do not try this at home
• Use a library.You might know of socket.io – Me
personally, I used ws.
• Our code: only 32 lines.
This is it
var WebSocketServer = require('ws').Server;
var redis = require('redis');
var wss = new WebSocketServer({port: 8080});
wss.on('connection', function(ws) {
var client = redis.createClient(6379, 'localhost');
ws.on('close', function(){
client.end();
});
client.select(2, function(err, result){
if (err) {
console.log("Failed to set redis database");
return;
}
client.subscribe('channels:cheese');
client.on('message', function(chn, message){
ws.send(message);
});
})
});
Actually, this is the meat
client.subscribe('channels:cheese');
client.on('message', function(chn, message){
ws.send(message);
});
And here’s the client
(function(window){
window.EventChannelWs = function(){
var socket = new WebSocket("ws://localhost:8080/");
var self = this;
socket.onmessage = function(evt){
var event_info = JSON.parse(evt.data);
var evt = jQuery.Event(event_info.type, event_info.data);
$(self).trigger(evt);
}
}
})(window);
And here’s the client
(function(window){
window.EventChannelWs = function(){
var socket = new WebSocket("ws://localhost:8080/");
var self = this;
socket.onmessage = function(evt){
var event_info = JSON.parse(evt.data);
var evt = jQuery.Event(event_info.type, event_info.data);
$(self).trigger(evt);
}
}
})(window);
And here’s the client
(function(window){
window.EventChannelWs = function(){
var socket = new WebSocket("ws://localhost:8080/");
var self = this;
socket.onmessage = function(evt){
var event_info = JSON.parse(evt.data);
var evt = jQuery.Event(event_info.type, event_info.data);
$(self).trigger(evt);
}
}
})(window);
And here’s the client
(function(window){
window.EventChannelWs = function(){
var socket = new WebSocket("ws://localhost:8080/");
var self = this;
socket.onmessage = function(evt){
var event_info = JSON.parse(evt.data);
var evt = jQuery.Event(event_info.type, event_info.data);
$(self).trigger(evt);
}
}
})(window);
Sample was very simple
• No synchronisation with server for initial event
• No fallback when the web socket server is down
• No reverse proxy involved
• No channel separation
Flip-Side
PoweringYour 39 Lines
• 6K lines of JavaScript code
• Plus 3.3K lines of C code
• Plus 508 lines of C++ code
• Which is the body that you actually run (excluding
tests and benchmarks)
• Some of which redundant because NPM
WebSockets are a bloody mess™
• RFC6455 is 71 pages long
• Adding a lot of bit twiddling to intentionally break
proxy servers
• Proxies that work might only actually work*
• Many deployments require a special port to run over
Use SSL.
By the Gods. Use SSL
EventSource
Client
var cheese_channel = new EventSource(url);
var log_source = $('#eventsource');
cheese_channel.addEventListener('cheese_created', function(e){
var data = JSON.parse(e.data);
log_source.prepend($(‘<li>').text(
data.pieces + ' pieces of ' + data.cheese_type
));
});
Client
var cheese_channel = new EventSource(url);
var log_source = $('#eventsource');
cheese_channel.addEventListener('cheese_created', function(e){
var data = JSON.parse(e.data);
log_source.prepend($(‘<li>').text(
data.pieces + ' pieces of ' + data.cheese_type
));
});
Client
var cheese_channel = new EventSource(url);
var log_source = $('#eventsource');
cheese_channel.addEventListener('cheese_created', function(e){
var data = JSON.parse(e.data);
log_source.prepend($(‘<li>').text(
data.pieces + ' pieces of ' + data.cheese_type
));
});
Client
var cheese_channel = new EventSource(url);
var log_source = $('#eventsource');
cheese_channel.addEventListener('cheese_created', function(e){
var data = JSON.parse(e.data);
log_source.prepend($(‘<li>').text(
data.pieces + ' pieces of ' + data.cheese_type
));
});
Server
• Keeps the connection open
• Sends blank-line separated groups of key/value pairs as events happen
• Can tell the client how long to wait when reconnecting
Disillusioning
• Bound to the 6-connections per host rule
• Still needs manual synchronising if you don’t want to lose events
• Browser support is as always
Disillusioning
• Bound to the 6-connections per host rule
• Still needs manual synchronising if you don’t want to lose events
• Browser support is as always
Long Polling
I like it
• Works even with IE6 (god forbid you have to do this)
• Works fine with proxies
• On both ends
• Works fine over HTTP
• Needs some help due to the connection limit
• Works even when your infrastructure is down*
Production code
• The following code samples form the basis of the
initial demo
• It’s production code
• No support issue caused by this.
• Runs fine in a developer-hostile environment
Caution: ☕ ahead
Synchronising using the
database
events_since_id = (channel, id, cb)->
q = """
select * from events
where channel_id = $1 and id > $2
order by id asc
"""
query q, [channel, id], cb
events_since_time = (channel, ts, cb)->
q = """
select * from events o
where channel_id = $1
and ts > (SELECT TIMESTAMP WITH TIME ZONE 'epoch'
+ $2 * INTERVAL '1 second’
)
order by id asc
"""
query q, [channel, ts], cb
The meat
handle_subscription = (c, message)->
fetch_events channel, last_event_id, (err, evts)->
return http_error 500, 'Failed to get event data' if err
abort_processing = write res, evts, true
last_event_id = evts[evts.length-1].id if (evts and evts.length > 0)
if abort_processing
unsubscribe channel, handle_subscription
clear_waiting()
res.end()
fetch_events channel, last_event_id, (err, evts)->
return http_error res, 500, 'Failed to get event data: ' + err if err
last_event_id = evts[evts.length-1].id if (evts and evts.length > 0)
if waiting() or (evts and evts.length > 0)
abort_processing = write(res, evts, not waiting());
if waiting() or abort_processing
unsubscribe channel, handle_subscription
res.end()
set_waiting()
subscribe channel, handle_subscription
if waiting() or (evts and evts.length > 0)
abort_processing = write(res, evts, not waiting());
if waiting() or abort_processing
unsubscribe channel, handle_subscription
res.end()
• If events are pending
• Or if there’s already a connection waiting for the same
channel
• Then return the event data immediately
• And tell the client when to reconnect
• The abort_processing mess is because of support for
both EventSource and long-polling
if waiting() or (evts and evts.length > 0)
abort_processing = write(res, evts, not waiting());
if waiting() or abort_processing
unsubscribe channel, handle_subscription
res.end()
• If events are pending
• Or if there’s already a connection waiting for the same
channel
• Then return the event data immediately
• And tell the client when to reconnect
• The abort_processing mess is because of support for
both EventSource and long-polling
if waiting() or (evts and evts.length > 0)
abort_processing = write(res, evts, not waiting());
if waiting() or abort_processing
unsubscribe channel, handle_subscription
res.end()
• If events are pending
• Or if there’s already a connection waiting for the same
channel
• Then return the event data immediately
• And tell the client when to reconnect
• The abort_processing mess is because of support for
both EventSource and long-polling
if waiting() or (evts and evts.length > 0)
abort_processing = write(res, evts, not waiting());
if waiting() or abort_processing
unsubscribe channel, handle_subscription
res.end()
• If events are pending
• Or if there’s already a connection waiting for the same
channel
• Then return the event data immediately
• And tell the client when to reconnect
• The abort_processing mess is because of support for
both EventSource and long-polling
if waiting() or (evts and evts.length > 0)
abort_processing = write(res, evts, not waiting());
if waiting() or abort_processing
unsubscribe channel, handle_subscription
res.end()
• If events are pending
• Or if there’s already a connection waiting for the same
channel
• Then return the event data immediately
• And tell the client when to reconnect
• The abort_processing mess is because of support for
both EventSource and long-polling
handle_subscription = (c, message)->
fetch_events channel, last_event_id, (err, evts)->
return http_error 500, 'Failed to get event data' if err
abort_processing = write res, evts, true
last_event_id = evts[evts.length-1].id if (evts and evts.length > 0)
if abort_processing
unsubscribe channel, handle_subscription
clear_waiting()
res.end()
set_waiting()
subscribe channel, handle_subscription
Waiting
handle_subscription = (c, message)->
fetch_events channel, last_event_id, (err, evts)->
return http_error 500, 'Failed to get event data' if err
abort_processing = write res, evts, true
last_event_id = evts[evts.length-1].id if (evts and evts.length > 0)
if abort_processing
unsubscribe channel, handle_subscription
clear_waiting()
res.end()
set_waiting()
subscribe channel, handle_subscription
Waiting
handle_subscription = (c, message)->
fetch_events channel, last_event_id, (err, evts)->
return http_error 500, 'Failed to get event data' if err
abort_processing = write res, evts, true
last_event_id = evts[evts.length-1].id if (evts and evts.length > 0)
if abort_processing
unsubscribe channel, handle_subscription
clear_waiting()
res.end()
set_waiting()
subscribe channel, handle_subscription
Waiting
handle_subscription = (c, message)->
fetch_events channel, last_event_id, (err, evts)->
return http_error 500, 'Failed to get event data' if err
abort_processing = write res, evts, true
last_event_id = evts[evts.length-1].id if (evts and evts.length > 0)
if abort_processing
unsubscribe channel, handle_subscription
clear_waiting()
res.end()
set_waiting()
subscribe channel, handle_subscription
Waiting
handle_subscription = (c, message)->
fetch_events channel, last_event_id, (err, evts)->
return http_error 500, 'Failed to get event data' if err
abort_processing = write res, evts, true
last_event_id = evts[evts.length-1].id if (evts and evts.length > 0)
if abort_processing
unsubscribe channel, handle_subscription
clear_waiting()
res.end()
set_waiting()
subscribe channel, handle_subscription
Waiting
LOL - Boolean
parameter!!!
handle_subscription = (c, message)->
fetch_events channel, last_event_id, (err, evts)->
return http_error 500, 'Failed to get event data' if err
abort_processing = write res, evts, true
last_event_id = evts[evts.length-1].id if (evts and evts.length > 0)
if abort_processing
unsubscribe channel, handle_subscription
clear_waiting()
res.end()
set_waiting()
subscribe channel, handle_subscription
Waiting
Fallback
• Our fronted code connects to /e.php
• Our reverse proxy redirects that to the node
daemon
• If that daemon is down or no reverse proxy is there,
there’s an actual honest-to god /e.php …
• …which follows the exact same interface but is
always* short-polling
Client is more complicated
poll: =>
url = "#{@endpoint}/#{@channel}/#{@wait_id}"
$.ajax url,
cache: false,
dataType: 'json',
headers:
'Last-Event-Id': @last_event_id
success: (data, s, xhr) =>
return unless @enabled
@fireAll data
reconnect_in = parseInt xhr.getResponseHeader('x-ps-reconnect-in'), 10
reconnect_in = 10 unless reconnect_in >= 0
setTimeout @poll, reconnect_in*1000 if @enabled
error: (xhr, textStatus, error) =>
return unless @enabled
# 504 means nginx gave up waiting. This is totally to be
# expected and we can just treat it as an invitation to
# reconnect immediately. All other cases are likely bad, so
# we remove a bit of load by waiting a really long time
# 12002 is the ie proprietary way to report an WinInet timeout
# if it was registry-hacked to a low ReadTimeout.
# This isn't a server-error, so we can just reconnect.
rc = if (xhr.status in [504, 12002]) || (textStatus == 'timeout') then 0 else 10000
setTimeout @poll, rc if @enabled
Client is more complicated
poll: =>
url = "#{@endpoint}/#{@channel}/#{@wait_id}"
$.ajax url,
cache: false,
dataType: 'json',
headers:
'Last-Event-Id': @last_event_id
success: (data, s, xhr) =>
return unless @enabled
@fireAll data
reconnect_in = parseInt xhr.getResponseHeader('x-ps-reconnect-in'), 10
reconnect_in = 10 unless reconnect_in >= 0
setTimeout @poll, reconnect_in*1000 if @enabled
error: (xhr, textStatus, error) =>
return unless @enabled
# 504 means nginx gave up waiting. This is totally to be
# expected and we can just treat it as an invitation to
# reconnect immediately. All other cases are likely bad, so
# we remove a bit of load by waiting a really long time
# 12002 is the ie proprietary way to report an WinInet timeout
# if it was registry-hacked to a low ReadTimeout.
# This isn't a server-error, so we can just reconnect.
rc = if (xhr.status in [504, 12002]) || (textStatus == 'timeout') then 0 else 10000
setTimeout @poll, rc if @enabled
Client is more complicated
poll: =>
url = "#{@endpoint}/#{@channel}/#{@wait_id}"
$.ajax url,
cache: false,
dataType: 'json',
headers:
'Last-Event-Id': @last_event_id
success: (data, s, xhr) =>
return unless @enabled
@fireAll data
reconnect_in = parseInt xhr.getResponseHeader('x-ps-reconnect-in'), 10
reconnect_in = 10 unless reconnect_in >= 0
setTimeout @poll, reconnect_in*1000 if @enabled
error: (xhr, textStatus, error) =>
return unless @enabled
# 504 means nginx gave up waiting. This is totally to be
# expected and we can just treat it as an invitation to
# reconnect immediately. All other cases are likely bad, so
# we remove a bit of load by waiting a really long time
# 12002 is the ie proprietary way to report an WinInet timeout
# if it was registry-hacked to a low ReadTimeout.
# This isn't a server-error, so we can just reconnect.
rc = if (xhr.status in [504, 12002]) || (textStatus == 'timeout') then 0 else 10000
setTimeout @poll, rc if @enabled
Client is more complicated
poll: =>
url = "#{@endpoint}/#{@channel}/#{@wait_id}"
$.ajax url,
cache: false,
dataType: 'json',
headers:
'Last-Event-Id': @last_event_id
success: (data, s, xhr) =>
return unless @enabled
@fireAll data
reconnect_in = parseInt xhr.getResponseHeader('x-ps-reconnect-in'), 10
reconnect_in = 10 unless reconnect_in >= 0
setTimeout @poll, reconnect_in*1000 if @enabled
error: (xhr, textStatus, error) =>
return unless @enabled
# 504 means nginx gave up waiting. This is totally to be
# expected and we can just treat it as an invitation to
# reconnect immediately. All other cases are likely bad, so
# we remove a bit of load by waiting a really long time
# 12002 is the ie proprietary way to report an WinInet timeout
# if it was registry-hacked to a low ReadTimeout.
# This isn't a server-error, so we can just reconnect.
rc = if (xhr.status in [504, 12002]) || (textStatus == 'timeout') then 0 else 10000
setTimeout @poll, rc if @enabled
Client is more complicated
poll: =>
url = "#{@endpoint}/#{@channel}/#{@wait_id}"
$.ajax url,
cache: false,
dataType: 'json',
headers:
'Last-Event-Id': @last_event_id
success: (data, s, xhr) =>
return unless @enabled
@fireAll data
reconnect_in = parseInt xhr.getResponseHeader('x-ps-reconnect-in'), 10
reconnect_in = 10 unless reconnect_in >= 0
setTimeout @poll, reconnect_in*1000 if @enabled
error: (xhr, textStatus, error) =>
return unless @enabled
# 504 means nginx gave up waiting. This is totally to be
# expected and we can just treat it as an invitation to
# reconnect immediately. All other cases are likely bad, so
# we remove a bit of load by waiting a really long time
# 12002 is the ie proprietary way to report an WinInet timeout
# if it was registry-hacked to a low ReadTimeout.
# This isn't a server-error, so we can just reconnect.
rc = if (xhr.status in [504, 12002]) || (textStatus == 'timeout') then 0 else 10000
setTimeout @poll, rc if @enabled
Client is more complicated
poll: =>
url = "#{@endpoint}/#{@channel}/#{@wait_id}"
$.ajax url,
cache: false,
dataType: 'json',
headers:
'Last-Event-Id': @last_event_id
success: (data, s, xhr) =>
return unless @enabled
@fireAll data
reconnect_in = parseInt xhr.getResponseHeader('x-ps-reconnect-in'), 10
reconnect_in = 10 unless reconnect_in >= 0
setTimeout @poll, reconnect_in*1000 if @enabled
error: (xhr, textStatus, error) =>
return unless @enabled
# 504 means nginx gave up waiting. This is totally to be
# expected and we can just treat it as an invitation to
# reconnect immediately. All other cases are likely bad, so
# we remove a bit of load by waiting a really long time
# 12002 is the ie proprietary way to report an WinInet timeout
# if it was registry-hacked to a low ReadTimeout.
# This isn't a server-error, so we can just reconnect.
rc = if (xhr.status in [504, 12002]) || (textStatus == 'timeout') then 0 else 10000
setTimeout @poll, rc if @enabled
So.Why a daemon?
• Evented architecture lends itself well to many open
connections never really using CPU
• You do not want to long-poll with forking
architectures
• Unless you have unlimited RAM
In conclusionalso quite different from what I initially meant to say
https://twitter.com/pilif/status/491943226258239490
And that was last
wednesday
So…
• If your clients use browsers (and IE10+)
• If your clients use browsers (and IE10+)
• and if you have a good reverse proxy
• If your clients use browsers (and IE10+)
• and if you have a good reverse proxy
• and if you can use SSL
• If your clients use browsers (and IE10+)
• and if you have a good reverse proxy
• and if you can use SSL
• then use WebSockets
• If your clients use browsers (and IE10+)
• and if you have a good reverse proxy
• and if you can use SSL
• then use WebSockets
• Otherwise use long polling
• If your clients use browsers (and IE10+)
• and if you have a good reverse proxy
• and if you can use SSL
• then use WebSockets
• Otherwise use long polling
• Also, only use one - don’t mix - not worth the effort
• If your clients use browsers (and IE10+)
• and if you have a good reverse proxy
• and if you can use SSL
• then use WebSockets
• Otherwise use long polling
• Also, only use one - don’t mix - not worth the effort
• EventSource, frankly, sucks
Thank you!
• @pilif on twitter
• https://github.com/pilif/server-side-events

Also: We are looking for a front-end designer with CSS
skills and a backend developer. If you are interested or
know somebody, come to me

More Related Content

What's hot

Async. and Realtime Geo Applications with Node.js
Async. and Realtime Geo Applications with Node.jsAsync. and Realtime Geo Applications with Node.js
Async. and Realtime Geo Applications with Node.js
Shoaib Burq
 
JavaScript APIs - The Web is the Platform - .toster conference, Moscow
JavaScript APIs - The Web is the Platform - .toster conference, MoscowJavaScript APIs - The Web is the Platform - .toster conference, Moscow
JavaScript APIs - The Web is the Platform - .toster conference, Moscow
Robert Nyman
 
JavaScript APIs - The Web is the Platform - MDN Hack Day, Montevideo
JavaScript APIs - The Web is the Platform - MDN Hack Day, MontevideoJavaScript APIs - The Web is the Platform - MDN Hack Day, Montevideo
JavaScript APIs - The Web is the Platform - MDN Hack Day, Montevideo
Robert Nyman
 
JavaScript APIs - The Web is the Platform - MDN Hack Day, Santiago, Chile
JavaScript APIs - The Web is the Platform - MDN Hack Day, Santiago, ChileJavaScript APIs - The Web is the Platform - MDN Hack Day, Santiago, Chile
JavaScript APIs - The Web is the Platform - MDN Hack Day, Santiago, Chile
Robert Nyman
 
JavaScript APIs - The Web is the Platform - MozCamp, Buenos Aires
JavaScript APIs - The Web is the Platform - MozCamp, Buenos AiresJavaScript APIs - The Web is the Platform - MozCamp, Buenos Aires
JavaScript APIs - The Web is the Platform - MozCamp, Buenos Aires
Robert Nyman
 
Doctype htm1
Doctype htm1Doctype htm1
Doctype htm1
Eddy_TKJ
 

What's hot (20)

Async. and Realtime Geo Applications with Node.js
Async. and Realtime Geo Applications with Node.jsAsync. and Realtime Geo Applications with Node.js
Async. and Realtime Geo Applications with Node.js
 
Exch test-mail flow
Exch test-mail flowExch test-mail flow
Exch test-mail flow
 
History of jQuery
History of jQueryHistory of jQuery
History of jQuery
 
Kamil Chmielewski, Jacek Juraszek - "Hadoop. W poszukiwaniu złotego młotka."
Kamil Chmielewski, Jacek Juraszek - "Hadoop. W poszukiwaniu złotego młotka."Kamil Chmielewski, Jacek Juraszek - "Hadoop. W poszukiwaniu złotego młotka."
Kamil Chmielewski, Jacek Juraszek - "Hadoop. W poszukiwaniu złotego młotka."
 
JQuery
JQueryJQuery
JQuery
 
JavaScript APIs - The Web is the Platform - .toster conference, Moscow
JavaScript APIs - The Web is the Platform - .toster conference, MoscowJavaScript APIs - The Web is the Platform - .toster conference, Moscow
JavaScript APIs - The Web is the Platform - .toster conference, Moscow
 
Dpilot Source Code With ScreenShots
Dpilot Source Code With ScreenShots Dpilot Source Code With ScreenShots
Dpilot Source Code With ScreenShots
 
Source Code for Dpilot
Source Code for Dpilot Source Code for Dpilot
Source Code for Dpilot
 
Cnam azure 2014 mobile services
Cnam azure 2014   mobile servicesCnam azure 2014   mobile services
Cnam azure 2014 mobile services
 
Designing The Right Schema To Power Heap (PGConf Silicon Valley 2016)
Designing The Right Schema To Power Heap (PGConf Silicon Valley 2016)Designing The Right Schema To Power Heap (PGConf Silicon Valley 2016)
Designing The Right Schema To Power Heap (PGConf Silicon Valley 2016)
 
async/await in Swift
async/await in Swiftasync/await in Swift
async/await in Swift
 
JavaScript APIs - The Web is the Platform - MDN Hack Day, Montevideo
JavaScript APIs - The Web is the Platform - MDN Hack Day, MontevideoJavaScript APIs - The Web is the Platform - MDN Hack Day, Montevideo
JavaScript APIs - The Web is the Platform - MDN Hack Day, Montevideo
 
JavaScript APIs - The Web is the Platform - MDN Hack Day, Santiago, Chile
JavaScript APIs - The Web is the Platform - MDN Hack Day, Santiago, ChileJavaScript APIs - The Web is the Platform - MDN Hack Day, Santiago, Chile
JavaScript APIs - The Web is the Platform - MDN Hack Day, Santiago, Chile
 
JavaScript APIs - The Web is the Platform - MozCamp, Buenos Aires
JavaScript APIs - The Web is the Platform - MozCamp, Buenos AiresJavaScript APIs - The Web is the Platform - MozCamp, Buenos Aires
JavaScript APIs - The Web is the Platform - MozCamp, Buenos Aires
 
The (unknown) collections module
The (unknown) collections moduleThe (unknown) collections module
The (unknown) collections module
 
Doctype htm1
Doctype htm1Doctype htm1
Doctype htm1
 
Get Real: Adventures in realtime web apps
Get Real: Adventures in realtime web appsGet Real: Adventures in realtime web apps
Get Real: Adventures in realtime web apps
 
A evolução da persistência de dados (com sqlite) no android
A evolução da persistência de dados (com sqlite) no androidA evolução da persistência de dados (com sqlite) no android
A evolução da persistência de dados (com sqlite) no android
 
神に近づくx/net/context (Finding God with x/net/context)
神に近づくx/net/context (Finding God with x/net/context)神に近づくx/net/context (Finding God with x/net/context)
神に近づくx/net/context (Finding God with x/net/context)
 
Centralize your Business Logic with Pipelines in Elixir
Centralize your Business Logic with Pipelines in ElixirCentralize your Business Logic with Pipelines in Elixir
Centralize your Business Logic with Pipelines in Elixir
 

Similar to Server Side Events

Websockets talk at Rubyconf Uruguay 2010
Websockets talk at Rubyconf Uruguay 2010Websockets talk at Rubyconf Uruguay 2010
Websockets talk at Rubyconf Uruguay 2010
Ismael Celis
 
Live Streaming & Server Sent Events
Live Streaming & Server Sent EventsLive Streaming & Server Sent Events
Live Streaming & Server Sent Events
tkramar
 
HTML5 - The 2012 of the Web - Adobe MAX
HTML5 - The 2012 of the Web - Adobe MAXHTML5 - The 2012 of the Web - Adobe MAX
HTML5 - The 2012 of the Web - Adobe MAX
Robert Nyman
 
JSON Schema: Your API's Secret Weapon
JSON Schema: Your API's Secret WeaponJSON Schema: Your API's Secret Weapon
JSON Schema: Your API's Secret Weapon
Pete Gamache
 
HTML5 - The 2012 of the Web
HTML5 - The 2012 of the WebHTML5 - The 2012 of the Web
HTML5 - The 2012 of the Web
Robert Nyman
 
Is HTML5 Ready? (workshop)
Is HTML5 Ready? (workshop)Is HTML5 Ready? (workshop)
Is HTML5 Ready? (workshop)
Remy Sharp
 
Is html5-ready-workshop-110727181512-phpapp02
Is html5-ready-workshop-110727181512-phpapp02Is html5-ready-workshop-110727181512-phpapp02
Is html5-ready-workshop-110727181512-phpapp02
PL dream
 
Monitoring with Syslog and EventMachine (RailswayConf 2012)
Monitoring  with  Syslog and EventMachine (RailswayConf 2012)Monitoring  with  Syslog and EventMachine (RailswayConf 2012)
Monitoring with Syslog and EventMachine (RailswayConf 2012)
Wooga
 
A language for the Internet: Why JavaScript and Node.js is right for Internet...
A language for the Internet: Why JavaScript and Node.js is right for Internet...A language for the Internet: Why JavaScript and Node.js is right for Internet...
A language for the Internet: Why JavaScript and Node.js is right for Internet...
Tom Croucher
 
Taking Web Apps Offline
Taking Web Apps OfflineTaking Web Apps Offline
Taking Web Apps Offline
Pedro Morais
 
Engage 2013 - Multi Channel Data Collection
Engage 2013 - Multi Channel Data CollectionEngage 2013 - Multi Channel Data Collection
Engage 2013 - Multi Channel Data Collection
Webtrends
 
The Open Web and what it means
The Open Web and what it meansThe Open Web and what it means
The Open Web and what it means
Robert Nyman
 

Similar to Server Side Events (20)

Websockets talk at Rubyconf Uruguay 2010
Websockets talk at Rubyconf Uruguay 2010Websockets talk at Rubyconf Uruguay 2010
Websockets talk at Rubyconf Uruguay 2010
 
Live Streaming & Server Sent Events
Live Streaming & Server Sent EventsLive Streaming & Server Sent Events
Live Streaming & Server Sent Events
 
Small pieces loosely joined
Small pieces loosely joinedSmall pieces loosely joined
Small pieces loosely joined
 
HTML5 - The 2012 of the Web - Adobe MAX
HTML5 - The 2012 of the Web - Adobe MAXHTML5 - The 2012 of the Web - Adobe MAX
HTML5 - The 2012 of the Web - Adobe MAX
 
JS Fest 2018. Martin Chaov. SSE vs WebSockets vs Long Polling
JS Fest 2018. Martin Chaov. SSE vs WebSockets vs Long PollingJS Fest 2018. Martin Chaov. SSE vs WebSockets vs Long Polling
JS Fest 2018. Martin Chaov. SSE vs WebSockets vs Long Polling
 
JSON Schema: Your API's Secret Weapon
JSON Schema: Your API's Secret WeaponJSON Schema: Your API's Secret Weapon
JSON Schema: Your API's Secret Weapon
 
HTML5 - The 2012 of the Web
HTML5 - The 2012 of the WebHTML5 - The 2012 of the Web
HTML5 - The 2012 of the Web
 
Is HTML5 Ready? (workshop)
Is HTML5 Ready? (workshop)Is HTML5 Ready? (workshop)
Is HTML5 Ready? (workshop)
 
Is html5-ready-workshop-110727181512-phpapp02
Is html5-ready-workshop-110727181512-phpapp02Is html5-ready-workshop-110727181512-phpapp02
Is html5-ready-workshop-110727181512-phpapp02
 
[JCConf 2020] 用 Kotlin 跨入 Serverless 世代
[JCConf 2020] 用 Kotlin 跨入 Serverless 世代[JCConf 2020] 用 Kotlin 跨入 Serverless 世代
[JCConf 2020] 用 Kotlin 跨入 Serverless 世代
 
Monitoring with Syslog and EventMachine (RailswayConf 2012)
Monitoring  with  Syslog and EventMachine (RailswayConf 2012)Monitoring  with  Syslog and EventMachine (RailswayConf 2012)
Monitoring with Syslog and EventMachine (RailswayConf 2012)
 
Arduino and the real time web
Arduino and the real time webArduino and the real time web
Arduino and the real time web
 
Streaming Data with scalaz-stream
Streaming Data with scalaz-streamStreaming Data with scalaz-stream
Streaming Data with scalaz-stream
 
Monitoring Your ISP Using InfluxDB Cloud and Raspberry Pi
Monitoring Your ISP Using InfluxDB Cloud and Raspberry PiMonitoring Your ISP Using InfluxDB Cloud and Raspberry Pi
Monitoring Your ISP Using InfluxDB Cloud and Raspberry Pi
 
A language for the Internet: Why JavaScript and Node.js is right for Internet...
A language for the Internet: Why JavaScript and Node.js is right for Internet...A language for the Internet: Why JavaScript and Node.js is right for Internet...
A language for the Internet: Why JavaScript and Node.js is right for Internet...
 
Taking Web Apps Offline
Taking Web Apps OfflineTaking Web Apps Offline
Taking Web Apps Offline
 
Bare-knuckle web development
Bare-knuckle web developmentBare-knuckle web development
Bare-knuckle web development
 
Engage 2013 - Multi Channel Data Collection
Engage 2013 - Multi Channel Data CollectionEngage 2013 - Multi Channel Data Collection
Engage 2013 - Multi Channel Data Collection
 
The Open Web and what it means
The Open Web and what it meansThe Open Web and what it means
The Open Web and what it means
 
Html server sent events
Html server sent eventsHtml server sent events
Html server sent events
 

Recently uploaded

AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM TechniquesAI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
VictorSzoltysek
 
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
masabamasaba
 
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdfintroduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
VishalKumarJha10
 

Recently uploaded (20)

Define the academic and professional writing..pdf
Define the academic and professional writing..pdfDefine the academic and professional writing..pdf
Define the academic and professional writing..pdf
 
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
Direct Style Effect Systems -The Print[A] Example- A Comprehension AidDirect Style Effect Systems -The Print[A] Example- A Comprehension Aid
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
 
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
 
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview Questions
 
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
 
Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdfPayment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
Payment Gateway Testing Simplified_ A Step-by-Step Guide for Beginners.pdf
 
AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM TechniquesAI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
 
SHRMPro HRMS Software Solutions Presentation
SHRMPro HRMS Software Solutions PresentationSHRMPro HRMS Software Solutions Presentation
SHRMPro HRMS Software Solutions Presentation
 
Chinsurah Escorts ☎️8617697112 Starting From 5K to 15K High Profile Escorts ...
Chinsurah Escorts ☎️8617697112  Starting From 5K to 15K High Profile Escorts ...Chinsurah Escorts ☎️8617697112  Starting From 5K to 15K High Profile Escorts ...
Chinsurah Escorts ☎️8617697112 Starting From 5K to 15K High Profile Escorts ...
 
Introducing Microsoft’s new Enterprise Work Management (EWM) Solution
Introducing Microsoft’s new Enterprise Work Management (EWM) SolutionIntroducing Microsoft’s new Enterprise Work Management (EWM) Solution
Introducing Microsoft’s new Enterprise Work Management (EWM) Solution
 
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
 
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
 
%in kempton park+277-882-255-28 abortion pills for sale in kempton park
%in kempton park+277-882-255-28 abortion pills for sale in kempton park %in kempton park+277-882-255-28 abortion pills for sale in kempton park
%in kempton park+277-882-255-28 abortion pills for sale in kempton park
 
%in Harare+277-882-255-28 abortion pills for sale in Harare
%in Harare+277-882-255-28 abortion pills for sale in Harare%in Harare+277-882-255-28 abortion pills for sale in Harare
%in Harare+277-882-255-28 abortion pills for sale in Harare
 
VTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learnVTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learn
 
%+27788225528 love spells in Vancouver Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Vancouver Psychic Readings, Attraction spells,Br...%+27788225528 love spells in Vancouver Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Vancouver Psychic Readings, Attraction spells,Br...
 
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
 
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdfintroduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
introduction-to-automotive Andoid os-csimmonds-ndctechtown-2021.pdf
 
%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand
 

Server Side Events

  • 1. WHAT just happened? Reacting to Events on the Server
  • 3. • @pilif on twitter • pilif on github • working at Sensational AG
  • 4. • @pilif on twitter • pilif on github • working at Sensational AG • strongly dislike shirts
  • 5. "💩".length() is still 2 Though there is a lot of good news with ES6
  • 6. There will be some ☕
  • 8. Server-Side Events • Inform users about stuff happening while they are using the site • Edits made to the current resource by other people • Chat Messages • Mobile Devices interacting with the Account
  • 9. Additional Constraints • Must not lose events • Events must be unique • Must work with shared sessions • Separate channels per user • Must work* even when hand-written daemons are down • Must work* in development without massaging daemons
  • 10. Not losing events • Race condition between event happening and infrastructure coming up on page load • Need to persist events • Using a database • Using a sequence (auto increment ID) to identify last sent event • Falling back to timestamps if not available (initial page load)
  • 11. But back to the topic
  • 12. TIMTWWTDI * • Short Polling • Long Polling • EventSource • Web Sockets * yes. I’m that old
  • 13. Short Polling • Are we there yet? • Are we there yet? • Are we there yet? • And now?
  • 14. Long Polling • Send a Query to the Server • Have the server only* reply when an event is available • Keep the connection open otherwise • Response means: event has happened • Have the client reconnect immediately
  • 15. Server-Sent Events • http://www.w3.org/TR/eventsource/ • Keeping a connection open to the server • Server is sending data as text/event-stream • Colon-separated key-value data. • Empty line separates events.
  • 16. WebSockets • «TCP/IP in your browser»* • Full Duplex • Message passing • Persistent connection between Browser and Server
  • 19. index.html <script> (function(){ var channel = new EventChannel(); var log_ws = $('#websockets'); $(channel.bind('cheese_created', function(e){ log_ws.prepend($(‘<li>').text( e.pieces + ' pieces of ' + e.cheese_type )); }); })(); </script>
  • 20. index.html <script> (function(){ var channel = new EventChannel(); var log_ws = $('#websockets'); $(channel.bind('cheese_created', function(e){ log_ws.prepend($(‘<li>').text( e.pieces + ' pieces of ' + e.cheese_type )); }); })(); </script>
  • 21. index.html <script> (function(){ var channel = new EventChannel(); var log_ws = $('#websockets'); $(channel.bind('cheese_created', function(e){ log_ws.prepend($(‘<li>').text( e.pieces + ' pieces of ' + e.cheese_type )); }); })(); </script>
  • 22. index.html <script> (function(){ var channel = new EventChannel(); var log_ws = $('#websockets'); $(channel.bind('cheese_created', function(e){ log_ws.prepend($(‘<li>').text( e.pieces + ' pieces of ' + e.cheese_type )); }); })(); </script>
  • 23. publish.js • Creates between 5 and 120 pieces of random Swiss cheese • Publishes an event about this • We’re using redis as our Pub/Sub mechanism, but you could use other solutions too • Sorry for the indentation, but code had to fit the slide var cheese_types = ['Emmentaler', 'Appenzeller', 'Gruyère', 'Vacherin', ‘Sprinz' ]; function create_cheese(){ return { pieces: Math.floor(Math.random() * 115) + 5, cheese_type: cheese_types[Math.floor( Math.random() *cheese_types.length )] } } var cheese_delivery = create_cheese(); publish(cheese_delivery);
  • 25. Server • Do not try this at home • Use a library.You might know of socket.io – Me personally, I used ws. • Our code: only 32 lines.
  • 26. This is it var WebSocketServer = require('ws').Server; var redis = require('redis'); var wss = new WebSocketServer({port: 8080}); wss.on('connection', function(ws) { var client = redis.createClient(6379, 'localhost'); ws.on('close', function(){ client.end(); }); client.select(2, function(err, result){ if (err) { console.log("Failed to set redis database"); return; } client.subscribe('channels:cheese'); client.on('message', function(chn, message){ ws.send(message); }); }) });
  • 27. Actually, this is the meat client.subscribe('channels:cheese'); client.on('message', function(chn, message){ ws.send(message); });
  • 28. And here’s the client (function(window){ window.EventChannelWs = function(){ var socket = new WebSocket("ws://localhost:8080/"); var self = this; socket.onmessage = function(evt){ var event_info = JSON.parse(evt.data); var evt = jQuery.Event(event_info.type, event_info.data); $(self).trigger(evt); } } })(window);
  • 29. And here’s the client (function(window){ window.EventChannelWs = function(){ var socket = new WebSocket("ws://localhost:8080/"); var self = this; socket.onmessage = function(evt){ var event_info = JSON.parse(evt.data); var evt = jQuery.Event(event_info.type, event_info.data); $(self).trigger(evt); } } })(window);
  • 30. And here’s the client (function(window){ window.EventChannelWs = function(){ var socket = new WebSocket("ws://localhost:8080/"); var self = this; socket.onmessage = function(evt){ var event_info = JSON.parse(evt.data); var evt = jQuery.Event(event_info.type, event_info.data); $(self).trigger(evt); } } })(window);
  • 31. And here’s the client (function(window){ window.EventChannelWs = function(){ var socket = new WebSocket("ws://localhost:8080/"); var self = this; socket.onmessage = function(evt){ var event_info = JSON.parse(evt.data); var evt = jQuery.Event(event_info.type, event_info.data); $(self).trigger(evt); } } })(window);
  • 32. Sample was very simple • No synchronisation with server for initial event • No fallback when the web socket server is down • No reverse proxy involved • No channel separation
  • 34.
  • 35. PoweringYour 39 Lines • 6K lines of JavaScript code • Plus 3.3K lines of C code • Plus 508 lines of C++ code • Which is the body that you actually run (excluding tests and benchmarks) • Some of which redundant because NPM
  • 36. WebSockets are a bloody mess™ • RFC6455 is 71 pages long • Adding a lot of bit twiddling to intentionally break proxy servers • Proxies that work might only actually work* • Many deployments require a special port to run over
  • 37. Use SSL. By the Gods. Use SSL
  • 39. Client var cheese_channel = new EventSource(url); var log_source = $('#eventsource'); cheese_channel.addEventListener('cheese_created', function(e){ var data = JSON.parse(e.data); log_source.prepend($(‘<li>').text( data.pieces + ' pieces of ' + data.cheese_type )); });
  • 40. Client var cheese_channel = new EventSource(url); var log_source = $('#eventsource'); cheese_channel.addEventListener('cheese_created', function(e){ var data = JSON.parse(e.data); log_source.prepend($(‘<li>').text( data.pieces + ' pieces of ' + data.cheese_type )); });
  • 41. Client var cheese_channel = new EventSource(url); var log_source = $('#eventsource'); cheese_channel.addEventListener('cheese_created', function(e){ var data = JSON.parse(e.data); log_source.prepend($(‘<li>').text( data.pieces + ' pieces of ' + data.cheese_type )); });
  • 42. Client var cheese_channel = new EventSource(url); var log_source = $('#eventsource'); cheese_channel.addEventListener('cheese_created', function(e){ var data = JSON.parse(e.data); log_source.prepend($(‘<li>').text( data.pieces + ' pieces of ' + data.cheese_type )); });
  • 43. Server • Keeps the connection open • Sends blank-line separated groups of key/value pairs as events happen • Can tell the client how long to wait when reconnecting
  • 44. Disillusioning • Bound to the 6-connections per host rule • Still needs manual synchronising if you don’t want to lose events • Browser support is as always
  • 45. Disillusioning • Bound to the 6-connections per host rule • Still needs manual synchronising if you don’t want to lose events • Browser support is as always
  • 47. I like it • Works even with IE6 (god forbid you have to do this) • Works fine with proxies • On both ends • Works fine over HTTP • Needs some help due to the connection limit • Works even when your infrastructure is down*
  • 48. Production code • The following code samples form the basis of the initial demo • It’s production code • No support issue caused by this. • Runs fine in a developer-hostile environment
  • 50. Synchronising using the database events_since_id = (channel, id, cb)-> q = """ select * from events where channel_id = $1 and id > $2 order by id asc """ query q, [channel, id], cb events_since_time = (channel, ts, cb)-> q = """ select * from events o where channel_id = $1 and ts > (SELECT TIMESTAMP WITH TIME ZONE 'epoch' + $2 * INTERVAL '1 second’ ) order by id asc """ query q, [channel, ts], cb
  • 51. The meat handle_subscription = (c, message)-> fetch_events channel, last_event_id, (err, evts)-> return http_error 500, 'Failed to get event data' if err abort_processing = write res, evts, true last_event_id = evts[evts.length-1].id if (evts and evts.length > 0) if abort_processing unsubscribe channel, handle_subscription clear_waiting() res.end() fetch_events channel, last_event_id, (err, evts)-> return http_error res, 500, 'Failed to get event data: ' + err if err last_event_id = evts[evts.length-1].id if (evts and evts.length > 0) if waiting() or (evts and evts.length > 0) abort_processing = write(res, evts, not waiting()); if waiting() or abort_processing unsubscribe channel, handle_subscription res.end() set_waiting() subscribe channel, handle_subscription
  • 52. if waiting() or (evts and evts.length > 0) abort_processing = write(res, evts, not waiting()); if waiting() or abort_processing unsubscribe channel, handle_subscription res.end() • If events are pending • Or if there’s already a connection waiting for the same channel • Then return the event data immediately • And tell the client when to reconnect • The abort_processing mess is because of support for both EventSource and long-polling
  • 53. if waiting() or (evts and evts.length > 0) abort_processing = write(res, evts, not waiting()); if waiting() or abort_processing unsubscribe channel, handle_subscription res.end() • If events are pending • Or if there’s already a connection waiting for the same channel • Then return the event data immediately • And tell the client when to reconnect • The abort_processing mess is because of support for both EventSource and long-polling
  • 54. if waiting() or (evts and evts.length > 0) abort_processing = write(res, evts, not waiting()); if waiting() or abort_processing unsubscribe channel, handle_subscription res.end() • If events are pending • Or if there’s already a connection waiting for the same channel • Then return the event data immediately • And tell the client when to reconnect • The abort_processing mess is because of support for both EventSource and long-polling
  • 55. if waiting() or (evts and evts.length > 0) abort_processing = write(res, evts, not waiting()); if waiting() or abort_processing unsubscribe channel, handle_subscription res.end() • If events are pending • Or if there’s already a connection waiting for the same channel • Then return the event data immediately • And tell the client when to reconnect • The abort_processing mess is because of support for both EventSource and long-polling
  • 56. if waiting() or (evts and evts.length > 0) abort_processing = write(res, evts, not waiting()); if waiting() or abort_processing unsubscribe channel, handle_subscription res.end() • If events are pending • Or if there’s already a connection waiting for the same channel • Then return the event data immediately • And tell the client when to reconnect • The abort_processing mess is because of support for both EventSource and long-polling
  • 57. handle_subscription = (c, message)-> fetch_events channel, last_event_id, (err, evts)-> return http_error 500, 'Failed to get event data' if err abort_processing = write res, evts, true last_event_id = evts[evts.length-1].id if (evts and evts.length > 0) if abort_processing unsubscribe channel, handle_subscription clear_waiting() res.end() set_waiting() subscribe channel, handle_subscription Waiting
  • 58. handle_subscription = (c, message)-> fetch_events channel, last_event_id, (err, evts)-> return http_error 500, 'Failed to get event data' if err abort_processing = write res, evts, true last_event_id = evts[evts.length-1].id if (evts and evts.length > 0) if abort_processing unsubscribe channel, handle_subscription clear_waiting() res.end() set_waiting() subscribe channel, handle_subscription Waiting
  • 59. handle_subscription = (c, message)-> fetch_events channel, last_event_id, (err, evts)-> return http_error 500, 'Failed to get event data' if err abort_processing = write res, evts, true last_event_id = evts[evts.length-1].id if (evts and evts.length > 0) if abort_processing unsubscribe channel, handle_subscription clear_waiting() res.end() set_waiting() subscribe channel, handle_subscription Waiting
  • 60. handle_subscription = (c, message)-> fetch_events channel, last_event_id, (err, evts)-> return http_error 500, 'Failed to get event data' if err abort_processing = write res, evts, true last_event_id = evts[evts.length-1].id if (evts and evts.length > 0) if abort_processing unsubscribe channel, handle_subscription clear_waiting() res.end() set_waiting() subscribe channel, handle_subscription Waiting
  • 61. handle_subscription = (c, message)-> fetch_events channel, last_event_id, (err, evts)-> return http_error 500, 'Failed to get event data' if err abort_processing = write res, evts, true last_event_id = evts[evts.length-1].id if (evts and evts.length > 0) if abort_processing unsubscribe channel, handle_subscription clear_waiting() res.end() set_waiting() subscribe channel, handle_subscription Waiting LOL - Boolean parameter!!!
  • 62. handle_subscription = (c, message)-> fetch_events channel, last_event_id, (err, evts)-> return http_error 500, 'Failed to get event data' if err abort_processing = write res, evts, true last_event_id = evts[evts.length-1].id if (evts and evts.length > 0) if abort_processing unsubscribe channel, handle_subscription clear_waiting() res.end() set_waiting() subscribe channel, handle_subscription Waiting
  • 63. Fallback • Our fronted code connects to /e.php • Our reverse proxy redirects that to the node daemon • If that daemon is down or no reverse proxy is there, there’s an actual honest-to god /e.php … • …which follows the exact same interface but is always* short-polling
  • 64. Client is more complicated poll: => url = "#{@endpoint}/#{@channel}/#{@wait_id}" $.ajax url, cache: false, dataType: 'json', headers: 'Last-Event-Id': @last_event_id success: (data, s, xhr) => return unless @enabled @fireAll data reconnect_in = parseInt xhr.getResponseHeader('x-ps-reconnect-in'), 10 reconnect_in = 10 unless reconnect_in >= 0 setTimeout @poll, reconnect_in*1000 if @enabled error: (xhr, textStatus, error) => return unless @enabled # 504 means nginx gave up waiting. This is totally to be # expected and we can just treat it as an invitation to # reconnect immediately. All other cases are likely bad, so # we remove a bit of load by waiting a really long time # 12002 is the ie proprietary way to report an WinInet timeout # if it was registry-hacked to a low ReadTimeout. # This isn't a server-error, so we can just reconnect. rc = if (xhr.status in [504, 12002]) || (textStatus == 'timeout') then 0 else 10000 setTimeout @poll, rc if @enabled
  • 65. Client is more complicated poll: => url = "#{@endpoint}/#{@channel}/#{@wait_id}" $.ajax url, cache: false, dataType: 'json', headers: 'Last-Event-Id': @last_event_id success: (data, s, xhr) => return unless @enabled @fireAll data reconnect_in = parseInt xhr.getResponseHeader('x-ps-reconnect-in'), 10 reconnect_in = 10 unless reconnect_in >= 0 setTimeout @poll, reconnect_in*1000 if @enabled error: (xhr, textStatus, error) => return unless @enabled # 504 means nginx gave up waiting. This is totally to be # expected and we can just treat it as an invitation to # reconnect immediately. All other cases are likely bad, so # we remove a bit of load by waiting a really long time # 12002 is the ie proprietary way to report an WinInet timeout # if it was registry-hacked to a low ReadTimeout. # This isn't a server-error, so we can just reconnect. rc = if (xhr.status in [504, 12002]) || (textStatus == 'timeout') then 0 else 10000 setTimeout @poll, rc if @enabled
  • 66. Client is more complicated poll: => url = "#{@endpoint}/#{@channel}/#{@wait_id}" $.ajax url, cache: false, dataType: 'json', headers: 'Last-Event-Id': @last_event_id success: (data, s, xhr) => return unless @enabled @fireAll data reconnect_in = parseInt xhr.getResponseHeader('x-ps-reconnect-in'), 10 reconnect_in = 10 unless reconnect_in >= 0 setTimeout @poll, reconnect_in*1000 if @enabled error: (xhr, textStatus, error) => return unless @enabled # 504 means nginx gave up waiting. This is totally to be # expected and we can just treat it as an invitation to # reconnect immediately. All other cases are likely bad, so # we remove a bit of load by waiting a really long time # 12002 is the ie proprietary way to report an WinInet timeout # if it was registry-hacked to a low ReadTimeout. # This isn't a server-error, so we can just reconnect. rc = if (xhr.status in [504, 12002]) || (textStatus == 'timeout') then 0 else 10000 setTimeout @poll, rc if @enabled
  • 67. Client is more complicated poll: => url = "#{@endpoint}/#{@channel}/#{@wait_id}" $.ajax url, cache: false, dataType: 'json', headers: 'Last-Event-Id': @last_event_id success: (data, s, xhr) => return unless @enabled @fireAll data reconnect_in = parseInt xhr.getResponseHeader('x-ps-reconnect-in'), 10 reconnect_in = 10 unless reconnect_in >= 0 setTimeout @poll, reconnect_in*1000 if @enabled error: (xhr, textStatus, error) => return unless @enabled # 504 means nginx gave up waiting. This is totally to be # expected and we can just treat it as an invitation to # reconnect immediately. All other cases are likely bad, so # we remove a bit of load by waiting a really long time # 12002 is the ie proprietary way to report an WinInet timeout # if it was registry-hacked to a low ReadTimeout. # This isn't a server-error, so we can just reconnect. rc = if (xhr.status in [504, 12002]) || (textStatus == 'timeout') then 0 else 10000 setTimeout @poll, rc if @enabled
  • 68. Client is more complicated poll: => url = "#{@endpoint}/#{@channel}/#{@wait_id}" $.ajax url, cache: false, dataType: 'json', headers: 'Last-Event-Id': @last_event_id success: (data, s, xhr) => return unless @enabled @fireAll data reconnect_in = parseInt xhr.getResponseHeader('x-ps-reconnect-in'), 10 reconnect_in = 10 unless reconnect_in >= 0 setTimeout @poll, reconnect_in*1000 if @enabled error: (xhr, textStatus, error) => return unless @enabled # 504 means nginx gave up waiting. This is totally to be # expected and we can just treat it as an invitation to # reconnect immediately. All other cases are likely bad, so # we remove a bit of load by waiting a really long time # 12002 is the ie proprietary way to report an WinInet timeout # if it was registry-hacked to a low ReadTimeout. # This isn't a server-error, so we can just reconnect. rc = if (xhr.status in [504, 12002]) || (textStatus == 'timeout') then 0 else 10000 setTimeout @poll, rc if @enabled
  • 69. Client is more complicated poll: => url = "#{@endpoint}/#{@channel}/#{@wait_id}" $.ajax url, cache: false, dataType: 'json', headers: 'Last-Event-Id': @last_event_id success: (data, s, xhr) => return unless @enabled @fireAll data reconnect_in = parseInt xhr.getResponseHeader('x-ps-reconnect-in'), 10 reconnect_in = 10 unless reconnect_in >= 0 setTimeout @poll, reconnect_in*1000 if @enabled error: (xhr, textStatus, error) => return unless @enabled # 504 means nginx gave up waiting. This is totally to be # expected and we can just treat it as an invitation to # reconnect immediately. All other cases are likely bad, so # we remove a bit of load by waiting a really long time # 12002 is the ie proprietary way to report an WinInet timeout # if it was registry-hacked to a low ReadTimeout. # This isn't a server-error, so we can just reconnect. rc = if (xhr.status in [504, 12002]) || (textStatus == 'timeout') then 0 else 10000 setTimeout @poll, rc if @enabled
  • 70. So.Why a daemon? • Evented architecture lends itself well to many open connections never really using CPU • You do not want to long-poll with forking architectures • Unless you have unlimited RAM
  • 71. In conclusionalso quite different from what I initially meant to say
  • 73. And that was last wednesday
  • 74.
  • 75. So…
  • 76.
  • 77. • If your clients use browsers (and IE10+)
  • 78. • If your clients use browsers (and IE10+) • and if you have a good reverse proxy
  • 79. • If your clients use browsers (and IE10+) • and if you have a good reverse proxy • and if you can use SSL
  • 80. • If your clients use browsers (and IE10+) • and if you have a good reverse proxy • and if you can use SSL • then use WebSockets
  • 81. • If your clients use browsers (and IE10+) • and if you have a good reverse proxy • and if you can use SSL • then use WebSockets • Otherwise use long polling
  • 82. • If your clients use browsers (and IE10+) • and if you have a good reverse proxy • and if you can use SSL • then use WebSockets • Otherwise use long polling • Also, only use one - don’t mix - not worth the effort
  • 83. • If your clients use browsers (and IE10+) • and if you have a good reverse proxy • and if you can use SSL • then use WebSockets • Otherwise use long polling • Also, only use one - don’t mix - not worth the effort • EventSource, frankly, sucks
  • 84. Thank you! • @pilif on twitter • https://github.com/pilif/server-side-events
 Also: We are looking for a front-end designer with CSS skills and a backend developer. If you are interested or know somebody, come to me