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...
Additional Constraints
• Must not lose events
• Events must be unique
• Must work with shared sessions
• Separate channels...
Not losing events
• Race condition between event happening and
infrastructure coming up on page load
• Need to persist eve...
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 o...
Server-Sent Events
• http://www.w3.org/TR/eventsource/
• Keeping a connection open to the server
• Server is sending data ...
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_c...
index.html
<script>
(function(){
var channel = new EventChannel();
var log_ws = $('#websockets');
$(channel.bind('cheese_c...
index.html
<script>
(function(){
var channel = new EventChannel();
var log_ws = $('#websockets');
$(channel.bind('cheese_c...
index.html
<script>
(function(){
var channel = new EventChannel();
var log_ws = $('#websockets');
$(channel.bind('cheese_c...
publish.js
• Creates between 5 and 120
pieces of random Swiss cheese
• Publishes an event about this
• We’re using redis a...
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 ...
This is it
var WebSocketServer = require('ws').Server;
var redis = require('redis');
var wss = new WebSocketServer({port: ...
Actually, this is the meat
client.subscribe('channels:cheese');
client.on('message', function(chn, message){
ws.send(messa...
And here’s the client
(function(window){
window.EventChannelWs = function(){
var socket = new WebSocket("ws://localhost:80...
And here’s the client
(function(window){
window.EventChannelWs = function(){
var socket = new WebSocket("ws://localhost:80...
And here’s the client
(function(window){
window.EventChannelWs = function(){
var socket = new WebSocket("ws://localhost:80...
And here’s the client
(function(window){
window.EventChannelWs = function(){
var socket = new WebSocket("ws://localhost:80...
Sample was very simple
• No synchronisation with server for initial event
• No fallback when the web socket server is down...
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 th...
WebSockets are a bloody mess™
• RFC6455 is 71 pages long
• Adding a lot of bit twiddling to intentionally break
proxy serv...
Use SSL.
By the Gods. Use SSL
EventSource
Client
var cheese_channel = new EventSource(url);
var log_source = $('#eventsource');
cheese_channel.addEventListener('che...
Client
var cheese_channel = new EventSource(url);
var log_source = $('#eventsource');
cheese_channel.addEventListener('che...
Client
var cheese_channel = new EventSource(url);
var log_source = $('#eventsource');
cheese_channel.addEventListener('che...
Client
var cheese_channel = new EventSource(url);
var log_source = $('#eventsource');
cheese_channel.addEventListener('che...
Server
• Keeps the connection open
• Sends blank-line separated groups of key/value pairs as events happen
• Can tell the ...
Disillusioning
• Bound to the 6-connections per host rule
• Still needs manual synchronising if you don’t want to lose eve...
Disillusioning
• Bound to the 6-connections per host rule
• Still needs manual synchronising if you don’t want to lose eve...
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 ...
Production code
• The following code samples form the basis of the
initial demo
• It’s production code
• No support issue ...
Caution: ☕ ahead
Synchronising using the
database
events_since_id = (channel, id, cb)->
q = """
select * from events
where channel_id = $1 ...
The meat
handle_subscription = (c, message)->
fetch_events channel, last_event_id, (err, evts)->
return http_error 500, 'F...
if waiting() or (evts and evts.length > 0)
abort_processing = write(res, evts, not waiting());
if waiting() or abort_proce...
if waiting() or (evts and evts.length > 0)
abort_processing = write(res, evts, not waiting());
if waiting() or abort_proce...
if waiting() or (evts and evts.length > 0)
abort_processing = write(res, evts, not waiting());
if waiting() or abort_proce...
if waiting() or (evts and evts.length > 0)
abort_processing = write(res, evts, not waiting());
if waiting() or abort_proce...
if waiting() or (evts and evts.length > 0)
abort_processing = write(res, evts, not waiting());
if waiting() or abort_proce...
handle_subscription = (c, message)->
fetch_events channel, last_event_id, (err, evts)->
return http_error 500, 'Failed to ...
handle_subscription = (c, message)->
fetch_events channel, last_event_id, (err, evts)->
return http_error 500, 'Failed to ...
handle_subscription = (c, message)->
fetch_events channel, last_event_id, (err, evts)->
return http_error 500, 'Failed to ...
handle_subscription = (c, message)->
fetch_events channel, last_event_id, (err, evts)->
return http_error 500, 'Failed to ...
handle_subscription = (c, message)->
fetch_events channel, last_event_id, (err, evts)->
return http_error 500, 'Failed to ...
handle_subscription = (c, message)->
fetch_events channel, last_event_id, (err, evts)->
return http_error 500, 'Failed to ...
Fallback
• Our fronted code connects to /e.php
• Our reverse proxy redirects that to the node
daemon
• If that daemon is d...
Client is more complicated
poll: =>
url = "#{@endpoint}/#{@channel}/#{@wait_id}"
$.ajax url,
cache: false,
dataType: 'json...
Client is more complicated
poll: =>
url = "#{@endpoint}/#{@channel}/#{@wait_id}"
$.ajax url,
cache: false,
dataType: 'json...
Client is more complicated
poll: =>
url = "#{@endpoint}/#{@channel}/#{@wait_id}"
$.ajax url,
cache: false,
dataType: 'json...
Client is more complicated
poll: =>
url = "#{@endpoint}/#{@channel}/#{@wait_id}"
$.ajax url,
cache: false,
dataType: 'json...
Client is more complicated
poll: =>
url = "#{@endpoint}/#{@channel}/#{@wait_id}"
$.ajax url,
cache: false,
dataType: 'json...
Client is more complicated
poll: =>
url = "#{@endpoint}/#{@channel}/#{@wait_id}"
$.ajax url,
cache: false,
dataType: 'json...
So.Why a daemon?
• Evented architecture lends itself well to many open
connections never really using CPU
• You do not wan...
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 WebS...
• If your clients use browsers (and IE10+)
• and if you have a good reverse proxy
• and if you can use SSL
• then use WebS...
• If your clients use browsers (and IE10+)
• and if you have a good reverse proxy
• and if you can use SSL
• then use WebS...
• If your clients use browsers (and IE10+)
• and if you have a good reverse proxy
• and if you can use SSL
• then use WebS...
Thank you!
• @pilif on twitter
• https://github.com/pilif/server-side-events

Also: We are looking for a front-end designe...
Server Side Events
Server Side Events
Server Side Events
Upcoming SlideShare
Loading in …5
×

Server Side Events

2,236 views

Published on

My Talk at Swissjeese 2014

Published in: Software
0 Comments
5 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
2,236
On SlideShare
0
From Embeds
0
Number of Embeds
85
Actions
Shares
0
Downloads
9
Comments
0
Likes
5
Embeds 0
No embeds

No notes for slide

Server Side Events

  1. 1. WHAT just happened? Reacting to Events on the Server
  2. 2. Administrative Notes
  3. 3. • @pilif on twitter • pilif on github • working at Sensational AG
  4. 4. • @pilif on twitter • pilif on github • working at Sensational AG • strongly dislike shirts
  5. 5. "💩".length() is still 2 Though there is a lot of good news with ES6
  6. 6. There will be some ☕
  7. 7. Demo first™
  8. 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. 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. 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. 11. But back to the topic
  12. 12. TIMTWWTDI * • Short Polling • Long Polling • EventSource • Web Sockets * yes. I’m that old
  13. 13. Short Polling • Are we there yet? • Are we there yet? • Are we there yet? • And now?
  14. 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. 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. 16. WebSockets • «TCP/IP in your browser»* • Full Duplex • Message passing • Persistent connection between Browser and Server
  17. 17. Let’s try them
  18. 18. Surprise Demo™also Demo first™
  19. 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. 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. 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. 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. 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);
  24. 24. Web Sockets
  25. 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. 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. 27. Actually, this is the meat client.subscribe('channels:cheese'); client.on('message', function(chn, message){ ws.send(message); });
  28. 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. 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. 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. 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. 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
  33. 33. Flip-Side
  34. 34. 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
  35. 35. 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
  36. 36. Use SSL. By the Gods. Use SSL
  37. 37. EventSource
  38. 38. 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 )); });
  39. 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. 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. 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. 42. 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
  43. 43. 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
  44. 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. 45. Long Polling
  46. 46. 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*
  47. 47. 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
  48. 48. Caution: ☕ ahead
  49. 49. 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
  50. 50. 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
  51. 51. 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
  52. 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. 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. 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. 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. 56. 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
  57. 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. 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. 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. 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 LOL - Boolean parameter!!!
  61. 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
  62. 62. 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
  63. 63. 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
  64. 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. 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. 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. 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. 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. 69. 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
  70. 70. In conclusionalso quite different from what I initially meant to say
  71. 71. https://twitter.com/pilif/status/491943226258239490
  72. 72. And that was last wednesday
  73. 73. So…
  74. 74. • If your clients use browsers (and IE10+)
  75. 75. • If your clients use browsers (and IE10+) • and if you have a good reverse proxy
  76. 76. • If your clients use browsers (and IE10+) • and if you have a good reverse proxy • and if you can use SSL
  77. 77. • If your clients use browsers (and IE10+) • and if you have a good reverse proxy • and if you can use SSL • then use WebSockets
  78. 78. • 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
  79. 79. • 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
  80. 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 • Otherwise use long polling • Also, only use one - don’t mix - not worth the effort • EventSource, frankly, sucks
  81. 81. 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

×