firehose 0.0.15 → 0.0.16

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.
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
- run Firehose::Rack::App.new
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
- subscription = Firehose::Subscription.new(cid)
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
- subscription.subscribe path do |message|
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
- subscription.unsubscribe
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
- subscription.unsubscribe if subscription
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
- @subscription = Firehose::Subscription.new(cid)
86
- @subscription.subscribe path do |message|
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
- @subscription.unsubscribe if @subscription
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
- @subscription.unsubscribe if @subscription
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 queue on the server after the subscription is canceled. This
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
- # Globally unique subscription id
11
- attr_reader :subscriber_id
13
+ # Consumer and channel for the subscription.
14
+ attr_reader :consumer
12
15
 
13
- def initialize(subscriber_id=nil)
14
- @subscriber_id = subscriber_id || self.class.subscriber_id
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 path to an initializer so that we can force on AMQP subscription per one
18
- # Firehose subscription. As it stands now, you could fire off multple subscriptions to diff channels
19
- def subscribe(path, &block)
20
- queue_name = "#{subscriber_id}@#{path}"
21
- channel = AMQP::Channel.new(Firehose.amqp.connection).prefetch(1)
22
- exchange = AMQP::Exchange.new(channel, :fanout, path, :auto_delete => true)
23
- queue = AMQP::Queue.new(channel, queue_name, :arguments => {'x-expires' => ttl})
24
- queue.bind(exchange)
25
-
26
- # When we get a message, we want to remove the consumer from the queue so that the x-expires
27
- # ttl starts ticking down. On the reconnect, the consumer connects to the queue and resets 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
- @consumer = AMQP::Consumer.new(channel, queue, subscriber_id)
30
- @consumer.on_delivery do |metadata, message|
31
- Firehose.logger.debug "AMQP delivering `#{message}` to `#{subscriber_id}@#{path}`"
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 queue entirely.
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 `#{subscriber_id}@#{path}`"
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
- @consumer.cancel if @consumer
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 queue should live *after* the client unsubscribes. This is useful for
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
@@ -1,4 +1,4 @@
1
1
  module Firehose
2
- VERSION = "0.0.15"
3
- CODENAME = "Error Reportin' Roberto"
2
+ VERSION = "0.0.16"
3
+ CODENAME = "Statistical Sam"
4
4
  end
@@ -2,7 +2,8 @@ require 'spec_helper'
2
2
 
3
3
  describe "Firehose amqp resources" do
4
4
 
5
- let(:channel) { "/resource-test-#{Time.now.to_i}" }
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 channel do |payload|
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
- EM.stop if received_http.size == messages.size and received_ws.size == messages.size
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 { EM.stop }
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 { EM.stop }
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
- EM.run do
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
- EM.add_timer(1){ publish.call }
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
- # received_ws.should =~ messages
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
- # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
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.15
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-03 00:00:00.000000000 Z
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: &70353146415780 !ruby/object:Gem::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: *70353146415780
25
+ version_requirements: *70349732213400
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: amqp
28
- requirement: &70353146414160 !ruby/object:Gem::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: *70353146414160
36
+ version_requirements: *70349732212540
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: thin
39
- requirement: &70353146412800 !ruby/object:Gem::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: *70353146412800
47
+ version_requirements: *70349732211460
48
48
  - !ruby/object:Gem::Dependency
49
49
  name: thor
50
- requirement: &70353146411120 !ruby/object:Gem::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: *70353146411120
58
+ version_requirements: *70349732210400
59
59
  - !ruby/object:Gem::Dependency
60
60
  name: faraday
61
- requirement: &70353146454620 !ruby/object:Gem::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: *70353146454620
69
+ version_requirements: *70349732209120
70
70
  - !ruby/object:Gem::Dependency
71
71
  name: websocket-rack
72
- requirement: &70353146454100 !ruby/object:Gem::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: *70353146454100
80
+ version_requirements: *70349732223780
81
81
  - !ruby/object:Gem::Dependency
82
82
  name: em-http-request
83
- requirement: &70353146453540 !ruby/object:Gem::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: *70353146453540
91
+ version_requirements: *70349732222860
92
92
  - !ruby/object:Gem::Dependency
93
93
  name: rspec
94
- requirement: &70353146453060 !ruby/object:Gem::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: *70353146453060
102
+ version_requirements: *70349732221260
103
103
  - !ruby/object:Gem::Dependency
104
104
  name: webmock
105
- requirement: &70353146452280 !ruby/object:Gem::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: *70353146452280
113
+ version_requirements: *70349732220520
114
114
  - !ruby/object:Gem::Dependency
115
115
  name: guard-rspec
116
- requirement: &70353146451380 !ruby/object:Gem::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: *70353146451380
124
+ version_requirements: *70349732219740
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: guard-bundler
127
- requirement: &70353146448880 !ruby/object:Gem::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: *70353146448880
135
+ version_requirements: *70349732219100
136
136
  - !ruby/object:Gem::Dependency
137
137
  name: guard-coffeescript
138
- requirement: &70353146448120 !ruby/object:Gem::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: *70353146448120
146
+ version_requirements: *70349732218080
147
147
  - !ruby/object:Gem::Dependency
148
148
  name: em-websocket-client
149
- requirement: &70353146447120 !ruby/object:Gem::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: *70353146447120
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