SlideShare ist ein Scribd-Unternehmen logo
1 von 36
Downloaden Sie, um offline zu lesen
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
    –   Client does not need to send messages
    –   Uni-directional communication
    –   Asynchronously
How? / Terminology
●   AJAX polling
●   Comet
●   WebSockets
●   Server-Sent Events
AJAX polling
                   Any news?




Browser/Client                  Server
AJAX polling
                   Any news?

                     No




Browser/Client                  Server
AJAX polling
                   Any news?

                     No

                   Any news?

                     No



Browser/Client                  Server
AJAX polling
                   Any news?

                     No

                   Any news?

                     No

                   Any news?

Browser/Client       Yes!       Server
AJAX polling
                   Any news?

                     No

                   Any news?

                     No

                   Any news?

Browser/Client       Yes!       Server

                   Any news?

                     No
AJAX polling
●   Overhead
    –   Establishing new connections, TCP handshakes
    –   Sending HTTP headers
    –   Multiply by number of clients
●   Not really realtime
    –   Poll each 2 seconds
Comet
●   set of technology principles/communication
    patterns
●   mostly hacks
    –   forever-iframe
    –   htmlfile ActiveX object
    –   XHR multipart/streaming/long-polling
    –   Flash
    –   ..
WebSockets
●   bi-directional, full-duplex communication
    channels over a single TCP connection
●   HTML5
●   being standardized
Server-Sent Events
●   HTML5
●   Traditional HTTP
    –   No special protocol or server implementation
●   Browser establishes single connection and
    waits
●   Server generates events
SSE
                 Request w parameters




                 id: 1
                 event: display
                 data: { foo: 'moo' }

Browser/Client                           Server
SSE
                 Request w parameters




                 id: 1
                 event: display
                 data: { foo: 'moo' }

Browser/Client   id: 2                   Server
                 event: redraw
                 data: { boo: 'hoo' }
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
        ●   ...
    –   …
●   Don't let the user wait, display results when
    they are available
Demo
Client

this.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));
});
Client gotchas
 ●    Special events:
      –   open
      –   error
 ●    Don't forget to close the request

self.source.addEventListener('finished', function(e) {
 self.status.searchFinished();
 self.source.close();
});
Server
●   Must support
    –   long-running request
    –   Live-streaming (i.e., no output buffering)
●   Rainbows!, Puma or Thin
●   Rails 4 (beta) supports live streaming
Rails 4 Live Streaming
class 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
  end
end
require 'json'


class 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
 end
end
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?
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 don't need it if we remove the processed
        events
marks/search?q=eset                      GirlFriday
                                                      Search
                                                     3342345
           HTTP 202 Accepted
           marks/results?job_id=3342345




Browser                                   Server       Redis



          marks/results?job_id=3342345


                                                   MarksController

             event: results
             data: {foo: 'boo'}

             event: status
             data: {moo: 'hoo'}
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
 end
end
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
 end
end
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
 end
end
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
 end
end
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
 end
end
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
 end
end
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.stream
end
GirlFriday worker
class 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)
 end
end
SafeQueue
class 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)
 end
end
EventSource Compatibility
●   Firefox 6+, Chrome 6+, Safari 5+, Opera 11+,
    iOS Safari 4+, Blackberry, Opera Mobile,
    Chrome for Android, Firefox for Android
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
Summary
●   Unidirectional server-to-client communication
●   Single request
●   Real-time
●   Easy to implement
●   Well supported except for IE

Weitere ähnliche Inhalte

Was ist angesagt?

Working with Dynamic Content and Adding Templating engines, MVC
Working with Dynamic Content and Adding Templating engines, MVCWorking with Dynamic Content and Adding Templating engines, MVC
Working with Dynamic Content and Adding Templating engines, MVCKnoldus Inc.
 
Jenkins - perdre du temps pour en gagner
Jenkins - perdre du temps pour en gagnerJenkins - perdre du temps pour en gagner
Jenkins - perdre du temps pour en gagnerGeeks Anonymes
 
Architecture java j2 ee a partager
Architecture java j2 ee a partagerArchitecture java j2 ee a partager
Architecture java j2 ee a partageraliagadir
 
Service Worker Presentation
Service Worker PresentationService Worker Presentation
Service Worker PresentationKyle Dorman
 
React new features and intro to Hooks
React new features and intro to HooksReact new features and intro to Hooks
React new features and intro to HooksSoluto
 
PWA 與 Service Worker
PWA 與 Service WorkerPWA 與 Service Worker
PWA 與 Service WorkerAnna Su
 
Formation jpa-hibernate-spring-data
Formation jpa-hibernate-spring-dataFormation jpa-hibernate-spring-data
Formation jpa-hibernate-spring-dataLhouceine OUHAMZA
 
React + Redux Introduction
React + Redux IntroductionReact + Redux Introduction
React + Redux IntroductionNikolaus Graf
 
Web sockets in Angular
Web sockets in AngularWeb sockets in Angular
Web sockets in AngularYakov Fain
 
Jmeter vs loadrunner vs neoload
Jmeter vs loadrunner vs neoloadJmeter vs loadrunner vs neoload
Jmeter vs loadrunner vs neoloadpratik mohite
 
introduction to Vue.js 3
introduction to Vue.js 3 introduction to Vue.js 3
introduction to Vue.js 3 ArezooKmn
 
Gitlab CI : Integration et Déploiement Continue
Gitlab CI : Integration et Déploiement ContinueGitlab CI : Integration et Déploiement Continue
Gitlab CI : Integration et Déploiement ContinueVincent Composieux
 
InjectionIII의 Hot Reload를 이용하여 앱 개발을 좀 더 편하게 하기.pdf
InjectionIII의 Hot Reload를 이용하여 앱 개발을 좀 더 편하게 하기.pdfInjectionIII의 Hot Reload를 이용하여 앱 개발을 좀 더 편하게 하기.pdf
InjectionIII의 Hot Reload를 이용하여 앱 개발을 좀 더 편하게 하기.pdf정민 안
 

Was ist angesagt? (20)

Working with Dynamic Content and Adding Templating engines, MVC
Working with Dynamic Content and Adding Templating engines, MVCWorking with Dynamic Content and Adding Templating engines, MVC
Working with Dynamic Content and Adding Templating engines, MVC
 
react-slides.pptx
react-slides.pptxreact-slides.pptx
react-slides.pptx
 
Jenkins - perdre du temps pour en gagner
Jenkins - perdre du temps pour en gagnerJenkins - perdre du temps pour en gagner
Jenkins - perdre du temps pour en gagner
 
Maven et industrialisation du logiciel
Maven et industrialisation du logicielMaven et industrialisation du logiciel
Maven et industrialisation du logiciel
 
Architecture java j2 ee a partager
Architecture java j2 ee a partagerArchitecture java j2 ee a partager
Architecture java j2 ee a partager
 
Service Worker Presentation
Service Worker PresentationService Worker Presentation
Service Worker Presentation
 
React new features and intro to Hooks
React new features and intro to HooksReact new features and intro to Hooks
React new features and intro to Hooks
 
PWA 與 Service Worker
PWA 與 Service WorkerPWA 與 Service Worker
PWA 與 Service Worker
 
Formation jpa-hibernate-spring-data
Formation jpa-hibernate-spring-dataFormation jpa-hibernate-spring-data
Formation jpa-hibernate-spring-data
 
Spring boot
Spring bootSpring boot
Spring boot
 
React + Redux Introduction
React + Redux IntroductionReact + Redux Introduction
React + Redux Introduction
 
Web sockets in Angular
Web sockets in AngularWeb sockets in Angular
Web sockets in Angular
 
Jmeter vs loadrunner vs neoload
Jmeter vs loadrunner vs neoloadJmeter vs loadrunner vs neoload
Jmeter vs loadrunner vs neoload
 
Spring Batch Introduction
Spring Batch IntroductionSpring Batch Introduction
Spring Batch Introduction
 
An introduction to Vue.js
An introduction to Vue.jsAn introduction to Vue.js
An introduction to Vue.js
 
introduction to Vue.js 3
introduction to Vue.js 3 introduction to Vue.js 3
introduction to Vue.js 3
 
Gitlab CI : Integration et Déploiement Continue
Gitlab CI : Integration et Déploiement ContinueGitlab CI : Integration et Déploiement Continue
Gitlab CI : Integration et Déploiement Continue
 
Spring Batch 2.0
Spring Batch 2.0Spring Batch 2.0
Spring Batch 2.0
 
InjectionIII의 Hot Reload를 이용하여 앱 개발을 좀 더 편하게 하기.pdf
InjectionIII의 Hot Reload를 이용하여 앱 개발을 좀 더 편하게 하기.pdfInjectionIII의 Hot Reload를 이용하여 앱 개발을 좀 더 편하게 하기.pdf
InjectionIII의 Hot Reload를 이용하여 앱 개발을 좀 더 편하게 하기.pdf
 
react redux.pdf
react redux.pdfreact redux.pdf
react redux.pdf
 

Ähnlich wie Live Streaming & Server Sent Events

Spring Web Services: SOAP vs. REST
Spring Web Services: SOAP vs. RESTSpring Web Services: SOAP vs. REST
Spring Web Services: SOAP vs. RESTSam Brannen
 
Writing robust Node.js applications
Writing robust Node.js applicationsWriting robust Node.js applications
Writing robust Node.js applicationsTom Croucher
 
Future Decoded - Node.js per sviluppatori .NET
Future Decoded - Node.js per sviluppatori .NETFuture Decoded - Node.js per sviluppatori .NET
Future Decoded - Node.js per sviluppatori .NETGianluca Carucci
 
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
 
Node.js: Continuation-Local-Storage and the Magic of AsyncListener
Node.js: Continuation-Local-Storage and the Magic of AsyncListenerNode.js: Continuation-Local-Storage and the Magic of AsyncListener
Node.js: Continuation-Local-Storage and the Magic of AsyncListenerIslam Sharabash
 
Intoduction to Play Framework
Intoduction to Play FrameworkIntoduction to Play Framework
Intoduction to Play FrameworkKnoldus Inc.
 
Comet from JavaOne 2008
Comet from JavaOne 2008Comet from JavaOne 2008
Comet from JavaOne 2008Joe Walker
 
Introduction of server sent events (sse)
Introduction of server sent events (sse)Introduction of server sent events (sse)
Introduction of server sent events (sse)Yuji KONDO
 
Speed up your Web applications with HTML5 WebSockets
Speed up your Web applications with HTML5 WebSocketsSpeed up your Web applications with HTML5 WebSockets
Speed up your Web applications with HTML5 WebSocketsYakov Fain
 
Intro To JavaScript Unit Testing - Ran Mizrahi
Intro To JavaScript Unit Testing - Ran MizrahiIntro To JavaScript Unit Testing - Ran Mizrahi
Intro To JavaScript Unit Testing - Ran MizrahiRan Mizrahi
 
Sherlock Homepage - A detective story about running large web services - NDC ...
Sherlock Homepage - A detective story about running large web services - NDC ...Sherlock Homepage - A detective story about running large web services - NDC ...
Sherlock Homepage - A detective story about running large web services - NDC ...Maarten Balliauw
 
Reactive & Realtime Web Applications with TurboGears2
Reactive & Realtime Web Applications with TurboGears2Reactive & Realtime Web Applications with TurboGears2
Reactive & Realtime Web Applications with TurboGears2Alessandro Molina
 
PlayFab Advanced Cloud Script
PlayFab Advanced Cloud ScriptPlayFab Advanced Cloud Script
PlayFab Advanced Cloud ScriptThomas Robbins
 
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
 
Rhebok, High Performance Rack Handler / Rubykaigi 2015
Rhebok, High Performance Rack Handler / Rubykaigi 2015Rhebok, High Performance Rack Handler / Rubykaigi 2015
Rhebok, High Performance Rack Handler / Rubykaigi 2015Masahiro Nagano
 
Monitoring und Metriken im Wunderland
Monitoring und Metriken im WunderlandMonitoring und Metriken im Wunderland
Monitoring und Metriken im WunderlandD
 
HTML5 Server Sent Events/JSF JAX 2011 Conference
HTML5 Server Sent Events/JSF  JAX 2011 ConferenceHTML5 Server Sent Events/JSF  JAX 2011 Conference
HTML5 Server Sent Events/JSF JAX 2011 ConferenceRoger Kitain
 
Server Side Events
Server Side EventsServer Side Events
Server Side Eventsthepilif
 
soft-shake.ch - Hands on Node.js
soft-shake.ch - Hands on Node.jssoft-shake.ch - Hands on Node.js
soft-shake.ch - Hands on Node.jssoft-shake.ch
 

Ähnlich wie Live Streaming & Server Sent Events (20)

Intro to Node
Intro to NodeIntro to Node
Intro to Node
 
Spring Web Services: SOAP vs. REST
Spring Web Services: SOAP vs. RESTSpring Web Services: SOAP vs. REST
Spring Web Services: SOAP vs. REST
 
Writing robust Node.js applications
Writing robust Node.js applicationsWriting robust Node.js applications
Writing robust Node.js applications
 
Future Decoded - Node.js per sviluppatori .NET
Future Decoded - Node.js per sviluppatori .NETFuture Decoded - Node.js per sviluppatori .NET
Future Decoded - Node.js per sviluppatori .NET
 
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...
 
Node.js: Continuation-Local-Storage and the Magic of AsyncListener
Node.js: Continuation-Local-Storage and the Magic of AsyncListenerNode.js: Continuation-Local-Storage and the Magic of AsyncListener
Node.js: Continuation-Local-Storage and the Magic of AsyncListener
 
Intoduction to Play Framework
Intoduction to Play FrameworkIntoduction to Play Framework
Intoduction to Play Framework
 
Comet from JavaOne 2008
Comet from JavaOne 2008Comet from JavaOne 2008
Comet from JavaOne 2008
 
Introduction of server sent events (sse)
Introduction of server sent events (sse)Introduction of server sent events (sse)
Introduction of server sent events (sse)
 
Speed up your Web applications with HTML5 WebSockets
Speed up your Web applications with HTML5 WebSocketsSpeed up your Web applications with HTML5 WebSockets
Speed up your Web applications with HTML5 WebSockets
 
Intro To JavaScript Unit Testing - Ran Mizrahi
Intro To JavaScript Unit Testing - Ran MizrahiIntro To JavaScript Unit Testing - Ran Mizrahi
Intro To JavaScript Unit Testing - Ran Mizrahi
 
Sherlock Homepage - A detective story about running large web services - NDC ...
Sherlock Homepage - A detective story about running large web services - NDC ...Sherlock Homepage - A detective story about running large web services - NDC ...
Sherlock Homepage - A detective story about running large web services - NDC ...
 
Reactive & Realtime Web Applications with TurboGears2
Reactive & Realtime Web Applications with TurboGears2Reactive & Realtime Web Applications with TurboGears2
Reactive & Realtime Web Applications with TurboGears2
 
PlayFab Advanced Cloud Script
PlayFab Advanced Cloud ScriptPlayFab Advanced Cloud Script
PlayFab Advanced Cloud Script
 
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...
 
Rhebok, High Performance Rack Handler / Rubykaigi 2015
Rhebok, High Performance Rack Handler / Rubykaigi 2015Rhebok, High Performance Rack Handler / Rubykaigi 2015
Rhebok, High Performance Rack Handler / Rubykaigi 2015
 
Monitoring und Metriken im Wunderland
Monitoring und Metriken im WunderlandMonitoring und Metriken im Wunderland
Monitoring und Metriken im Wunderland
 
HTML5 Server Sent Events/JSF JAX 2011 Conference
HTML5 Server Sent Events/JSF  JAX 2011 ConferenceHTML5 Server Sent Events/JSF  JAX 2011 Conference
HTML5 Server Sent Events/JSF JAX 2011 Conference
 
Server Side Events
Server Side EventsServer Side Events
Server Side Events
 
soft-shake.ch - Hands on Node.js
soft-shake.ch - Hands on Node.jssoft-shake.ch - Hands on Node.js
soft-shake.ch - Hands on Node.js
 

Mehr von tkramar

Lessons learned from SearchD development
Lessons learned from SearchD developmentLessons learned from SearchD development
Lessons learned from SearchD developmenttkramar
 
Learning to rank fulltext results from clicks
Learning to rank fulltext results from clicksLearning to rank fulltext results from clicks
Learning to rank fulltext results from clickstkramar
 
Unix is my IDE
Unix is my IDEUnix is my IDE
Unix is my IDEtkramar
 
Optimising Web Application Frontend
Optimising Web Application FrontendOptimising Web Application Frontend
Optimising Web Application Frontendtkramar
 
MongoDB: Repository for Web-scale metadata
MongoDB: Repository for Web-scale metadataMongoDB: Repository for Web-scale metadata
MongoDB: Repository for Web-scale metadatatkramar
 
Cassandra: Indexing and discovering similar images
Cassandra: Indexing and discovering similar imagesCassandra: Indexing and discovering similar images
Cassandra: Indexing and discovering similar imagestkramar
 
CouchDB: replicated data store for distributed proxy server
CouchDB: replicated data store for distributed proxy serverCouchDB: replicated data store for distributed proxy server
CouchDB: replicated data store for distributed proxy servertkramar
 
Ruby vim
Ruby vimRuby vim
Ruby vimtkramar
 

Mehr von tkramar (8)

Lessons learned from SearchD development
Lessons learned from SearchD developmentLessons learned from SearchD development
Lessons learned from SearchD development
 
Learning to rank fulltext results from clicks
Learning to rank fulltext results from clicksLearning to rank fulltext results from clicks
Learning to rank fulltext results from clicks
 
Unix is my IDE
Unix is my IDEUnix is my IDE
Unix is my IDE
 
Optimising Web Application Frontend
Optimising Web Application FrontendOptimising Web Application Frontend
Optimising Web Application Frontend
 
MongoDB: Repository for Web-scale metadata
MongoDB: Repository for Web-scale metadataMongoDB: Repository for Web-scale metadata
MongoDB: Repository for Web-scale metadata
 
Cassandra: Indexing and discovering similar images
Cassandra: Indexing and discovering similar imagesCassandra: Indexing and discovering similar images
Cassandra: Indexing and discovering similar images
 
CouchDB: replicated data store for distributed proxy server
CouchDB: replicated data store for distributed proxy serverCouchDB: replicated data store for distributed proxy server
CouchDB: replicated data store for distributed proxy server
 
Ruby vim
Ruby vimRuby vim
Ruby vim
 

Kürzlich hochgeladen

TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonetsnaman860154
 
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptxFactors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptxKatpro Technologies
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationRadu Cotescu
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsMaria Levchenko
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfsudhanshuwaghmare1
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking MenDelhi Call girls
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Miguel Araújo
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)Gabriella Davis
 
Real Time Object Detection Using Open CV
Real Time Object Detection Using Open CVReal Time Object Detection Using Open CV
Real Time Object Detection Using Open CVKhem
 
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdfUnderstanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdfUK Journal
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking MenDelhi Call girls
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking MenDelhi Call girls
 
[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdfhans926745
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountPuma Security, LLC
 
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEarley Information Science
 
Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Enterprise Knowledge
 
The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxMalak Abu Hammad
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024The Digital Insurer
 
A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024Results
 

Kürzlich hochgeladen (20)

TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonets
 
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptxFactors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organization
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed texts
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 
08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men08448380779 Call Girls In Friends Colony Women Seeking Men
08448380779 Call Girls In Friends Colony Women Seeking Men
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)
 
Real Time Object Detection Using Open CV
Real Time Object Detection Using Open CVReal Time Object Detection Using Open CV
Real Time Object Detection Using Open CV
 
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdfUnderstanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men
 
[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path Mount
 
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
 
Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...
 
The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptx
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024
 
A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024A Call to Action for Generative AI in 2024
A Call to Action for Generative AI in 2024
 

Live Streaming & Server Sent Events

  • 1. Live Streaming & Server Sent Events Tomáš Kramár @tkramar
  • 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. How? / Terminology ● AJAX polling ● Comet ● WebSockets ● Server-Sent Events
  • 4. AJAX polling Any news? Browser/Client Server
  • 5. AJAX polling Any news? No Browser/Client Server
  • 6. AJAX polling Any news? No Any news? No Browser/Client Server
  • 7. AJAX polling Any news? No Any news? No Any news? Browser/Client Yes! Server
  • 8. AJAX polling Any news? No Any news? No Any news? Browser/Client Yes! Server Any news? No
  • 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. Comet ● set of technology principles/communication patterns ● mostly hacks – forever-iframe – htmlfile ActiveX object – XHR multipart/streaming/long-polling – Flash – ..
  • 11. WebSockets ● bi-directional, full-duplex communication channels over a single TCP connection ● HTML5 ● being standardized
  • 12. Server-Sent Events ● HTML5 ● Traditional HTTP – No special protocol or server implementation ● Browser establishes single connection and waits ● Server generates events
  • 13. SSE Request w parameters id: 1 event: display data: { foo: 'moo' } Browser/Client Server
  • 14. SSE Request w parameters id: 1 event: display data: { foo: 'moo' } Browser/Client id: 2 Server event: redraw data: { boo: 'hoo' }
  • 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 ● ... – … ● Don't let the user wait, display results when they are available
  • 16. Demo
  • 17. Client this.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. Client gotchas ● Special events: – open – error ● Don't forget to close the request self.source.addEventListener('finished', function(e) { self.status.searchFinished(); self.source.close(); });
  • 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. Rails 4 Live Streaming class 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 end end
  • 21. require 'json' class 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 end end
  • 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. 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 don't need it if we remove the processed events
  • 24. marks/search?q=eset GirlFriday Search 3342345 HTTP 202 Accepted marks/results?job_id=3342345 Browser Server Redis marks/results?job_id=3342345 MarksController event: results data: {foo: 'boo'} event: status data: {moo: 'hoo'}
  • 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 end end
  • 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 end end
  • 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 end end
  • 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 end end
  • 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 end end
  • 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 end end
  • 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.stream end
  • 32. GirlFriday worker class 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) end end
  • 33. SafeQueue class 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) end end
  • 34. EventSource Compatibility ● Firefox 6+, Chrome 6+, Safari 5+, Opera 11+, iOS Safari 4+, Blackberry, Opera Mobile, Chrome for Android, Firefox for Android
  • 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. Summary ● Unidirectional server-to-client communication ● Single request ● Real-time ● Easy to implement ● Well supported except for IE