firehose 0.0.15 → 0.0.16
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/firehose.rb +4 -2
- data/lib/firehose/broker.rb +34 -0
- data/lib/firehose/cli.rb +15 -1
- data/lib/firehose/consumer.rb +49 -0
- data/lib/firehose/rack.rb +27 -11
- data/lib/firehose/subscription.rb +38 -29
- data/lib/firehose/version.rb +2 -2
- data/spec/integrations/amqp_resources_spec.rb +4 -3
- data/spec/integrations/thin_spec.rb +8 -9
- data/spec/lib/broker_spec.rb +30 -0
- data/spec/lib/consumer_spec.rb +66 -0
- data/spec/spec_helper.rb +21 -3
- metadata +34 -28
data/lib/firehose.rb
CHANGED
@@ -4,10 +4,12 @@ require 'amqp'
|
|
4
4
|
require 'logger'
|
5
5
|
|
6
6
|
module Firehose
|
7
|
-
autoload :Default, 'firehose/default'
|
8
7
|
autoload :Subscription, 'firehose/subscription'
|
9
|
-
autoload :Producer, 'firehose/producer'
|
10
8
|
autoload :Publisher, 'firehose/publisher'
|
9
|
+
autoload :Producer, 'firehose/producer'
|
10
|
+
autoload :Consumer, 'firehose/consumer'
|
11
|
+
autoload :Default, 'firehose/default'
|
12
|
+
autoload :Broker, 'firehose/broker'
|
11
13
|
autoload :Rack, 'firehose/rack'
|
12
14
|
autoload :CLI, 'firehose/cli'
|
13
15
|
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Firehose
|
2
|
+
# TODO add support to broker for publishing, then abstract this out into a backend. A
|
3
|
+
# broker will eventually be passed into a web server front-end to serve up the web requests.
|
4
|
+
class Broker
|
5
|
+
def consumers
|
6
|
+
@consumers ||= Hash.new do |consumers, consumer_id|
|
7
|
+
consumer = Firehose::Consumer.new(consumer_id)
|
8
|
+
consumer.on_unsubscribe do
|
9
|
+
consumers.delete consumer_id
|
10
|
+
end
|
11
|
+
consumers[consumer_id] = consumer
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Don't like the [] syntax to get at consumers? No worries mate!
|
16
|
+
def consumer(consumer_id)
|
17
|
+
consumers[consumer_id]
|
18
|
+
end
|
19
|
+
|
20
|
+
# Gracefully unsubscribe all of the consumers and get rid of them from the consumers
|
21
|
+
# collection.
|
22
|
+
def stop
|
23
|
+
consumers.values.each(&:unsubscribe)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns a hash of the connected consumers with the number of their subscriptions
|
27
|
+
def stats
|
28
|
+
consumers.inject({}) do |memo, (consumer_id, consumer)|
|
29
|
+
memo[consumer_id] = { 'subscriptions' => consumer.subscriptions.keys }
|
30
|
+
memo
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/firehose/cli.rb
CHANGED
@@ -6,9 +6,23 @@ module Firehose
|
|
6
6
|
desc "server", "starts the firehose server"
|
7
7
|
method_option :port, :type => :numeric, :default => Firehose::Default::URI.port, :required => true, :aliases => '-p'
|
8
8
|
method_option :host, :type => :string, :default => '0.0.0.0', :required => true, :aliases => '-h'
|
9
|
+
|
9
10
|
def server
|
11
|
+
broker = Firehose::Broker.new
|
12
|
+
|
10
13
|
server = Thin::Server.new(options[:host], options[:port]) do
|
11
|
-
|
14
|
+
# TODO move this into a socket... this is really janky, but I need to to troubleshoot
|
15
|
+
# connection reference issues. I'd like to have this ancillary stuff accessiable via
|
16
|
+
# a different port or even a socket.
|
17
|
+
map '/_firehose/stats' do
|
18
|
+
run Proc.new {
|
19
|
+
[200, {'Content-Type' => 'text/plain'}, [broker.stats.inspect]]
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
map '/' do
|
24
|
+
run Firehose::Rack::App.new(broker)
|
25
|
+
end
|
12
26
|
end
|
13
27
|
|
14
28
|
begin
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Firehose
|
2
|
+
class Consumer
|
3
|
+
# Unique identifier for a consumer. Note that a consumer does not map directly to
|
4
|
+
# a user_id. In a web browser world, you might have a user with multiple tabs open,
|
5
|
+
# so you'll went to send each users tab a seperate message stream. Consider a convention
|
6
|
+
# such as :user_id-:guid for your application.
|
7
|
+
attr_reader :guid
|
8
|
+
|
9
|
+
def initialize(guid = self.class.next_guid)
|
10
|
+
@guid = guid
|
11
|
+
end
|
12
|
+
|
13
|
+
# Create a subscription and subscribe to a channel.
|
14
|
+
def subscribe_to(channel, &block)
|
15
|
+
subscriptions[channel].subscribe(&block)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Active subscriptions to the backend.
|
19
|
+
def subscriptions
|
20
|
+
@subscriptions ||= Hash.new do |subscriptions, channel|
|
21
|
+
# Setup the hash to generate subscriptions that can delete themselves from
|
22
|
+
# their own collection on an unsubscription event.
|
23
|
+
subscription = Subscription.new(self, channel)
|
24
|
+
subscription.on_unsubscribe do
|
25
|
+
# Remove the subscription from the consumer.subscriptions
|
26
|
+
# list when unsubscribe.
|
27
|
+
subscriptions.delete channel
|
28
|
+
end
|
29
|
+
subscriptions[channel] = subscription
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Unsubscribe from all subscriptions.
|
34
|
+
def unsubscribe
|
35
|
+
subscriptions.values.each(&:unsubscribe)
|
36
|
+
@on_unsubscribe.call(self) if @on_unsubscribe
|
37
|
+
end
|
38
|
+
|
39
|
+
# Define callback for when unsubscribe is called from the consumer.
|
40
|
+
def on_unsubscribe(&block)
|
41
|
+
@on_unsubscribe = block
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
def self.next_guid
|
46
|
+
rand(999_999_999_999).to_s
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/firehose/rack.rb
CHANGED
@@ -5,12 +5,17 @@ module Firehose
|
|
5
5
|
AsyncResponse = [-1, {}, []]
|
6
6
|
|
7
7
|
class HttpLongPoll
|
8
|
+
def initialize(broker)
|
9
|
+
@broker = broker
|
10
|
+
end
|
11
|
+
|
8
12
|
def call(env)
|
9
13
|
req = ::Rack::Request.new(env)
|
10
14
|
cid = req.params['cid']
|
11
15
|
path = req.path
|
12
16
|
method = req.request_method
|
13
17
|
timeout = 30
|
18
|
+
# TODO seperate out CORS logic as an async middleware with a Goliath web server.
|
14
19
|
cors_origin = env['HTTP_ORIGIN']
|
15
20
|
cors_headers = {
|
16
21
|
'Access-Control-Allow-Origin' => cors_origin,
|
@@ -28,7 +33,7 @@ module Firehose
|
|
28
33
|
response_headers = cors_origin ? cors_headers : {}
|
29
34
|
|
30
35
|
# Setup a subscription with a client id. We haven't subscribed yet here.
|
31
|
-
|
36
|
+
consumer = @broker.consumer(cid)
|
32
37
|
|
33
38
|
# Setup a timeout timer to tell clients that time out that everything is OK
|
34
39
|
# and they should come back for more
|
@@ -39,10 +44,9 @@ module Firehose
|
|
39
44
|
end
|
40
45
|
|
41
46
|
# Ok, now subscribe to the subscription.
|
42
|
-
|
47
|
+
consumer.subscribe_to path do |message, subscription|
|
43
48
|
timer.cancel # Turn off the heart beat so we don't execute any of that business.
|
44
|
-
|
45
|
-
subscription = nil # Set this to nil so that our heart beat timer doesn't try to double unsub.
|
49
|
+
consumer.unsubscribe
|
46
50
|
env['async.callback'].call [200, response_headers, [message]]
|
47
51
|
Firehose.logger.debug "HTTP sent `#{message}` to `#{cid}@#{path}`"
|
48
52
|
end
|
@@ -51,7 +55,7 @@ module Firehose
|
|
51
55
|
# Unsubscribe from the subscription if its still open and something bad happened
|
52
56
|
# or the heart beat triggered before we could finish.
|
53
57
|
env['async.close'].callback do
|
54
|
-
|
58
|
+
consumer.unsubscribe if consumer
|
55
59
|
Firehose.logger.debug "HTTP connection `#{cid}@#{path}` closing"
|
56
60
|
end
|
57
61
|
end
|
@@ -76,14 +80,18 @@ module Firehose
|
|
76
80
|
class WebSocket < ::Rack::WebSocket::Application
|
77
81
|
attr_reader :cid, :path
|
78
82
|
|
83
|
+
def initialize(broker)
|
84
|
+
@broker = broker
|
85
|
+
end
|
86
|
+
|
79
87
|
# Subscribe to a path and make some magic happen, mmkmay?
|
80
88
|
def on_open(env)
|
81
89
|
req = ::Rack::Request.new(env)
|
82
90
|
@cid = req.params['cid']
|
83
91
|
@path = req.path
|
84
92
|
|
85
|
-
@
|
86
|
-
@
|
93
|
+
@consumer = @broker.consumer(@cid)
|
94
|
+
@consumer.subscribe_to path do |message|
|
87
95
|
Firehose.logger.debug "WS sent `#{message}` to `#{cid}@#{path}`"
|
88
96
|
send_data message
|
89
97
|
end
|
@@ -92,29 +100,37 @@ module Firehose
|
|
92
100
|
|
93
101
|
# Delete the subscription if the thing even happened.
|
94
102
|
def on_close(env)
|
95
|
-
@
|
103
|
+
@consumer.unsubscribe if @consumer
|
96
104
|
Firehose.logger.debug "WS connection `#{cid}@#{path}` closing"
|
97
105
|
end
|
98
106
|
|
99
107
|
# Log websocket level errors
|
100
108
|
def on_error(env, error)
|
101
109
|
Firehose.logger.error "WS connection `#{cid}@#{path}` error `#{error}`: #{env.inspect}"
|
102
|
-
@
|
110
|
+
@consumer.unsubscribe if @consumer
|
103
111
|
end
|
104
112
|
end
|
105
113
|
|
106
114
|
class App
|
115
|
+
# Firehose broker that will be used to pub/sub messages.
|
116
|
+
attr_reader :broker
|
117
|
+
|
118
|
+
# Fire up a default broker if one is not specified.
|
119
|
+
def initialize(broker = Firehose::Broker.new)
|
120
|
+
@broker = broker
|
121
|
+
end
|
122
|
+
|
107
123
|
def call(env)
|
108
124
|
websocket_request?(env) ? websocket.call(env) : http_long_poll.call(env)
|
109
125
|
end
|
110
126
|
|
111
127
|
private
|
112
128
|
def websocket
|
113
|
-
@websocket ||= WebSocket.new
|
129
|
+
@websocket ||= WebSocket.new(broker)
|
114
130
|
end
|
115
131
|
|
116
132
|
def http_long_poll
|
117
|
-
@http_long_poll ||= HttpLongPoll.new
|
133
|
+
@http_long_poll ||= HttpLongPoll.new(broker)
|
118
134
|
end
|
119
135
|
|
120
136
|
def websocket_request?(env)
|
@@ -1,56 +1,65 @@
|
|
1
1
|
module Firehose
|
2
2
|
class Subscription
|
3
|
+
# Default TTL for how long a subscription should live on the server when the
|
4
|
+
# consumer disconnects.
|
5
|
+
# TODO should the Consumer handle TTL?
|
3
6
|
TTL = 15000
|
4
7
|
|
5
|
-
# Time to live for the
|
8
|
+
# Time to live for the amqp_queue on the server after the subscription is canceled. This
|
6
9
|
# is mostly for flakey connections where the client may reconnect after *ttl* and continue
|
7
10
|
# receiving messages.
|
8
11
|
attr_accessor :ttl
|
9
12
|
|
10
|
-
#
|
11
|
-
attr_reader :
|
13
|
+
# Consumer and channel for the subscription.
|
14
|
+
attr_reader :consumer
|
12
15
|
|
13
|
-
|
14
|
-
|
16
|
+
# Channel that we'll use for the pub-sub activity. This probably maps to an URL
|
17
|
+
attr_reader :channel
|
18
|
+
|
19
|
+
def initialize(consumer, channel)
|
20
|
+
@consumer, @channel = consumer, channel
|
15
21
|
end
|
16
22
|
|
17
|
-
# TODO - Move the
|
18
|
-
# Firehose subscription. As it stands now, you could fire off multple subscriptions to diff
|
19
|
-
def subscribe(
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
# When we get a message, we want to remove the consumer from the
|
27
|
-
# ttl starts ticking down. On the reconnect, the consumer connects to the
|
23
|
+
# TODO - Move the channel to an initializer so that we can force on AMQP subscription per one
|
24
|
+
# Firehose subscription. As it stands now, you could fire off multple subscriptions to diff amqp_channels
|
25
|
+
def subscribe(&block)
|
26
|
+
amqp_queue_name = "#{consumer.guid}@#{channel}"
|
27
|
+
amqp_channel = AMQP::Channel.new(Firehose.amqp.connection).prefetch(1)
|
28
|
+
amqp_exchange = AMQP::Exchange.new(amqp_channel, :fanout, channel, :auto_delete => true)
|
29
|
+
amqp_queue = AMQP::Queue.new(amqp_channel, amqp_queue_name, :arguments => {'x-expires' => ttl})
|
30
|
+
amqp_queue.bind(amqp_exchange)
|
31
|
+
|
32
|
+
# When we get a message, we want to remove the consumer from the amqp_queue so that the x-expires
|
33
|
+
# ttl starts ticking down. On the reconnect, the consumer connects to the amqp_queue and resets the
|
28
34
|
# timer on x-expires... in theory at least.
|
29
|
-
@
|
30
|
-
@
|
31
|
-
Firehose.logger.debug "AMQP delivering `#{message}` to `#{
|
32
|
-
block.call(message)
|
35
|
+
@amqp_consumer = AMQP::Consumer.new(amqp_channel, amqp_queue, consumer.guid)
|
36
|
+
@amqp_consumer.on_delivery do |metadata, message|
|
37
|
+
Firehose.logger.debug "AMQP delivering `#{message}` to `#{consumer.guid}@#{channel}`"
|
38
|
+
block.call(message, self)
|
33
39
|
# The ack needs to go after the block is called. This makes sure that all processing
|
34
|
-
# happens downstream before we remove it from the
|
40
|
+
# happens downstream before we remove it from the amqp_queue entirely.
|
35
41
|
metadata.ack
|
36
42
|
end.consume
|
37
|
-
Firehose.logger.debug "AMQP subscribed to `#{
|
43
|
+
Firehose.logger.debug "AMQP subscribed to `#{consumer.guid}@#{channel}`"
|
44
|
+
self # Return the subscription for chaining.
|
38
45
|
end
|
39
46
|
|
40
47
|
def unsubscribe
|
41
48
|
Firehose.logger.debug "AMQP unsubscribed"
|
42
|
-
@
|
49
|
+
@amqp_consumer.cancel if @amqp_consumer
|
50
|
+
@unsubscribe_callback.call self if @unsubscribe_callback
|
51
|
+
end
|
52
|
+
|
53
|
+
# Callback when consumer unsubscribes from subscription. The consumer uses this to remove
|
54
|
+
# subscriptions from itself when an unsubscribe happens.
|
55
|
+
def on_unsubscribe(&block)
|
56
|
+
@unsubscribe_callback = block
|
43
57
|
end
|
44
58
|
|
45
|
-
# The time that a
|
59
|
+
# The time that a amqp_queue should live *after* the client unsubscribes. This is useful for
|
46
60
|
# flakey network connections, like HTTP Long Polling or even broken web sockets.
|
47
61
|
def ttl
|
48
62
|
@ttl ||= TTL
|
49
63
|
end
|
50
|
-
|
51
|
-
protected
|
52
|
-
def self.subscriber_id
|
53
|
-
rand(999_999_999_999).to_s
|
54
|
-
end
|
55
64
|
end
|
56
65
|
end
|
data/lib/firehose/version.rb
CHANGED
@@ -2,7 +2,8 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe "Firehose amqp resources" do
|
4
4
|
|
5
|
-
let(:channel)
|
5
|
+
let(:channel) { "/resource-test-#{Time.now.to_i}" }
|
6
|
+
let(:consumer) { Firehose::Consumer.new }
|
6
7
|
|
7
8
|
it "should clean up exchanges and queues" do
|
8
9
|
sent, received = 'howdy!', nil
|
@@ -17,10 +18,10 @@ describe "Firehose amqp resources" do
|
|
17
18
|
# Kill test if it runs longer than 5s
|
18
19
|
EM.add_timer(5) { EM.stop }
|
19
20
|
|
20
|
-
subscription = Firehose::Subscription.new
|
21
|
+
subscription = Firehose::Subscription.new(consumer, channel)
|
21
22
|
subscription.ttl = 1
|
22
23
|
|
23
|
-
subscription.subscribe
|
24
|
+
subscription.subscribe do |payload|
|
24
25
|
received = payload
|
25
26
|
subscription.unsubscribe
|
26
27
|
|
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Firehose::Rack do
|
4
|
+
include EM::TestHelper
|
5
|
+
|
4
6
|
before(:all) do
|
5
7
|
Firehose::Producer.adapter = :em_http
|
6
8
|
end
|
@@ -25,7 +27,7 @@ describe Firehose::Rack do
|
|
25
27
|
# Our WS and Http clients call this when they have received their messages to determine
|
26
28
|
# when to turn off EM and make the test assertion at the very bottom.
|
27
29
|
succeed = Proc.new do
|
28
|
-
|
30
|
+
em.stop if received_http.size == messages.size and received_ws.size == messages.size
|
29
31
|
end
|
30
32
|
|
31
33
|
# Setup a publisher
|
@@ -38,7 +40,7 @@ describe Firehose::Rack do
|
|
38
40
|
# Lets have an HTTP Long poll client
|
39
41
|
http_long_poll = Proc.new do
|
40
42
|
http = EM::HttpRequest.new(http_url).get(:query => {'cid' => cid})
|
41
|
-
http.errback {
|
43
|
+
http.errback { em.stop }
|
42
44
|
http.callback do
|
43
45
|
received_http << http.response
|
44
46
|
if received_http.size < messages.size
|
@@ -52,7 +54,7 @@ describe Firehose::Rack do
|
|
52
54
|
# And test a web socket client too, at the same time.
|
53
55
|
websocket = Proc.new do
|
54
56
|
ws = EventMachine::WebSocketClient.connect(ws_url)
|
55
|
-
ws.errback {
|
57
|
+
ws.errback { em.stop }
|
56
58
|
ws.stream do |msg|
|
57
59
|
received_ws << msg
|
58
60
|
succeed.call unless received_ws.size < messages.size
|
@@ -60,10 +62,7 @@ describe Firehose::Rack do
|
|
60
62
|
end
|
61
63
|
|
62
64
|
# Great, we have all the pieces in order, lets run this thing in the reactor.
|
63
|
-
|
64
|
-
# Stop the server no matter what happens.
|
65
|
-
EM.add_timer(30) { EM.stop }
|
66
|
-
|
65
|
+
em do
|
67
66
|
# Start the server
|
68
67
|
::Thin::Server.new('0.0.0.0', uri.port, app).start
|
69
68
|
|
@@ -72,11 +71,11 @@ describe Firehose::Rack do
|
|
72
71
|
websocket.call
|
73
72
|
|
74
73
|
# Wait a sec to let our http_long_poll setup.
|
75
|
-
|
74
|
+
em.add_timer(1){ publish.call }
|
76
75
|
end
|
77
76
|
|
78
77
|
# When EM stops, these assertions will be made.
|
79
78
|
received_http.should =~ messages
|
80
|
-
|
79
|
+
received_ws.should =~ messages
|
81
80
|
end
|
82
81
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Firehose::Broker do
|
4
|
+
include EM::TestHelper
|
5
|
+
|
6
|
+
let(:broker) { Firehose::Broker.new }
|
7
|
+
|
8
|
+
it "should unsubscibe consumers and remove them from the collection" do
|
9
|
+
stats = nil
|
10
|
+
|
11
|
+
em do
|
12
|
+
broker.consumer('1').subscribe_to('/the-channel')
|
13
|
+
broker.consumer('2').subscribe_to('/the-channel')
|
14
|
+
broker.consumer('2').subscribe_to('/a-channel')
|
15
|
+
|
16
|
+
em.add_timer(1) do
|
17
|
+
stats = broker.stats
|
18
|
+
broker.stop
|
19
|
+
em.stop
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
stats.should == {
|
24
|
+
'1' => {'subscriptions' => ['/the-channel'] },
|
25
|
+
'2' => {'subscriptions' => ['/the-channel', '/a-channel']}
|
26
|
+
}
|
27
|
+
|
28
|
+
broker.stats.should == {}
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Firehose::Consumer do
|
4
|
+
include EM::TestHelper
|
5
|
+
|
6
|
+
let(:consumer) { Firehose::Consumer.new }
|
7
|
+
let(:publisher) { Firehose::Publisher.new }
|
8
|
+
let(:channel) { '/papa-smurf' }
|
9
|
+
let(:another_channel) { '/mama-smurf' }
|
10
|
+
|
11
|
+
describe "subscriptions" do
|
12
|
+
it "should subscribe to channel" do
|
13
|
+
sent, recieved = 'hi', nil
|
14
|
+
|
15
|
+
em do
|
16
|
+
consumer.subscribe_to channel do |msg|
|
17
|
+
recieved = msg
|
18
|
+
em.stop
|
19
|
+
end
|
20
|
+
em.add_timer(1) do
|
21
|
+
publisher.publish(channel, sent)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
recieved.should == sent
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should track subscriptions" do
|
29
|
+
lambda{
|
30
|
+
em do
|
31
|
+
consumer.subscribe_to channel
|
32
|
+
consumer.subscribe_to another_channel
|
33
|
+
em.add_timer(1){ em.stop }
|
34
|
+
end
|
35
|
+
}.should change{ consumer.subscriptions.size }.by(2)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should only allow one subscription per channel" do
|
39
|
+
lambda{
|
40
|
+
em do
|
41
|
+
3.times { consumer.subscribe_to channel }
|
42
|
+
em.add_timer(1){ em.stop }
|
43
|
+
end
|
44
|
+
}.should change{ consumer.subscriptions.size }.by(1)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should unsubscribe from all channels" do
|
48
|
+
subscribed_count, after_unsubscribe_count = 0, nil
|
49
|
+
|
50
|
+
em do
|
51
|
+
consumer.subscribe_to channel
|
52
|
+
consumer.subscribe_to another_channel
|
53
|
+
subscribed_count = consumer.subscriptions.size
|
54
|
+
em.add_timer(1) do
|
55
|
+
consumer.unsubscribe
|
56
|
+
em.add_timer(1) do
|
57
|
+
em.stop
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
subscribed_count.should == 2
|
63
|
+
consumer.subscriptions.size.should == 0
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -3,18 +3,36 @@ require 'em-http'
|
|
3
3
|
require 'em-websocket-client'
|
4
4
|
|
5
5
|
require 'firehose'
|
6
|
-
Firehose.logger = Logger.new('/dev/null')
|
6
|
+
Firehose.logger = Logger.new('/dev/null') unless ENV['VERBOSE']
|
7
7
|
|
8
8
|
# Lets skip the verbosity of the thin for the test output.
|
9
9
|
require 'thin'
|
10
|
-
Thin::Logging.silent = true
|
10
|
+
Thin::Logging.silent = true unless ENV['VERBOSE']
|
11
11
|
|
12
12
|
# We use both EM::Http and Net::Http in this test lib. When EM:Http is fired up
|
13
13
|
# we're usually hitting thins for integrations, and when Net::Http we want to mock that up.
|
14
14
|
require 'webmock/rspec'
|
15
15
|
WebMock.allow_net_connect!
|
16
16
|
|
17
|
-
|
17
|
+
module EM::TestHelper
|
18
|
+
# Run test inside of reactor.
|
19
|
+
def em(ttl=30, &block)
|
20
|
+
if block
|
21
|
+
# Run the block inside of a reactor
|
22
|
+
EM.run do
|
23
|
+
EM.add_timer(ttl) do
|
24
|
+
EM.stop
|
25
|
+
raise "Test timed-out"
|
26
|
+
end
|
27
|
+
block.call(EM)
|
28
|
+
end
|
29
|
+
else # or just grab em and go nuts.
|
30
|
+
EM
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Configure RSpec runner. See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
18
36
|
RSpec.configure do |config|
|
19
37
|
config.treat_symbols_as_metadata_keys_with_true_values = true
|
20
38
|
config.run_all_when_everything_filtered = true
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: firehose
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.16
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,11 +10,11 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2012-05-
|
13
|
+
date: 2012-05-04 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: eventmachine
|
17
|
-
requirement: &
|
17
|
+
requirement: &70349732213400 !ruby/object:Gem::Requirement
|
18
18
|
none: false
|
19
19
|
requirements:
|
20
20
|
- - ! '>='
|
@@ -22,10 +22,10 @@ dependencies:
|
|
22
22
|
version: 1.0.0.beta
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
|
-
version_requirements: *
|
25
|
+
version_requirements: *70349732213400
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: amqp
|
28
|
-
requirement: &
|
28
|
+
requirement: &70349732212540 !ruby/object:Gem::Requirement
|
29
29
|
none: false
|
30
30
|
requirements:
|
31
31
|
- - ! '>='
|
@@ -33,10 +33,10 @@ dependencies:
|
|
33
33
|
version: 0.9.4
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
|
-
version_requirements: *
|
36
|
+
version_requirements: *70349732212540
|
37
37
|
- !ruby/object:Gem::Dependency
|
38
38
|
name: thin
|
39
|
-
requirement: &
|
39
|
+
requirement: &70349732211460 !ruby/object:Gem::Requirement
|
40
40
|
none: false
|
41
41
|
requirements:
|
42
42
|
- - ! '>='
|
@@ -44,10 +44,10 @@ dependencies:
|
|
44
44
|
version: '0'
|
45
45
|
type: :runtime
|
46
46
|
prerelease: false
|
47
|
-
version_requirements: *
|
47
|
+
version_requirements: *70349732211460
|
48
48
|
- !ruby/object:Gem::Dependency
|
49
49
|
name: thor
|
50
|
-
requirement: &
|
50
|
+
requirement: &70349732210400 !ruby/object:Gem::Requirement
|
51
51
|
none: false
|
52
52
|
requirements:
|
53
53
|
- - ! '>='
|
@@ -55,10 +55,10 @@ dependencies:
|
|
55
55
|
version: '0'
|
56
56
|
type: :runtime
|
57
57
|
prerelease: false
|
58
|
-
version_requirements: *
|
58
|
+
version_requirements: *70349732210400
|
59
59
|
- !ruby/object:Gem::Dependency
|
60
60
|
name: faraday
|
61
|
-
requirement: &
|
61
|
+
requirement: &70349732209120 !ruby/object:Gem::Requirement
|
62
62
|
none: false
|
63
63
|
requirements:
|
64
64
|
- - ! '>='
|
@@ -66,10 +66,10 @@ dependencies:
|
|
66
66
|
version: '0'
|
67
67
|
type: :runtime
|
68
68
|
prerelease: false
|
69
|
-
version_requirements: *
|
69
|
+
version_requirements: *70349732209120
|
70
70
|
- !ruby/object:Gem::Dependency
|
71
71
|
name: websocket-rack
|
72
|
-
requirement: &
|
72
|
+
requirement: &70349732223780 !ruby/object:Gem::Requirement
|
73
73
|
none: false
|
74
74
|
requirements:
|
75
75
|
- - ! '>='
|
@@ -77,10 +77,10 @@ dependencies:
|
|
77
77
|
version: '0'
|
78
78
|
type: :runtime
|
79
79
|
prerelease: false
|
80
|
-
version_requirements: *
|
80
|
+
version_requirements: *70349732223780
|
81
81
|
- !ruby/object:Gem::Dependency
|
82
82
|
name: em-http-request
|
83
|
-
requirement: &
|
83
|
+
requirement: &70349732222860 !ruby/object:Gem::Requirement
|
84
84
|
none: false
|
85
85
|
requirements:
|
86
86
|
- - ~>
|
@@ -88,10 +88,10 @@ dependencies:
|
|
88
88
|
version: 1.0.0
|
89
89
|
type: :runtime
|
90
90
|
prerelease: false
|
91
|
-
version_requirements: *
|
91
|
+
version_requirements: *70349732222860
|
92
92
|
- !ruby/object:Gem::Dependency
|
93
93
|
name: rspec
|
94
|
-
requirement: &
|
94
|
+
requirement: &70349732221260 !ruby/object:Gem::Requirement
|
95
95
|
none: false
|
96
96
|
requirements:
|
97
97
|
- - ! '>='
|
@@ -99,10 +99,10 @@ dependencies:
|
|
99
99
|
version: '0'
|
100
100
|
type: :development
|
101
101
|
prerelease: false
|
102
|
-
version_requirements: *
|
102
|
+
version_requirements: *70349732221260
|
103
103
|
- !ruby/object:Gem::Dependency
|
104
104
|
name: webmock
|
105
|
-
requirement: &
|
105
|
+
requirement: &70349732220520 !ruby/object:Gem::Requirement
|
106
106
|
none: false
|
107
107
|
requirements:
|
108
108
|
- - ! '>='
|
@@ -110,10 +110,10 @@ dependencies:
|
|
110
110
|
version: '0'
|
111
111
|
type: :development
|
112
112
|
prerelease: false
|
113
|
-
version_requirements: *
|
113
|
+
version_requirements: *70349732220520
|
114
114
|
- !ruby/object:Gem::Dependency
|
115
115
|
name: guard-rspec
|
116
|
-
requirement: &
|
116
|
+
requirement: &70349732219740 !ruby/object:Gem::Requirement
|
117
117
|
none: false
|
118
118
|
requirements:
|
119
119
|
- - ! '>='
|
@@ -121,10 +121,10 @@ dependencies:
|
|
121
121
|
version: '0'
|
122
122
|
type: :development
|
123
123
|
prerelease: false
|
124
|
-
version_requirements: *
|
124
|
+
version_requirements: *70349732219740
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
126
|
name: guard-bundler
|
127
|
-
requirement: &
|
127
|
+
requirement: &70349732219100 !ruby/object:Gem::Requirement
|
128
128
|
none: false
|
129
129
|
requirements:
|
130
130
|
- - ! '>='
|
@@ -132,10 +132,10 @@ dependencies:
|
|
132
132
|
version: '0'
|
133
133
|
type: :development
|
134
134
|
prerelease: false
|
135
|
-
version_requirements: *
|
135
|
+
version_requirements: *70349732219100
|
136
136
|
- !ruby/object:Gem::Dependency
|
137
137
|
name: guard-coffeescript
|
138
|
-
requirement: &
|
138
|
+
requirement: &70349732218080 !ruby/object:Gem::Requirement
|
139
139
|
none: false
|
140
140
|
requirements:
|
141
141
|
- - ! '>='
|
@@ -143,10 +143,10 @@ dependencies:
|
|
143
143
|
version: '0'
|
144
144
|
type: :development
|
145
145
|
prerelease: false
|
146
|
-
version_requirements: *
|
146
|
+
version_requirements: *70349732218080
|
147
147
|
- !ruby/object:Gem::Dependency
|
148
148
|
name: em-websocket-client
|
149
|
-
requirement: &
|
149
|
+
requirement: &70349732217260 !ruby/object:Gem::Requirement
|
150
150
|
none: false
|
151
151
|
requirements:
|
152
152
|
- - ! '>='
|
@@ -154,7 +154,7 @@ dependencies:
|
|
154
154
|
version: '0'
|
155
155
|
type: :development
|
156
156
|
prerelease: false
|
157
|
-
version_requirements: *
|
157
|
+
version_requirements: *70349732217260
|
158
158
|
description: Firehose is a realtime web application toolkit for building realtime
|
159
159
|
Ruby web applications.
|
160
160
|
email:
|
@@ -184,7 +184,9 @@ files:
|
|
184
184
|
- lib/assets/javascripts/firehose/transport.js.coffee
|
185
185
|
- lib/assets/javascripts/firehose/web_socket.js.coffee
|
186
186
|
- lib/firehose.rb
|
187
|
+
- lib/firehose/broker.rb
|
187
188
|
- lib/firehose/cli.rb
|
189
|
+
- lib/firehose/consumer.rb
|
188
190
|
- lib/firehose/default.rb
|
189
191
|
- lib/firehose/producer.rb
|
190
192
|
- lib/firehose/publisher.rb
|
@@ -193,6 +195,8 @@ files:
|
|
193
195
|
- lib/firehose/version.rb
|
194
196
|
- spec/integrations/amqp_resources_spec.rb
|
195
197
|
- spec/integrations/thin_spec.rb
|
198
|
+
- spec/lib/broker_spec.rb
|
199
|
+
- spec/lib/consumer_spec.rb
|
196
200
|
- spec/lib/default_spec.rb
|
197
201
|
- spec/lib/producer_spec.rb
|
198
202
|
- spec/spec_helper.rb
|
@@ -223,6 +227,8 @@ summary: Build realtime Ruby web applications
|
|
223
227
|
test_files:
|
224
228
|
- spec/integrations/amqp_resources_spec.rb
|
225
229
|
- spec/integrations/thin_spec.rb
|
230
|
+
- spec/lib/broker_spec.rb
|
231
|
+
- spec/lib/consumer_spec.rb
|
226
232
|
- spec/lib/default_spec.rb
|
227
233
|
- spec/lib/producer_spec.rb
|
228
234
|
- spec/spec_helper.rb
|