Your SlideShare is downloading. ×
0
Live Streaming        &Server Sent Events    Tomáš Kramár      @tkramar
When?●   Server needs to stream data to client    –   Server decides when and what to send    –   Client waits and listens...
How? / Terminology●   AJAX polling●   Comet●   WebSockets●   Server-Sent Events
AJAX polling                   Any news?Browser/Client                  Server
AJAX polling                   Any news?                     NoBrowser/Client                  Server
AJAX polling                   Any news?                     No                   Any news?                     NoBrowser/...
AJAX polling                   Any news?                     No                   Any news?                     No        ...
AJAX polling                   Any news?                     No                   Any news?                     No        ...
AJAX polling●   Overhead    –   Establishing new connections, TCP handshakes    –   Sending HTTP headers    –   Multiply b...
Comet●   set of technology principles/communication    patterns●   mostly hacks    –   forever-iframe    –   htmlfile Acti...
WebSockets●   bi-directional, full-duplex communication    channels over a single TCP connection●   HTML5●   being standar...
Server-Sent Events●   HTML5●   Traditional HTTP    –   No special protocol or server implementation●   Browser establishes...
SSE                 Request w parameters                 id: 1                 event: display                 data: { foo:...
SSE                 Request w parameters                 id: 1                 event: display                 data: { foo:...
Case study●   Live search in trademark databases●   query    –   search in register #1        ●   Search (~15s), parse sea...
Demo
Clientthis.source = new EventSource(marks/search);self.source.addEventListener(results, function(e) {  self.marks.appendMa...
Client gotchas ●    Special events:      –   open      –   error ●    Dont forget to close the requestself.source.addEvent...
Server●   Must support    –   long-running request    –   Live-streaming (i.e., no output buffering)●   Rainbows!, Puma or...
Rails 4 Live Streamingclass MarksController < ApplicationController  include ActionController::Live  def results    respon...
require jsonclass SSE def initialize io                                               event: displayn  @io = io           ...
Timeouts, lost connections, internet  explorers and other bad things●   EventSource request can be interrupted●   EventSou...
Handling reconnections●   When EventSource reconnects, we need to    continue sending the data from the point the    conne...
marks/search?q=eset                      GirlFriday                                                      Search           ...
class MarksController < ApplicationController include ActionController::Live def search!  uuid = UUID.new.generate(:compac...
class MarksController < ApplicationController include ActionController::Live def search!  uuid = UUID.new.generate(:compac...
class MarksController < ApplicationController include ActionController::Live def search!  uuid = UUID.new.generate(:compac...
class MarksController < ApplicationController include ActionController::Live def search!  uuid = UUID.new.generate(:compac...
class MarksController < ApplicationController include ActionController::Live def search!  uuid = UUID.new.generate(:compac...
class MarksController < ApplicationController include ActionController::Live def search!  uuid = UUID.new.generate(:compac...
class MarksController < ApplicationController include ActionController::Live def search!  uuid = UUID.new.generate(:compac...
GirlFriday workerclass SearchWorker def self.perform(phrase, job_id)   channel = Channel.for_job(job_id)   queue = SafeQue...
SafeQueueclass SafeQueue def initialize(channel, redis)   @channel = channel   @redis = redis end def next_message(&block)...
EventSource Compatibility●   Firefox 6+, Chrome 6+, Safari 5+, Opera 11+,    iOS Safari 4+, Blackberry, Opera Mobile,    C...
Fallback●   Polyfills    –   https://github.com/remy/polyfills/blob/master/Event        Source.js        ●   Hanging GET, ...
Summary●   Unidirectional server-to-client communication●   Single request●   Real-time●   Easy to implement●   Well suppo...
Upcoming SlideShare
Loading in...5
×

Live Streaming & Server Sent Events

6,518

Published on

Published in: Technology

Transcript of "Live Streaming & Server Sent Events"

  1. 1. Live Streaming &Server Sent Events Tomáš Kramár @tkramar
  2. 2. When?● Server needs to stream data to client – Server decides when and what to send – Client waits and listens – Client does not need to send messages – Uni-directional communication – Asynchronously
  3. 3. How? / Terminology● AJAX polling● Comet● WebSockets● Server-Sent Events
  4. 4. AJAX polling Any news?Browser/Client Server
  5. 5. AJAX polling Any news? NoBrowser/Client Server
  6. 6. AJAX polling Any news? No Any news? NoBrowser/Client Server
  7. 7. AJAX polling Any news? No Any news? No Any news?Browser/Client Yes! Server
  8. 8. AJAX polling Any news? No Any news? No Any news?Browser/Client Yes! Server Any news? No
  9. 9. AJAX polling● Overhead – Establishing new connections, TCP handshakes – Sending HTTP headers – Multiply by number of clients● Not really realtime – Poll each 2 seconds
  10. 10. Comet● set of technology principles/communication patterns● mostly hacks – forever-iframe – htmlfile ActiveX object – XHR multipart/streaming/long-polling – Flash – ..
  11. 11. WebSockets● bi-directional, full-duplex communication channels over a single TCP connection● HTML5● being standardized
  12. 12. Server-Sent Events● HTML5● Traditional HTTP – No special protocol or server implementation● Browser establishes single connection and waits● Server generates events
  13. 13. SSE Request w parameters id: 1 event: display data: { foo: moo }Browser/Client Server
  14. 14. SSE Request w parameters id: 1 event: display data: { foo: moo }Browser/Client id: 2 Server event: redraw data: { boo: hoo }
  15. 15. Case study● Live search in trademark databases● query – search in register #1 ● Search (~15s), parse search result list, fetch each result (~3s each), go to next page in search result list (~10s), fetch each result, ... – search in register #2 ● ... – …● Dont let the user wait, display results when they are available
  16. 16. Demo
  17. 17. Clientthis.source = new EventSource(marks/search);self.source.addEventListener(results, function(e) { self.marks.appendMarks($.parseJSON(e.data));});self.source.addEventListener(failure, function(e) { self.errors.showError();});self.source.addEventListener(status, function(e) { self.paging.update($.parseJSON(e.data));});
  18. 18. Client gotchas ● Special events: – open – error ● Dont forget to close the requestself.source.addEventListener(finished, function(e) { self.status.searchFinished(); self.source.close();});
  19. 19. Server● Must support – long-running request – Live-streaming (i.e., no output buffering)● Rainbows!, Puma or Thin● Rails 4 (beta) supports live streaming
  20. 20. Rails 4 Live Streamingclass MarksController < ApplicationController include ActionController::Live def results response.headers[Content-Type] = text/event-stream sse = SSE.new(response.stream) Tort.search(params[:query]) do |on| on.results do |hits| sse.write(hits, event: result) end on.status_change do |status| sse.write(status, event: status) end on.error do sse.write({}, event: failure) end end endend
  21. 21. require jsonclass SSE def initialize io event: displayn @io = io data: { foo: moo }nn end def write object, options = {} options.each do |k,v| @io.write "#{k}: #{v}n" end @io.write "data: #{JSON.dump(object)}nn" end def close @io.close endend
  22. 22. Timeouts, lost connections, internet explorers and other bad things● EventSource request can be interrupted● EventSource will reconnect automatically● What happens with the data during the time connection was not available?
  23. 23. Handling reconnections● When EventSource reconnects, we need to continue sending the data from the point the connection was lost – Do the work in the background and store events somewhere – In the controller, load events from the storage● EventSource sends Last-Event-Id in HTTP header – But we dont need it if we remove the processed events
  24. 24. marks/search?q=eset GirlFriday Search 3342345 HTTP 202 Accepted marks/results?job_id=3342345Browser Server Redis marks/results?job_id=3342345 MarksController event: results data: {foo: boo} event: status data: {moo: hoo}
  25. 25. class MarksController < ApplicationController include ActionController::Live def search! uuid = UUID.new.generate(:compact) TORT_QUEUE << { phrase: params[:q], job_id: uuid } render status: 202, text: marks_results_path(job: uuid) end def results response.headers[Content-Type] = text/event-stream sse = SSE.new(response.stream) queue = SafeQueue.new(Channel.for_job(params[:job]), Tmzone.redis) finished = false begin begin queue.next_message do |json_message| message = JSON.parse(json_message) case message["type"] when "results" then sse.write(message["data"], event: results) when "failure" then sse.write({}, event: failure) when "fatal" then sse.write({}, event: fatal) finished = true when "status" then sse.write(message["data"], event: status) when "finished" then sse.write({}, event: finished) finished = true end end end while !finished rescue IOError # when clients disconnects ensure sse.close end endend
  26. 26. class MarksController < ApplicationController include ActionController::Live def search! uuid = UUID.new.generate(:compact) TORT_QUEUE << { phrase: params[:q], job_id: uuid } generate job_id render status: 202, text: marks_results_path(job: uuid) end def results response.headers[Content-Type] = text/event-stream sse = SSE.new(response.stream) queue = SafeQueue.new(Channel.for_job(params[:job]), Tmzone.redis) finished = false begin begin queue.next_message do |json_message| message = JSON.parse(json_message) case message["type"] when "results" then sse.write(message["data"], event: results) when "failure" then sse.write({}, event: failure) when "fatal" then sse.write({}, event: fatal) finished = true when "status" then sse.write(message["data"], event: status) when "finished" then sse.write({}, event: finished) finished = true end end end while !finished rescue IOError # when clients disconnects ensure sse.close end endend
  27. 27. class MarksController < ApplicationController include ActionController::Live def search! uuid = UUID.new.generate(:compact) TORT_QUEUE << { phrase: params[:q], job_id: uuid } start async job (GirlFriday) render status: 202, text: marks_results_path(job: uuid) end def results response.headers[Content-Type] = text/event-stream sse = SSE.new(response.stream) queue = SafeQueue.new(Channel.for_job(params[:job]), Tmzone.redis) finished = false begin begin queue.next_message do |json_message| message = JSON.parse(json_message) case message["type"] when "results" then sse.write(message["data"], event: results) when "failure" then sse.write({}, event: failure) when "fatal" then sse.write({}, event: fatal) finished = true when "status" then sse.write(message["data"], event: status) when "finished" then sse.write({}, event: finished) finished = true end end end while !finished rescue IOError # when clients disconnects ensure sse.close end endend
  28. 28. class MarksController < ApplicationController include ActionController::Live def search! uuid = UUID.new.generate(:compact) TORT_QUEUE << { phrase: params[:q], job_id: uuid } render status: 202, text: marks_results_path(job: uuid) end send results URL def results response.headers[Content-Type] = text/event-stream sse = SSE.new(response.stream) queue = SafeQueue.new(Channel.for_job(params[:job]), Tmzone.redis) finished = false begin begin queue.next_message do |json_message| message = JSON.parse(json_message) case message["type"] when "results" then sse.write(message["data"], event: results) when "failure" then sse.write({}, event: failure) when "fatal" then sse.write({}, event: fatal) finished = true when "status" then sse.write(message["data"], event: status) when "finished" then sse.write({}, event: finished) finished = true end end end while !finished rescue IOError # when clients disconnects ensure sse.close end endend
  29. 29. class MarksController < ApplicationController include ActionController::Live def search! uuid = UUID.new.generate(:compact) TORT_QUEUE << { phrase: params[:q], job_id: uuid } render status: 202, text: marks_results_path(job: uuid) end def results response.headers[Content-Type] = text/event-stream sse = SSE.new(response.stream) queue = SafeQueue.new(Channel.for_job(params[:job]), Tmzone.redis) Get queue for this job, finished = false async job is pushing begin to this queue begin queue.next_message do |json_message| message = JSON.parse(json_message) case message["type"] when "results" then sse.write(message["data"], event: results) when "failure" then sse.write({}, event: failure) when "fatal" then sse.write({}, event: fatal) finished = true when "status" then sse.write(message["data"], event: status) when "finished" then sse.write({}, event: finished) finished = true end end end while !finished rescue IOError # when clients disconnects ensure sse.close end endend
  30. 30. class MarksController < ApplicationController include ActionController::Live def search! uuid = UUID.new.generate(:compact) TORT_QUEUE << { phrase: params[:q], job_id: uuid } render status: 202, text: marks_results_path(job: uuid) end def results response.headers[Content-Type] = text/event-stream sse = SSE.new(response.stream) queue = SafeQueue.new(Channel.for_job(params[:job]), Tmzone.redis) finished = false begin begin queue.next_message do |json_message| Fetch next message message = JSON.parse(json_message) from queue (blocks until case message["type"] when "results" then one is available) sse.write(message["data"], event: results) when "failure" then sse.write({}, event: failure) when "fatal" then sse.write({}, event: fatal) finished = true when "status" then sse.write(message["data"], event: status) when "finished" then sse.write({}, event: finished) finished = true end end end while !finished rescue IOError # when clients disconnects ensure sse.close end endend
  31. 31. class MarksController < ApplicationController include ActionController::Live def search! uuid = UUID.new.generate(:compact) TORT_QUEUE << { phrase: params[:q], job_id: uuid } render status: 202, text: marks_results_path(job: uuid) end def results response.headers[Content-Type] = text/event-stream sse = SSE.new(response.stream) queue = SafeQueue.new(Channel.for_job(params[:job]), Tmzone.redis) finished = false begin begin queue.next_message do |json_message| message = JSON.parse(json_message) case message["type"] when "results" then sse.write(message["data"], event: results) when "failure" then sse.write({}, event: failure) when "fatal" then sse.write({}, event: fatal) finished = true when "status" then sse.write(message["data"], event: status) when "finished" then sse.write({}, event: finished) finished = true end end end while !finished rescue IOError # when clients disconnects IOError is raised when client ensure sse.close disconnected and we are end end writing to response.streamend
  32. 32. GirlFriday workerclass SearchWorker def self.perform(phrase, job_id) channel = Channel.for_job(job_id) queue = SafeQueue.new(channel, Tmzone.redis) Tort.search(phrase) do |on| on.results do |hits| queue.push({ type: "results", data: hits }.to_json) end on.status_change do |status| queue.push({ type: "status", data: status }.to_json) end on.error do queue.push({ type: failure }.to_json) end end queue.push({ type: "finished" }.to_json) endend
  33. 33. SafeQueueclass SafeQueue def initialize(channel, redis) @channel = channel @redis = redis end def next_message(&block) begin _, message = @redis.blpop(@channel) block.call(message) rescue => error @redis.lpush(@channel, message) raise error end end def push(message) @redis.rpush(@channel, message) endend
  34. 34. EventSource Compatibility● Firefox 6+, Chrome 6+, Safari 5+, Opera 11+, iOS Safari 4+, Blackberry, Opera Mobile, Chrome for Android, Firefox for Android
  35. 35. Fallback● Polyfills – https://github.com/remy/polyfills/blob/master/Event Source.js ● Hanging GET, waits until the request terminates, essentially buffering the live output – https://github.com/Yaffle/EventSource ● send a keep-alive message each 15 seconds
  36. 36. Summary● Unidirectional server-to-client communication● Single request● Real-time● Easy to implement● Well supported except for IE
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×