firehose 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/.rbenv-version +1 -1
- data/README.md +1 -1
- data/config/rainbows.rb +4 -3
- data/lib/firehose.rb +6 -8
- data/lib/firehose/assets.rb +1 -3
- data/lib/firehose/cli.rb +6 -8
- data/lib/firehose/client.rb +2 -84
- data/lib/firehose/client/consumer.rb +94 -0
- data/lib/firehose/client/producer.rb +106 -0
- data/lib/firehose/logging.rb +1 -4
- data/lib/{rainbows_em_swf_policy.rb → firehose/patches/rainbows.rb} +4 -2
- data/lib/firehose/patches/swf_policy_request.rb +26 -0
- data/lib/{thin_em_swf_policy.rb → firehose/patches/thin.rb} +2 -1
- data/lib/firehose/rack.rb +12 -39
- data/lib/firehose/rack/app.rb +42 -0
- data/lib/firehose/rack/{consumer_app.rb → consumer.rb} +7 -5
- data/lib/firehose/rack/{ping_app.rb → ping.rb} +4 -2
- data/lib/firehose/rack/{publisher_app.rb → publisher.rb} +3 -3
- data/lib/firehose/server.rb +16 -42
- data/lib/firehose/server/app.rb +53 -0
- data/lib/firehose/server/channel.rb +80 -0
- data/lib/firehose/server/publisher.rb +134 -0
- data/lib/firehose/server/subscriber.rb +50 -0
- data/lib/firehose/version.rb +2 -2
- data/spec/integrations/integration_test_helper.rb +2 -2
- data/spec/integrations/shared_examples.rb +3 -3
- data/spec/lib/{client_spec.rb → client/consumer_spec.rb} +0 -0
- data/spec/lib/{producer_spec.rb → client/producer_spec.rb} +13 -13
- data/spec/lib/firehose_spec.rb +7 -0
- data/spec/lib/rack/{consumer_app_spec.rb → consumer_spec.rb} +2 -2
- data/spec/lib/rack/{ping_app_spec.rb → ping_spec.rb} +3 -3
- data/spec/lib/rack/{publisher_app_spec.rb → publisher_spec.rb} +3 -3
- data/spec/lib/server/app_spec.rb +1 -0
- data/spec/lib/{channel_spec.rb → server/channel_spec.rb} +4 -4
- data/spec/lib/{publisher_spec.rb → server/publisher_spec.rb} +9 -9
- data/spec/lib/{subscriber_spec.rb → server/subscriber_spec.rb} +4 -4
- data/spec/spec_helper.rb +0 -5
- metadata +38 -77
- data/lib/firehose/channel.rb +0 -84
- data/lib/firehose/default.rb +0 -8
- data/lib/firehose/producer.rb +0 -104
- data/lib/firehose/publisher.rb +0 -127
- data/lib/firehose/subscriber.rb +0 -54
- data/lib/firehose/swf_policy_request.rb +0 -23
- data/spec/lib/broker_spec.rb +0 -30
- data/spec/lib/consumer_spec.rb +0 -66
- data/spec/lib/default_spec.rb +0 -7
@@ -2,7 +2,7 @@
|
|
2
2
|
# Enable this with something like this in your config/rainbows.rb file:
|
3
3
|
#
|
4
4
|
# after_fork do |server, worker|
|
5
|
-
# require '
|
5
|
+
# require 'firehose/patches/rainbows'
|
6
6
|
# end if ENV['RACK_ENV'] == 'development'
|
7
7
|
#
|
8
8
|
# You should only use this in development. It has not been well tested in a
|
@@ -15,12 +15,14 @@
|
|
15
15
|
# http://www.adobe.com/devnet/flashplayer/articles/socket_policy_files.html
|
16
16
|
# http://blog.vokle.com/index.php/2009/06/10/dealing-with-adobe-and-serving-socket-policy-servers-via-nginx-and-10-lines-of-code/
|
17
17
|
|
18
|
+
require 'firehose/patches/swf_policy_request'
|
18
19
|
require 'rainbows'
|
20
|
+
|
19
21
|
# Ensure the class already exists so we are overwriting it.
|
20
22
|
Rainbows::EventMachine::Client
|
21
23
|
|
22
24
|
class Rainbows::EventMachine::Client
|
23
|
-
include Firehose::SwfPolicyRequest
|
25
|
+
include Firehose::Patches::SwfPolicyRequest
|
24
26
|
alias_method :receive_data_without_swf_policy, :receive_data
|
25
27
|
# Borrowed from: https://github.com/igrigorik/em-websocket/blob/3e7f7d7760cc23b9d1d34fc1c17bab4423b5d11a/lib/em-websocket/connection.rb#L104
|
26
28
|
def receive_data(data)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Firehose
|
2
|
+
module Patches
|
3
|
+
# Helpers for making Firehose work with Macromedia Flash Sockets. Since this doesn't use "normal" HTTP, we
|
4
|
+
# have to monkey patch both Rainbows and Thin to recognize when a request is for a SWF policy.
|
5
|
+
module SwfPolicyRequest
|
6
|
+
# Borrowed from: https://github.com/igrigorik/em-websocket/blob/3e7f7d7760cc23b9d1d34fc1c17bab4423b5d11a/lib/em-websocket/connection.rb#L104
|
7
|
+
def handle_swf_policy_request(data)
|
8
|
+
if data =~ /\A<policy-file-request\s*\/>/
|
9
|
+
Firehose.logger.debug "Received SWF Policy request: #{data.inspect}"
|
10
|
+
send_data policy
|
11
|
+
close_connection_after_writing
|
12
|
+
true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def policy
|
17
|
+
<<-EOS
|
18
|
+
<?xml version="1.0"?>
|
19
|
+
<cross-domain-policy>
|
20
|
+
<allow-access-from domain="*" to-ports="*"/>
|
21
|
+
</cross-domain-policy>
|
22
|
+
EOS
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -9,12 +9,13 @@
|
|
9
9
|
# http://www.adobe.com/devnet/flashplayer/articles/socket_policy_files.html
|
10
10
|
# http://blog.vokle.com/index.php/2009/06/10/dealing-with-adobe-and-serving-socket-policy-servers-via-nginx-and-10-lines-of-code/
|
11
11
|
|
12
|
+
require 'firehose/patches/swf_policy_request'
|
12
13
|
require 'thin'
|
13
14
|
# Ensure the class already exists so we are overwriting it.
|
14
15
|
Thin::Connection
|
15
16
|
|
16
17
|
class Thin::Connection
|
17
|
-
include Firehose::SwfPolicyRequest
|
18
|
+
include Firehose::Patches::SwfPolicyRequest
|
18
19
|
alias_method :receive_data_without_swf_policy, :receive_data
|
19
20
|
def receive_data(data)
|
20
21
|
if handle_swf_policy_request(data)
|
data/lib/firehose/rack.rb
CHANGED
@@ -1,55 +1,28 @@
|
|
1
1
|
module Firehose
|
2
2
|
module Rack
|
3
|
-
autoload :
|
4
|
-
autoload :
|
5
|
-
autoload :
|
3
|
+
autoload :Consumer, 'firehose/rack/consumer'
|
4
|
+
autoload :Publisher, 'firehose/rack/publisher'
|
5
|
+
autoload :Ping, 'firehose/rack/ping'
|
6
|
+
autoload :App, 'firehose/rack/app'
|
6
7
|
|
7
|
-
# Evented web servers recognize
|
8
|
+
# Evented web servers recognize the -1 HTTP code as a response deferral, which
|
9
|
+
# is needed to stream responses via WebSockets or HTTP long polling.
|
8
10
|
ASYNC_RESPONSE = [-1, {}, []].freeze
|
9
11
|
|
10
12
|
# Normally we'd want to use a custom header to reduce the likelihood of some
|
11
13
|
# HTTP middleware clobbering the value. But Safari seems to ignore our CORS
|
12
14
|
# header instructions, so we are using 'pragma' because it is always allowed.
|
13
15
|
LAST_MESSAGE_SEQUENCE_HEADER = 'Pragma'
|
14
|
-
RACK_LAST_MESSAGE_SEQUENCE_HEADER = "HTTP_#{LAST_MESSAGE_SEQUENCE_HEADER.upcase.gsub('-', '_')}"
|
15
|
-
# Don't cache in development mode
|
16
|
-
CORS_OPTIONS_MAX_AGE = ENV['RACK_ENV'] == 'development' ? '1' : '1728000'
|
17
16
|
|
18
|
-
#
|
19
|
-
|
20
|
-
def call(env)
|
21
|
-
# Cache the parsed request so we don't need to re-parse it when we pass
|
22
|
-
# control onto another app.
|
23
|
-
req = env['parsed_request'] ||= ::Rack::Request.new(env)
|
24
|
-
method = req.request_method
|
25
|
-
|
26
|
-
case method
|
27
|
-
when 'PUT'
|
28
|
-
publisher.call(env)
|
29
|
-
when 'HEAD'
|
30
|
-
ping.call(env)
|
31
|
-
else
|
32
|
-
consumer.call(env)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
|
37
|
-
private
|
38
|
-
def publisher
|
39
|
-
@publisher ||= PublisherApp.new
|
40
|
-
end
|
41
|
-
|
42
|
-
def consumer
|
43
|
-
@consumer ||= ConsumerApp.new
|
44
|
-
end
|
17
|
+
# Rack wants the header to start with HTTP, so we deal with that here.
|
18
|
+
RACK_LAST_MESSAGE_SEQUENCE_HEADER = "HTTP_#{LAST_MESSAGE_SEQUENCE_HEADER.upcase.gsub('-', '_')}"
|
45
19
|
|
46
|
-
|
47
|
-
|
48
|
-
end
|
49
|
-
end
|
20
|
+
# Disable CORS preflight caches for requests in development mode.
|
21
|
+
CORS_OPTIONS_MAX_AGE = ENV['RACK_ENV'] == 'development' ? '1' : '1728000'
|
50
22
|
|
51
23
|
module Helpers
|
52
|
-
# Calculates the content
|
24
|
+
# Calculates the content of a message body for the response so that HTTP Keep-Alive
|
25
|
+
# connections work.
|
53
26
|
def response(status, body='', headers={})
|
54
27
|
headers = {'Content-Length' => body.size.to_s}.merge(headers)
|
55
28
|
[status, headers, [body]]
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Firehose
|
2
|
+
module Rack
|
3
|
+
# Acts as the glue between the HTTP/WebSocket world and the Firehose::Server class,
|
4
|
+
# which talks directly to the Redis server. Also dispatches between HTTP and WebSocket
|
5
|
+
# transport handlers depending on the clients' request.
|
6
|
+
class App
|
7
|
+
def call(env)
|
8
|
+
# Cache the parsed request so we don't need to re-parse it when we pass
|
9
|
+
# control onto another app.
|
10
|
+
req = env['parsed_request'] ||= ::Rack::Request.new(env)
|
11
|
+
method = req.request_method
|
12
|
+
|
13
|
+
case method
|
14
|
+
when 'PUT'
|
15
|
+
# Firehose::Client::Publisher PUT's payloads to the server.
|
16
|
+
publisher.call(env)
|
17
|
+
when 'HEAD'
|
18
|
+
# HEAD requests are used to prevent sockets from timing out
|
19
|
+
# from inactivity
|
20
|
+
ping.call(env)
|
21
|
+
else
|
22
|
+
# TODO - 'harden' this up with a GET request and throw a "Bad Request"
|
23
|
+
# HTTP error code. I'd do it now but I'm in a plane and can't think of it.
|
24
|
+
consumer.call(env)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def publisher
|
30
|
+
@publisher ||= Publisher.new
|
31
|
+
end
|
32
|
+
|
33
|
+
def consumer
|
34
|
+
@consumer ||= Consumer.new
|
35
|
+
end
|
36
|
+
|
37
|
+
def ping
|
38
|
+
@ping ||= Ping.new
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -3,7 +3,10 @@ require 'json'
|
|
3
3
|
|
4
4
|
module Firehose
|
5
5
|
module Rack
|
6
|
-
|
6
|
+
# Handles a subscription request over HTTP or WebSockets depeding on its abilities and
|
7
|
+
# binds that to the Firehose::Server::Subscription class, which is bound to a channel that
|
8
|
+
# gets published to.
|
9
|
+
class Consumer
|
7
10
|
def call(env)
|
8
11
|
websocket_request?(env) ? websocket.call(env) : http_long_poll.call(env)
|
9
12
|
end
|
@@ -47,7 +50,7 @@ module Firehose
|
|
47
50
|
if last_sequence < 0
|
48
51
|
env['async.callback'].call response(400, "The last_message_sequence parameter may not be less than zero", response_headers(env))
|
49
52
|
else
|
50
|
-
Channel.new(path).next_message(last_sequence, :timeout => TIMEOUT).callback do |message, sequence|
|
53
|
+
Server::Channel.new(path).next_message(last_sequence, :timeout => TIMEOUT).callback do |message, sequence|
|
51
54
|
env['async.callback'].call response(200, wrap_frame(message, sequence), response_headers(env))
|
52
55
|
end.errback do |e|
|
53
56
|
if e == :timeout
|
@@ -107,11 +110,10 @@ module Firehose
|
|
107
110
|
return @ws.rack_response
|
108
111
|
end
|
109
112
|
|
110
|
-
|
111
|
-
|
113
|
+
private
|
112
114
|
def subscribe(last_sequence)
|
113
115
|
@subscribed = true
|
114
|
-
@channel = Channel.new @path
|
116
|
+
@channel = Server::Channel.new @path
|
115
117
|
@deferrable = @channel.next_message last_sequence
|
116
118
|
@deferrable.callback do |message, sequence|
|
117
119
|
Firehose.logger.debug "WS sent `#{message}` to `#{@path}` with sequence `#{sequence}`"
|
@@ -1,6 +1,9 @@
|
|
1
1
|
module Firehose
|
2
2
|
module Rack
|
3
|
-
|
3
|
+
# Allows the Firehose client to periodically "ping" the server
|
4
|
+
# so that the connection isn't timed out by browsers or proxies from
|
5
|
+
# inactivity.
|
6
|
+
class Ping
|
4
7
|
attr_reader :redis
|
5
8
|
|
6
9
|
def initialize(redis=nil)
|
@@ -12,7 +15,6 @@ module Firehose
|
|
12
15
|
ASYNC_RESPONSE
|
13
16
|
end
|
14
17
|
|
15
|
-
|
16
18
|
# Encapsulate this in a class so we aren't passing a bunch of variables around
|
17
19
|
class PingCheck
|
18
20
|
include Firehose::Rack::Helpers
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Firehose
|
2
2
|
module Rack
|
3
|
-
class
|
3
|
+
class Publisher
|
4
4
|
include Firehose::Rack::Helpers
|
5
5
|
|
6
6
|
def call(env)
|
@@ -44,9 +44,9 @@ module Firehose
|
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
|
-
|
47
|
+
private
|
48
48
|
def publisher
|
49
|
-
@publisher ||= Firehose::Publisher.new
|
49
|
+
@publisher ||= Firehose::Server::Publisher.new
|
50
50
|
end
|
51
51
|
end
|
52
52
|
end
|
data/lib/firehose/server.rb
CHANGED
@@ -1,48 +1,22 @@
|
|
1
1
|
require 'faye/websocket'
|
2
|
+
require 'em-hiredis'
|
2
3
|
|
3
|
-
|
4
|
-
|
5
|
-
def initialize(opts={})
|
6
|
-
@port = opts[:port] || Firehose::Default::URI.port
|
7
|
-
@host = opts[:host] || Firehose::Default::URI.host
|
8
|
-
@server = opts[:server] || :rainbows
|
9
|
-
|
10
|
-
Firehose.logger.info "Starting #{Firehose::VERSION} '#{Firehose::CODENAME}', in #{ENV['RACK_ENV']}"
|
11
|
-
end
|
12
|
-
|
13
|
-
def start
|
14
|
-
self.send("start_#{@server}")
|
15
|
-
end
|
16
|
-
|
17
|
-
private
|
18
|
-
def start_rainbows
|
19
|
-
require 'rainbows'
|
20
|
-
Faye::WebSocket.load_adapter('rainbows')
|
21
|
-
|
22
|
-
rackup = Unicorn::Configurator::RACKUP
|
23
|
-
rackup[:port] = @port if @port
|
24
|
-
rackup[:host] = @host if @host
|
25
|
-
rackup[:set_listener] = true
|
26
|
-
opts = rackup[:options]
|
27
|
-
opts[:config_file] = File.expand_path('../../../config/rainbows.rb', __FILE__)
|
4
|
+
# Set the EM::Hiredis logger to be the same as the Firehose logger.
|
5
|
+
EM::Hiredis.logger = Firehose.logger
|
28
6
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
server = Thin::Server.new(@host, @port) do
|
44
|
-
run Firehose::Rack::App.new
|
45
|
-
end.start
|
7
|
+
module Firehose
|
8
|
+
# Firehose components that sit between the Rack HTTP software and the Redis server.
|
9
|
+
# This mostly handles message sequencing and different HTTP channel names.
|
10
|
+
module Server
|
11
|
+
autoload :Subscriber, 'firehose/server/subscriber'
|
12
|
+
autoload :Publisher, 'firehose/server/publisher'
|
13
|
+
autoload :Channel, 'firehose/server/channel'
|
14
|
+
autoload :App, 'firehose/server/app'
|
15
|
+
|
16
|
+
# Generates keys for all firehose interactions with Redis. Ensures a root
|
17
|
+
# key of `firehose`
|
18
|
+
def self.key(*segments)
|
19
|
+
segments.unshift(:firehose).join(':')
|
46
20
|
end
|
47
21
|
end
|
48
22
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Firehose
|
2
|
+
module Server
|
3
|
+
# Configure servers that are booted with-out going through Rack. This is mostly used by
|
4
|
+
# the `firehose server` CLI command or for testing. Production configurations are likely
|
5
|
+
# to boot with custom rack configurations.
|
6
|
+
class App
|
7
|
+
def initialize(opts={})
|
8
|
+
@port = opts[:port] || Firehose::URI.port
|
9
|
+
@host = opts[:host] || Firehose::URI.host
|
10
|
+
@server = opts[:server] || :rainbows
|
11
|
+
|
12
|
+
Firehose.logger.info "Starting #{Firehose::VERSION} '#{Firehose::CODENAME}', in #{ENV['RACK_ENV']}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def start
|
16
|
+
self.send("start_#{@server}")
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
# Boot the Firehose server with the Rainbows app server.
|
21
|
+
def start_rainbows
|
22
|
+
require 'rainbows'
|
23
|
+
Faye::WebSocket.load_adapter('rainbows')
|
24
|
+
|
25
|
+
rackup = Unicorn::Configurator::RACKUP
|
26
|
+
rackup[:port] = @port if @port
|
27
|
+
rackup[:host] = @host if @host
|
28
|
+
rackup[:set_listener] = true
|
29
|
+
opts = rackup[:options]
|
30
|
+
opts[:config_file] = File.expand_path('../../../../config/rainbows.rb', __FILE__)
|
31
|
+
|
32
|
+
server = Rainbows::HttpServer.new(Firehose::Rack::App.new, opts)
|
33
|
+
server.start.join
|
34
|
+
end
|
35
|
+
|
36
|
+
# Boot the Firehose server with the Thin app server.
|
37
|
+
def start_thin
|
38
|
+
require 'thin'
|
39
|
+
require 'firehose/patches/thin' if ENV['RACK_ENV'] == 'development'
|
40
|
+
|
41
|
+
Faye::WebSocket.load_adapter('thin')
|
42
|
+
|
43
|
+
# TODO: See if we can just set Thin to use Firehose.logger instead of
|
44
|
+
# printing out messages by itself.
|
45
|
+
Thin::Logging.silent = true if Firehose.logger.level == Logger::ERROR
|
46
|
+
|
47
|
+
server = Thin::Server.new(@host, @port) do
|
48
|
+
run Firehose::Rack::App.new
|
49
|
+
end.start
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Firehose
|
2
|
+
module Server
|
3
|
+
# Connects to a specific channel on Redis and listens for messages to notify subscribers.
|
4
|
+
class Channel
|
5
|
+
attr_reader :channel_key, :redis, :subscriber, :list_key, :sequence_key
|
6
|
+
|
7
|
+
def self.redis
|
8
|
+
@redis ||= EM::Hiredis.connect
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.subscriber
|
12
|
+
@subscriber ||= Server::Subscriber.new(EM::Hiredis.connect)
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(channel_key, redis=self.class.redis, subscriber=self.class.subscriber)
|
16
|
+
@channel_key, @redis, @subscriber = channel_key, redis, subscriber
|
17
|
+
@list_key, @sequence_key = Server.key(channel_key, :list), Server.key(channel_key, :sequence)
|
18
|
+
end
|
19
|
+
|
20
|
+
def next_message(last_sequence=nil, options={})
|
21
|
+
last_sequence = last_sequence.to_i
|
22
|
+
|
23
|
+
deferrable = EM::DefaultDeferrable.new
|
24
|
+
# TODO - Think this through a little harder... maybe some tests ol buddy!
|
25
|
+
deferrable.errback {|e| EM.next_tick { raise e } unless [:timeout, :disconnect].include?(e) }
|
26
|
+
|
27
|
+
# TODO: Use HSET so we don't have to pull 100 messages back every time.
|
28
|
+
redis.multi
|
29
|
+
redis.get(sequence_key).
|
30
|
+
errback {|e| deferrable.fail e }
|
31
|
+
redis.lrange(list_key, 0, Server::Publisher::MAX_MESSAGES).
|
32
|
+
errback {|e| deferrable.fail e }
|
33
|
+
redis.exec.callback do |(sequence, message_list)|
|
34
|
+
Firehose.logger.debug "exec returened: `#{sequence}` and `#{message_list.inspect}`"
|
35
|
+
sequence = sequence.to_i
|
36
|
+
|
37
|
+
if sequence.nil? || (diff = sequence - last_sequence) <= 0
|
38
|
+
Firehose.logger.debug "No message available yet, subscribing. sequence: `#{sequence}`"
|
39
|
+
# Either this resource has never been seen before or we are all caught up.
|
40
|
+
# Subscribe and hope something gets published to this end-point.
|
41
|
+
subscribe(deferrable, options[:timeout])
|
42
|
+
elsif last_sequence > 0 && diff < Server::Publisher::MAX_MESSAGES
|
43
|
+
# The client is kinda-sorta running behind, but has a chance to catch
|
44
|
+
# up. Catch them up FTW.
|
45
|
+
# But we won't "catch them up" if last_sequence was zero/nil because
|
46
|
+
# that implies the client is connecting for the 1st time.
|
47
|
+
message = message_list[diff-1]
|
48
|
+
Firehose.logger.debug "Sending old message `#{message}` and sequence `#{sequence}` to client directly. Client is `#{diff}` behind, at `#{last_sequence}`."
|
49
|
+
deferrable.succeed message, last_sequence + 1
|
50
|
+
else
|
51
|
+
# The client is hopelessly behind and underwater. Just reset
|
52
|
+
# their whole world with the lastest message.
|
53
|
+
message = message_list[0]
|
54
|
+
Firehose.logger.debug "Sending latest message `#{message}` and sequence `#{sequence}` to client directly."
|
55
|
+
deferrable.succeed message, sequence
|
56
|
+
end
|
57
|
+
end.errback {|e| deferrable.fail e }
|
58
|
+
|
59
|
+
deferrable
|
60
|
+
end
|
61
|
+
|
62
|
+
def unsubscribe(deferrable)
|
63
|
+
subscriber.unsubscribe channel_key, deferrable
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
def subscribe(deferrable, timeout=nil)
|
68
|
+
subscriber.subscribe(channel_key, deferrable)
|
69
|
+
if timeout
|
70
|
+
timer = EventMachine::Timer.new(timeout) do
|
71
|
+
deferrable.fail :timeout
|
72
|
+
unsubscribe deferrable
|
73
|
+
end
|
74
|
+
# Cancel the timer if when the deferrable succeeds
|
75
|
+
deferrable.callback { timer.cancel }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|