Class: SseRailsEngine::Manager

Inherits:
Object
  • Object
show all
Defined in:
lib/sse_rails_engine/manager.rb

Overview

This class provides the ability to track SSE connections and broadcast events to all connected clients from anywhere in your Rails app.

Example Usage:

class MyController < ActionController::Base
  def do_stuff
    SseRailsEngine.send_event('event name', 'any ruby object or string for data')
  end
end

Note: SSEs are not currently supported by IE.

Constant Summary collapse

RackHijackUnsupported =
Class.new RuntimeError
SSE_HEADER =
["HTTP/1.1 200 OK\r\n",
"Content-Type: text/event-stream\r\n",
"Cache-Control: no-cache, no-store\r\n",
"Connection: close\r\n",
"\r\n"].join.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeManager

Returns a new instance of Manager.



25
26
27
28
29
# File 'lib/sse_rails_engine/manager.rb', line 25

def initialize
  @mutex = Mutex.new
  @connections = {}
  start_heartbeats
end

Instance Attribute Details

#connectionsObject (readonly)

Returns the value of attribute connections.



17
18
19
# File 'lib/sse_rails_engine/manager.rb', line 17

def connections
  @connections
end

#heartbeat_threadObject (readonly)

Returns the value of attribute heartbeat_thread.



17
18
19
# File 'lib/sse_rails_engine/manager.rb', line 17

def heartbeat_thread
  @heartbeat_thread
end

Instance Method Details

#call(env) ⇒ Object



69
70
71
72
# File 'lib/sse_rails_engine/manager.rb', line 69

def call(env)
  SseRailsEngine.manager.register(env)
  [-1, {}, []]
end

#open_connection(io) ⇒ Object

rubocop:enable Metrics/MethodLength



61
62
63
64
65
66
67
# File 'lib/sse_rails_engine/manager.rb', line 61

def open_connection(io)
  Rails.logger.debug "New SSE Client connected: #{io}"
  io.write(SSE_HEADER)
  @mutex.synchronize do
    @connections[io] = ActionController::Live::SSE.new(io)
  end
end

#register(env) ⇒ Object



31
32
33
34
35
36
37
38
39
40
# File 'lib/sse_rails_engine/manager.rb', line 31

def register(env)
  if env['rack.hijack']
    env['rack.hijack'].call
    socket = env['rack.hijack_io']
    # Perform full hijack of socket (http://old.blog.phusion.nl/2013/01/23/the-new-rack-socket-hijacking-api/)
    SseRailsEngine.manager.open_connection(socket)
  else
    fail RackHijackUnsupported, 'This Rack server does not support hijacking, ensure you are using >= v1.5 of Rack'
  end
end

#send_event(name, data) ⇒ Object

rubocop:disable Metrics/MethodLength



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/sse_rails_engine/manager.rb', line 43

def send_event(name, data)
  @mutex.synchronize do
    @connections.dup.each do |stream, sse|
      begin
        sse.write(data, event: name)
        stream.flush
      rescue IOError, Errno::EPIPE, Errno::ETIMEDOUT
        Rails.logger.debug "SSE Client disconnected: #{stream}"
        close_connection(stream)
      rescue => ex
        Rails.logger.error "Failed to send event to SSE: #{stream} (#{name}, #{data} - #{ex.message} (#{ex.class}"
        close_connection(stream)
      end
    end
  end
end