SlideShare a Scribd company logo
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

More Related Content

What's hot

Students report card for C++ project..
Students report card for C++ project..Students report card for C++ project..
Students report card for C++ project..
Syed Muhammad Zeejah Hashmi
 
BeEF
BeEFBeEF
Overview of Student Management System Components-eduWare
Overview of Student Management System Components-eduWareOverview of Student Management System Components-eduWare
Overview of Student Management System Components-eduWare
Arth InfoSoft P. Ltd.
 
Students record keeping system
Students record keeping systemStudents record keeping system
Students record keeping system
Mia Manik
 
Paybis - Crypto as payment method
Paybis - Crypto as payment methodPaybis - Crypto as payment method
Paybis - Crypto as payment method
Konstantins Vasilenko
 
Software AG’s webMethods Integration Cloud: Integrate Cloud Apps with ease
Software AG’s webMethods Integration Cloud: Integrate Cloud Apps with ease Software AG’s webMethods Integration Cloud: Integrate Cloud Apps with ease
Software AG’s webMethods Integration Cloud: Integrate Cloud Apps with ease
Kellton Tech Solutions Ltd
 
Cross Site Scripting - Web Defacement Techniques
Cross Site Scripting - Web Defacement TechniquesCross Site Scripting - Web Defacement Techniques
Cross Site Scripting - Web Defacement TechniquesRonan Dunne, CEH, SSCP
 
Secure Code Warrior - NoSQL injection
Secure Code Warrior - NoSQL injectionSecure Code Warrior - NoSQL injection
Secure Code Warrior - NoSQL injection
Secure Code Warrior
 
CS8651 IP Unit 3.pptx
CS8651 IP Unit 3.pptxCS8651 IP Unit 3.pptx
CS8651 IP Unit 3.pptx
Vigneshkumar Ponnusamy
 
SharePoint Online vs. On-Premise
SharePoint Online vs. On-PremiseSharePoint Online vs. On-Premise
SharePoint Online vs. On-Premise
Evan Hodges
 
Application server
Application serverApplication server
Application server
nava rathna
 
Domino Server Health - Monitoring and Managing
 Domino Server Health - Monitoring and Managing Domino Server Health - Monitoring and Managing
Domino Server Health - Monitoring and Managing
Gabriella Davis
 
Internet Cookies
Internet CookiesInternet Cookies
Internet Cookies
anita gouda
 
Web School - School Management System
Web School - School Management SystemWeb School - School Management System
Web School - School Management System
aju a s
 
Employee Management System (EMS) Project Documentation
Employee Management System (EMS) Project DocumentationEmployee Management System (EMS) Project Documentation
Employee Management System (EMS) Project Documentation
Md. Rasel Hossain
 
Social networking project (2gether)
Social networking project (2gether)Social networking project (2gether)
Social networking project (2gether)
Niveditha Dhamodaran
 
Chat application android app ppt
Chat application android app pptChat application android app ppt
Chat application android app ppt
Zreena
 
DFIR Training: RDP Triage
DFIR Training: RDP TriageDFIR Training: RDP Triage
DFIR Training: RDP Triage
Christopher Gerritz
 
What is a Website?
What is a Website? What is a Website?
What is a Website?
Cardinal Web Solutions
 
Job Control Language
Job Control LanguageJob Control Language
Job Control Language
kapa rohit
 

What's hot (20)

Students report card for C++ project..
Students report card for C++ project..Students report card for C++ project..
Students report card for C++ project..
 
BeEF
BeEFBeEF
BeEF
 
Overview of Student Management System Components-eduWare
Overview of Student Management System Components-eduWareOverview of Student Management System Components-eduWare
Overview of Student Management System Components-eduWare
 
Students record keeping system
Students record keeping systemStudents record keeping system
Students record keeping system
 
Paybis - Crypto as payment method
Paybis - Crypto as payment methodPaybis - Crypto as payment method
Paybis - Crypto as payment method
 
Software AG’s webMethods Integration Cloud: Integrate Cloud Apps with ease
Software AG’s webMethods Integration Cloud: Integrate Cloud Apps with ease Software AG’s webMethods Integration Cloud: Integrate Cloud Apps with ease
Software AG’s webMethods Integration Cloud: Integrate Cloud Apps with ease
 
Cross Site Scripting - Web Defacement Techniques
Cross Site Scripting - Web Defacement TechniquesCross Site Scripting - Web Defacement Techniques
Cross Site Scripting - Web Defacement Techniques
 
Secure Code Warrior - NoSQL injection
Secure Code Warrior - NoSQL injectionSecure Code Warrior - NoSQL injection
Secure Code Warrior - NoSQL injection
 
CS8651 IP Unit 3.pptx
CS8651 IP Unit 3.pptxCS8651 IP Unit 3.pptx
CS8651 IP Unit 3.pptx
 
SharePoint Online vs. On-Premise
SharePoint Online vs. On-PremiseSharePoint Online vs. On-Premise
SharePoint Online vs. On-Premise
 
Application server
Application serverApplication server
Application server
 
Domino Server Health - Monitoring and Managing
 Domino Server Health - Monitoring and Managing Domino Server Health - Monitoring and Managing
Domino Server Health - Monitoring and Managing
 
Internet Cookies
Internet CookiesInternet Cookies
Internet Cookies
 
Web School - School Management System
Web School - School Management SystemWeb School - School Management System
Web School - School Management System
 
Employee Management System (EMS) Project Documentation
Employee Management System (EMS) Project DocumentationEmployee Management System (EMS) Project Documentation
Employee Management System (EMS) Project Documentation
 
Social networking project (2gether)
Social networking project (2gether)Social networking project (2gether)
Social networking project (2gether)
 
Chat application android app ppt
Chat application android app pptChat application android app ppt
Chat application android app ppt
 
DFIR Training: RDP Triage
DFIR Training: RDP TriageDFIR Training: RDP Triage
DFIR Training: RDP Triage
 
What is a Website?
What is a Website? What is a Website?
What is a Website?
 
Job Control Language
Job Control LanguageJob Control Language
Job Control Language
 

Similar to Live Streaming & Server Sent Events

Intro to Node
Intro to NodeIntro to Node
Intro to Node
Aaron Stannard
 
Spring Web Services: SOAP vs. REST
Spring Web Services: SOAP vs. RESTSpring Web Services: SOAP vs. REST
Spring Web Services: SOAP vs. REST
Sam 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 .NET
Gianluca 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 AsyncListener
Islam Sharabash
 
Intoduction to Play Framework
Intoduction to Play FrameworkIntoduction to Play Framework
Intoduction to Play Framework
Knoldus Inc.
 
Comet from JavaOne 2008
Comet from JavaOne 2008Comet from JavaOne 2008
Comet from JavaOne 2008
Joe 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 WebSockets
Yakov 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 Script
Thomas 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 2015
Masahiro Nagano
 
Monitoring und Metriken im Wunderland
Monitoring und Metriken im WunderlandMonitoring und Metriken im Wunderland
Monitoring und Metriken im Wunderland
D
 
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 Events
thepilif
 
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
soft-shake.ch
 

Similar to 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
 

More from tkramar

Lessons learned from SearchD development
Lessons learned from SearchD developmentLessons learned from SearchD development
Lessons learned from SearchD development
tkramar
 
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
 

More from 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
 

Recently uploaded

DevOps and Testing slides at DASA Connect
DevOps and Testing slides at DASA ConnectDevOps and Testing slides at DASA Connect
DevOps and Testing slides at DASA Connect
Kari Kakkonen
 
Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...
Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...
Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...
Thierry Lestable
 
From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...
From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...
From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...
Product School
 
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
UiPathCommunity
 
UiPath Test Automation using UiPath Test Suite series, part 3
UiPath Test Automation using UiPath Test Suite series, part 3UiPath Test Automation using UiPath Test Suite series, part 3
UiPath Test Automation using UiPath Test Suite series, part 3
DianaGray10
 
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdfFIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance
 
To Graph or Not to Graph Knowledge Graph Architectures and LLMs
To Graph or Not to Graph Knowledge Graph Architectures and LLMsTo Graph or Not to Graph Knowledge Graph Architectures and LLMs
To Graph or Not to Graph Knowledge Graph Architectures and LLMs
Paul Groth
 
JMeter webinar - integration with InfluxDB and Grafana
JMeter webinar - integration with InfluxDB and GrafanaJMeter webinar - integration with InfluxDB and Grafana
JMeter webinar - integration with InfluxDB and Grafana
RTTS
 
Bits & Pixels using AI for Good.........
Bits & Pixels using AI for Good.........Bits & Pixels using AI for Good.........
Bits & Pixels using AI for Good.........
Alison B. Lowndes
 
PCI PIN Basics Webinar from the Controlcase Team
PCI PIN Basics Webinar from the Controlcase TeamPCI PIN Basics Webinar from the Controlcase Team
PCI PIN Basics Webinar from the Controlcase Team
ControlCase
 
Transcript: Selling digital books in 2024: Insights from industry leaders - T...
Transcript: Selling digital books in 2024: Insights from industry leaders - T...Transcript: Selling digital books in 2024: Insights from industry leaders - T...
Transcript: Selling digital books in 2024: Insights from industry leaders - T...
BookNet Canada
 
Software Delivery At the Speed of AI: Inflectra Invests In AI-Powered Quality
Software Delivery At the Speed of AI: Inflectra Invests In AI-Powered QualitySoftware Delivery At the Speed of AI: Inflectra Invests In AI-Powered Quality
Software Delivery At the Speed of AI: Inflectra Invests In AI-Powered Quality
Inflectra
 
Elevating Tactical DDD Patterns Through Object Calisthenics
Elevating Tactical DDD Patterns Through Object CalisthenicsElevating Tactical DDD Patterns Through Object Calisthenics
Elevating Tactical DDD Patterns Through Object Calisthenics
Dorra BARTAGUIZ
 
The Art of the Pitch: WordPress Relationships and Sales
The Art of the Pitch: WordPress Relationships and SalesThe Art of the Pitch: WordPress Relationships and Sales
The Art of the Pitch: WordPress Relationships and Sales
Laura Byrne
 
Mission to Decommission: Importance of Decommissioning Products to Increase E...
Mission to Decommission: Importance of Decommissioning Products to Increase E...Mission to Decommission: Importance of Decommissioning Products to Increase E...
Mission to Decommission: Importance of Decommissioning Products to Increase E...
Product School
 
UiPath Test Automation using UiPath Test Suite series, part 4
UiPath Test Automation using UiPath Test Suite series, part 4UiPath Test Automation using UiPath Test Suite series, part 4
UiPath Test Automation using UiPath Test Suite series, part 4
DianaGray10
 
State of ICS and IoT Cyber Threat Landscape Report 2024 preview
State of ICS and IoT Cyber Threat Landscape Report 2024 previewState of ICS and IoT Cyber Threat Landscape Report 2024 preview
State of ICS and IoT Cyber Threat Landscape Report 2024 preview
Prayukth K V
 
GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...
GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...
GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...
James Anderson
 
GenAISummit 2024 May 28 Sri Ambati Keynote: AGI Belongs to The Community in O...
GenAISummit 2024 May 28 Sri Ambati Keynote: AGI Belongs to The Community in O...GenAISummit 2024 May 28 Sri Ambati Keynote: AGI Belongs to The Community in O...
GenAISummit 2024 May 28 Sri Ambati Keynote: AGI Belongs to The Community in O...
Sri Ambati
 
Monitoring Java Application Security with JDK Tools and JFR Events
Monitoring Java Application Security with JDK Tools and JFR EventsMonitoring Java Application Security with JDK Tools and JFR Events
Monitoring Java Application Security with JDK Tools and JFR Events
Ana-Maria Mihalceanu
 

Recently uploaded (20)

DevOps and Testing slides at DASA Connect
DevOps and Testing slides at DASA ConnectDevOps and Testing slides at DASA Connect
DevOps and Testing slides at DASA Connect
 
Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...
Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...
Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...
 
From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...
From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...
From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...
 
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
Dev Dives: Train smarter, not harder – active learning and UiPath LLMs for do...
 
UiPath Test Automation using UiPath Test Suite series, part 3
UiPath Test Automation using UiPath Test Suite series, part 3UiPath Test Automation using UiPath Test Suite series, part 3
UiPath Test Automation using UiPath Test Suite series, part 3
 
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdfFIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
 
To Graph or Not to Graph Knowledge Graph Architectures and LLMs
To Graph or Not to Graph Knowledge Graph Architectures and LLMsTo Graph or Not to Graph Knowledge Graph Architectures and LLMs
To Graph or Not to Graph Knowledge Graph Architectures and LLMs
 
JMeter webinar - integration with InfluxDB and Grafana
JMeter webinar - integration with InfluxDB and GrafanaJMeter webinar - integration with InfluxDB and Grafana
JMeter webinar - integration with InfluxDB and Grafana
 
Bits & Pixels using AI for Good.........
Bits & Pixels using AI for Good.........Bits & Pixels using AI for Good.........
Bits & Pixels using AI for Good.........
 
PCI PIN Basics Webinar from the Controlcase Team
PCI PIN Basics Webinar from the Controlcase TeamPCI PIN Basics Webinar from the Controlcase Team
PCI PIN Basics Webinar from the Controlcase Team
 
Transcript: Selling digital books in 2024: Insights from industry leaders - T...
Transcript: Selling digital books in 2024: Insights from industry leaders - T...Transcript: Selling digital books in 2024: Insights from industry leaders - T...
Transcript: Selling digital books in 2024: Insights from industry leaders - T...
 
Software Delivery At the Speed of AI: Inflectra Invests In AI-Powered Quality
Software Delivery At the Speed of AI: Inflectra Invests In AI-Powered QualitySoftware Delivery At the Speed of AI: Inflectra Invests In AI-Powered Quality
Software Delivery At the Speed of AI: Inflectra Invests In AI-Powered Quality
 
Elevating Tactical DDD Patterns Through Object Calisthenics
Elevating Tactical DDD Patterns Through Object CalisthenicsElevating Tactical DDD Patterns Through Object Calisthenics
Elevating Tactical DDD Patterns Through Object Calisthenics
 
The Art of the Pitch: WordPress Relationships and Sales
The Art of the Pitch: WordPress Relationships and SalesThe Art of the Pitch: WordPress Relationships and Sales
The Art of the Pitch: WordPress Relationships and Sales
 
Mission to Decommission: Importance of Decommissioning Products to Increase E...
Mission to Decommission: Importance of Decommissioning Products to Increase E...Mission to Decommission: Importance of Decommissioning Products to Increase E...
Mission to Decommission: Importance of Decommissioning Products to Increase E...
 
UiPath Test Automation using UiPath Test Suite series, part 4
UiPath Test Automation using UiPath Test Suite series, part 4UiPath Test Automation using UiPath Test Suite series, part 4
UiPath Test Automation using UiPath Test Suite series, part 4
 
State of ICS and IoT Cyber Threat Landscape Report 2024 preview
State of ICS and IoT Cyber Threat Landscape Report 2024 previewState of ICS and IoT Cyber Threat Landscape Report 2024 preview
State of ICS and IoT Cyber Threat Landscape Report 2024 preview
 
GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...
GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...
GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...
 
GenAISummit 2024 May 28 Sri Ambati Keynote: AGI Belongs to The Community in O...
GenAISummit 2024 May 28 Sri Ambati Keynote: AGI Belongs to The Community in O...GenAISummit 2024 May 28 Sri Ambati Keynote: AGI Belongs to The Community in O...
GenAISummit 2024 May 28 Sri Ambati Keynote: AGI Belongs to The Community in O...
 
Monitoring Java Application Security with JDK Tools and JFR Events
Monitoring Java Application Security with JDK Tools and JFR EventsMonitoring Java Application Security with JDK Tools and JFR Events
Monitoring Java Application Security with JDK Tools and JFR Events
 

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