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