firehose 0.0.3

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/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ .DS_Store
4
+ Gemfile.lock
5
+ log/*
6
+ pkg/*
data/.rbenv-version ADDED
@@ -0,0 +1 @@
1
+ 1.9.3-p125
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem 'goliath', :git => 'git://github.com/postrank-labs/goliath.git'
4
+ # Specify your gem's dependencies in firehose.gemspec
5
+ gemspec
data/Procfile ADDED
@@ -0,0 +1 @@
1
+ firehose: bundle exec thin -p $PORT -R config.ru --debug start
data/README.md ADDED
@@ -0,0 +1,105 @@
1
+ __ _ _
2
+ / _(_) | |
3
+ | |_ _ _ __ ___| |__ ___ ___ ___
4
+ | _| | '__/ _ \ '_ \ / _ \/ __|/ _ \
5
+ | | | | | | __/ | | | (_) \__ \ __/
6
+ |_| |_|_| \___|_| |_|\___/|___/\___|
7
+
8
+ Build Realtime web applications in Ruby
9
+
10
+ # What is Firehose?
11
+
12
+ Firehose is both a Rack application and JavasSript library that makes building scalable real-time web applications possible.
13
+
14
+ # How is it different from socket.io?
15
+
16
+ socket.io attempts to store connection state per node instance. Firehose makes no attempt to store connection state.
17
+
18
+ Also, socket.io attempts to abstract a low-latency full-duplex port. Firehose assumes that its impossible to simulate this in older web browsers that don't support WebSockets. As such, Firehose focuses on low-latency server-to-client connections and encourages the use of HTTP transports for client-to-server communications.
19
+
20
+ Finally, firehose attempts to solve data consistency issues and authentication by encourage the use of proxying to the web application.
21
+
22
+ # Getting Started
23
+
24
+ First, you'll need to install and run RabbitMQ.
25
+
26
+ ```
27
+ apt-get install rabbitmq # Install on Ubuntu
28
+ brew install rabbitmq # Install on Mac Homebrew
29
+ ```
30
+
31
+ ## The Consumer
32
+
33
+ The consumer is the web server that your client connects to for real-time updates. Create a config.ru file with the following:
34
+
35
+ ```ruby
36
+ require 'rubygems'
37
+ require 'firehose'
38
+
39
+ run Firehose::Transport::Dispatcher.new do |config|
40
+ config.timeout = 20
41
+
42
+ # Extract the consumer ID from the HTTP session. This could be a cookie
43
+ # query param, or whatever.
44
+ config.consumer = Proc.new do |env|
45
+ Firehose::Consumer.new(env['HTTP_CONSUMER_ID'])
46
+ end
47
+
48
+ # Use the /url/path for the queue channel. You could change this to a query
49
+ # param, or whatever
50
+ config.channel = Proc.new do |env|
51
+ env['PATH_INFO']
52
+ end
53
+ end
54
+ ```
55
+
56
+ Now run the config.ru file in a server that supports async Rack callbacks (like thin or rainbows)
57
+
58
+ ```ruby
59
+ thin -R config.ru -p 4000 start
60
+ ```
61
+
62
+ ## The Producer
63
+
64
+ Lets test the producer! Open two terminal windows. In one window, curl the consumer server:
65
+
66
+ ```sh
67
+ curl "http://localhost:4000/"
68
+ ```
69
+
70
+ Then run the following script in another terminal:
71
+
72
+ ```ruby
73
+ require 'rubygems'
74
+ require 'firehose'
75
+
76
+ Firehose::Producer.new.publish('hi there!').to('/greetings')
77
+ ```
78
+
79
+ ## JavaScript Client
80
+
81
+ Then in your browser create a new Firehose Client object as such:
82
+
83
+ ```javascript
84
+ new Firehose.Client()
85
+ .url({
86
+ websocket: 'ws://some_websocket_url.com',
87
+ longpoll: 'http://some_longpoll_url.com'
88
+ })
89
+ .params({
90
+ cid: '024023948234'
91
+ })
92
+ .options({
93
+ timeout: 5000
94
+ })
95
+ .message(function(msg){
96
+ alert(msg); // Fires when a message is received from the server.
97
+ })
98
+ .connected(function(){
99
+ alert('Howdy friend!');
100
+ })
101
+ .disconnected(function(){
102
+ alert('Bu bye');
103
+ })
104
+ .connect()
105
+ ```
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new
data/bin/firehose ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.expand_path('../../lib', __FILE__)
4
+
5
+ require 'rubygems'
6
+ require 'firehose'
data/bin/firehose-test ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.expand_path('../..', __FILE__)
4
+
5
+ require 'rubygems'
6
+ require 'test/long_poll_test'
data/config.ru ADDED
@@ -0,0 +1,6 @@
1
+ require 'firehose'
2
+ require 'rack'
3
+
4
+ use Rack::Reloader
5
+
6
+ run Firehose::Rack::App.new
data/firehose.gemspec ADDED
@@ -0,0 +1,36 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "firehose/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "firehose"
7
+ s.version = Firehose::VERSION
8
+ s.authors = ["Brad Gessler"]
9
+ s.email = ["brad@bradgessler.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{Build realtime Ruby web applications}
12
+ s.description = %q{Firehose is a realtime web application toolkit for building realtime Ruby web applications.}
13
+
14
+ s.rubyforge_project = "firehose"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ # s.add_runtime_dependency "goliath", "=> 0.9.4"
23
+ s.add_runtime_dependency "eventmachine", ">= 1.0.0.beta"
24
+ s.add_runtime_dependency "amqp", ">= 0.9.4"
25
+ s.add_runtime_dependency "thin"
26
+ s.add_runtime_dependency "websocket-rack"
27
+
28
+ s.add_development_dependency "rspec"
29
+ s.add_development_dependency "rack-test"
30
+ s.add_development_dependency "guard-rspec"
31
+ s.add_development_dependency "guard-bundler"
32
+ s.add_development_dependency "thin"
33
+ # em-http dropped support for WS as of version 1.0+ (https://github.com/igrigorik/em-http-request/issues/164)
34
+ s.add_development_dependency "em-http-request", "~> 0.3.0"
35
+ s.add_development_dependency "guard-coffeescript"
36
+ end
Binary file
data/lib/firehose.rb ADDED
@@ -0,0 +1,23 @@
1
+ require 'firehose/version'
2
+
3
+ require 'amqp'
4
+
5
+ module Firehose
6
+ autoload :Subscription, 'firehose/subscription'
7
+ autoload :Publisher, 'firehose/publisher'
8
+ autoload :Goliath, 'firehose/goliath'
9
+ autoload :Rack, 'firehose/rack'
10
+
11
+ # TODO move this into a configuration or session class.
12
+ # Hang on to AMQP configuration settings.
13
+ def self.amqp
14
+ @amqp ||= Struct.new(:connection).new(AMQP.connect)
15
+ end
16
+
17
+ # TODO figure out a better way to memoize AMQP connection for production runtimes, and
18
+ # make it resetable for testing environment. Some sort of Firehose::Session object is probably
19
+ # in order
20
+ def self.reset!
21
+ @amqp = nil
22
+ end
23
+ end
@@ -0,0 +1,69 @@
1
+ require 'goliath'
2
+ require 'goliath/websocket'
3
+
4
+ module Firehose
5
+ module Goliath
6
+ class WebSocket < ::Goliath::WebSocket
7
+ use ::Goliath::Rack::Params
8
+
9
+ def on_open(env)
10
+ # TODO Fix the Firehose::App app to not need '/ws' in front of the socket.
11
+ path = env['REQUEST_PATH'].gsub(/^\/ws/, '') # Name of the queue in AMQP we'll be pulling from.
12
+ cid = params[:cid]
13
+
14
+ @subscription = Firehose::Subscription.new(cid)
15
+ @subscription.subscribe path do |payload|
16
+ env.stream_send(payload)
17
+ end
18
+ end
19
+
20
+ def on_close(env)
21
+ @subscription.unsubscribe if @subscription
22
+ end
23
+ end
24
+
25
+ class LongPolling < ::Goliath::API
26
+ use ::Goliath::Rack::Params
27
+
28
+ def response(env)
29
+ method = env['REQUEST_METHOD'] # We use this to figure out if we're producing or consuming.
30
+ path = env['REQUEST_PATH'] # Name of the queue in AMQP we'll be pulling from.
31
+ cid = params[:cid]
32
+
33
+ case method
34
+ # GET is how clients subscribe to the queue. When a messages comes in, we flush out a response,
35
+ # close down the requeust, and the client then reconnects.
36
+ when 'GET'
37
+ subscription = Firehose::Subscription.new(cid)
38
+ subscription.subscribe path do |payload|
39
+ subscription.unsubscribe
40
+ env.chunked_stream_send(payload)
41
+ env.chunked_stream_close
42
+ end
43
+ chunked_streaming_response(200, 'Content-Type' => 'text/plain')
44
+ # PUT is how we throw messages on to the fan-out queue.
45
+ when 'PUT'
46
+ body = env['rack.input'].read
47
+ p [:put, path, body]
48
+ Firehose::Publisher.new.publish(path, body)
49
+
50
+ [202, {}, []]
51
+ else
52
+ [501, {}, ["#{method} not supported."]]
53
+ end
54
+ end
55
+
56
+ private
57
+ def self.connection
58
+ @connection ||= AMQP.connect
59
+ end
60
+ end
61
+
62
+ class App < ::Goliath::API
63
+ # TODO Figure out how to route this on schema (ws) or HTTP_UGPRADE header... it
64
+ # all uses HTTP router under the covers, so it should be doable.
65
+ map '/ws/*', WebSocket
66
+ map '/*', LongPolling
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,32 @@
1
+ require "net/http"
2
+ require "uri"
3
+
4
+ module Firehose
5
+ class HttpPublisher
6
+ attr_reader :uri
7
+
8
+ class RequestBuilder
9
+ attr_reader :uri, :message, :channel
10
+
11
+ def initialize(uri, message)
12
+ @uri, @message = uri, message
13
+ end
14
+
15
+ def to(channel)
16
+ req = Net::HTTP::Put.new(channel)
17
+ req.body = message
18
+ res = Net::HTTP.start(uri.host, uri.port) do |http|
19
+ response = http.request(req)
20
+ end
21
+ end
22
+ end
23
+
24
+ def initialize(uri)
25
+ @uri = URI.parse(uri)
26
+ end
27
+
28
+ def publish(message)
29
+ RequestBuilder.new(uri, message)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,12 @@
1
+ module Firehose
2
+ class Publisher
3
+ def publish(path, message)
4
+ channel = AMQP::Channel.new(Firehose.amqp.connection)
5
+ exchange = AMQP::Exchange.new(channel, :fanout, path, :auto_delete => true)
6
+ # TODO How do I clean up this exchange at this point? Do I close it somehow or the channel?
7
+ # The exchange just hangs out indefinitely now.
8
+ exchange.publish(message)
9
+ exchange.delete(:if_unused => true)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,76 @@
1
+ require 'rack/websocket'
2
+
3
+ module Firehose
4
+ module Rack
5
+ AsyncResponse = [-1, {}, []]
6
+
7
+ class HttpLongPoll
8
+ def call(env)
9
+ req = ::Rack::Request.new(env)
10
+ cid = req.params['cid']
11
+ path = req.path
12
+ method = req.request_method
13
+
14
+ case method
15
+ # GET is how clients subscribe to the queue. When a messages comes in, we flush out a response,
16
+ # close down the requeust, and the client then reconnects.
17
+ when 'GET'
18
+ EM.next_tick do
19
+ subscription = Firehose::Subscription.new(cid)
20
+ subscription.subscribe path do |payload|
21
+ subscription.unsubscribe
22
+ env['async.callback'].call([200, {}, [payload]])
23
+ end
24
+ end
25
+
26
+ Firehose::Rack::AsyncResponse
27
+
28
+ # PUT is how we throw messages on to the fan-out queue.
29
+ when 'PUT'
30
+ body = env['rack.input'].read
31
+ p [:put, path, body]
32
+ Firehose::Publisher.new.publish(path, body)
33
+
34
+ [202, {}, []]
35
+ else
36
+ [501, {}, ["#{method} not supported."]]
37
+ end
38
+ end
39
+ end
40
+
41
+ class WebSocket < ::Rack::WebSocket::Application
42
+ # Subscribe to a path and make some magic happen, mmkmay?
43
+ def on_open(env)
44
+ req = ::Rack::Request.new(env)
45
+ cid = req.params['cid']
46
+ path = req.path
47
+
48
+ @subscription = Firehose::Subscription.new(cid)
49
+ @subscription.subscribe path do |payload|
50
+ send_data payload
51
+ end
52
+ end
53
+
54
+ # Delete the subscription if the thing even happened.
55
+ def on_close(env)
56
+ @subscription.unsubscribe if @subscription
57
+ end
58
+
59
+ # Log websocket level errors
60
+ def on_error(env, error)
61
+ @subscription.unsubscribe if @subscription
62
+ end
63
+ end
64
+
65
+ class App
66
+ def call(env)
67
+ websocket_request?(env) ? WebSocket.new.call(env) : HttpLongPoll.new.call(env)
68
+ end
69
+
70
+ private
71
+ def websocket_request?(env)
72
+ env['HTTP_UPGRADE'] =~ /websocket/i
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,54 @@
1
+ require 'securerandom'
2
+
3
+ module Firehose
4
+ class Subscription
5
+ TTL = 15000
6
+
7
+ # Time to live for the queue on the server after the subscription is canceled. This
8
+ # is mostly for flakey connections where the client may reconnect after *ttl* and continue
9
+ # receiving messages.
10
+ attr_accessor :ttl
11
+
12
+ # Globally unique subscription id
13
+ attr_reader :subscriber_id
14
+
15
+ def initialize(subscriber_id=nil)
16
+ @subscriber_id = subscriber_id || self.class.subscriber_id
17
+ end
18
+
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
28
+ # timer on x-expires... in theory at least.
29
+ @consumer = AMQP::Consumer.new(channel, queue, subscriber_id)
30
+ @consumer.on_delivery do |metadata, payload|
31
+ p [:get, subscriber_id, @consumer.consumer_tag, path, payload]
32
+ block.call(payload)
33
+ # 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.
35
+ metadata.ack
36
+ end.consume
37
+ end
38
+
39
+ def unsubscribe
40
+ @consumer.cancel if @consumer
41
+ end
42
+
43
+ # The time that a queue should live *after* the client unsubscribes. This is useful for
44
+ # flakey network connections, like HTTP Long Polling or even broken web sockets.
45
+ def ttl
46
+ @ttl ||= TTL
47
+ end
48
+
49
+ protected
50
+ def self.subscriber_id
51
+ SecureRandom.uuid
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,7 @@
1
+ require 'thin'
2
+ require 'thin/websockets'
3
+
4
+ module Firehose
5
+ module Thin
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ module Firehose
2
+ VERSION = "0.0.3"
3
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Firehose amqp resources" do
4
+
5
+ let(:channel) { "/resource-test-#{Time.now.to_i}" }
6
+
7
+ it "should clean up exchanges and queues" do
8
+ sent, received = 'howdy!', nil
9
+
10
+ before_exchange_count = `rabbitmqctl list_exchanges`.lines.count
11
+ before_queue_count = `rabbitmqctl list_queues`.lines.count
12
+
13
+ during_exchange_count = nil
14
+ during_queue_count = nil
15
+
16
+ EM.run do
17
+ # Kill test if it runs longer than 5s
18
+ EM.add_timer(5) { EM.stop }
19
+
20
+ subscription = Firehose::Subscription.new
21
+ subscription.ttl = 1
22
+
23
+ subscription.subscribe channel do |payload|
24
+ received = payload
25
+ subscription.unsubscribe
26
+
27
+ during_exchange_count = `rabbitmqctl list_exchanges`.lines.count
28
+ during_queue_count = `rabbitmqctl list_queues`.lines.count
29
+
30
+ # I wait 1 second before killing em so that unsubscribe
31
+ # can talk to AMQP before the whole thing dies.
32
+ EM.add_timer(1){ EM.stop }
33
+ end
34
+
35
+ # Let the subscriber subscribe before publishing messages.
36
+ EM.add_timer(1){ Firehose::Publisher.new.publish(channel, sent) }
37
+ end
38
+
39
+ after_exchange_count = `rabbitmqctl list_exchanges`.lines.count
40
+ after_queue_count = `rabbitmqctl list_queues`.lines.count
41
+
42
+ received.should == sent
43
+
44
+ after_exchange_count.should == before_exchange_count
45
+ after_queue_count.should == before_queue_count
46
+
47
+ during_exchange_count.should == before_exchange_count + 1
48
+ during_queue_count.should == before_queue_count + 1
49
+ end
50
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+ require 'goliath'
3
+ require 'em-http'
4
+
5
+ describe Firehose::Goliath do
6
+ let(:app) { Firehose::Goliath::App.new }
7
+ let(:messages) { (1..1000).map(&:to_s) }
8
+ let(:channel) { "/firehose/integration/#{Time.now.to_i}" }
9
+ let(:uri) { URI.parse('http://127.0.0.1:9876') }
10
+ let(:url) { "#{uri}#{channel}" }
11
+ let(:cid) { "client-#{Time.now.to_i}" }
12
+
13
+ it "should pub-sub" do
14
+ outgoing, received = messages.dup, []
15
+
16
+ Goliath.env = :production
17
+ server = Goliath::Server.new
18
+ server.address = uri.host
19
+ server.port = uri.port
20
+ server.api = app
21
+ server.app = Goliath::Rack::Builder.build(Firehose::Goliath::App, server.api)
22
+ server.logger = Log4r::Logger.new('goliath')
23
+
24
+ server.start do
25
+ EM.add_timer(30) { EM.stop } # Stop the server no matter what happens.
26
+
27
+ publish = Proc.new do
28
+ http = EM::HttpRequest.new(url).put(:body => outgoing.pop)
29
+ http.errback { EM.stop }
30
+ http.callback { publish.call unless outgoing.empty? }
31
+ end
32
+
33
+ subscribe = Proc.new do
34
+ http = EM::HttpRequest.new(url).get(:query => {'cid' => cid})
35
+ http.errback { EM.stop }
36
+ http.callback do
37
+ received << http.response
38
+ if received.size < messages.size
39
+ subscribe.call
40
+ else
41
+ EM.stop
42
+ end
43
+ end
44
+ end
45
+
46
+ # Start the subscriber.
47
+ subscribe.call
48
+
49
+ # Wait a sec to let our subscribe setup.
50
+ EM.add_timer(1){ publish.call }
51
+ end
52
+
53
+ received.should =~ messages
54
+ end
55
+ end
@@ -0,0 +1,76 @@
1
+ require 'spec_helper'
2
+ require 'thin'
3
+ require 'em-http'
4
+
5
+ describe Firehose::Rack do
6
+ let(:app) { Firehose::Rack::App.new }
7
+ let(:messages) { (1..1000).map(&:to_s) }
8
+ let(:channel) { "/firehose/integration/#{Time.now.to_i}" }
9
+ let(:uri) { URI.parse('http://127.0.0.1:9876') }
10
+ let(:url) { "#{uri}#{channel}" }
11
+ let(:ws_url) { "ws://#{uri.host}:#{uri.port}#{channel}" }
12
+ let(:cid) { "client-#{Time.now.to_i}" }
13
+
14
+ it "should pub-sub http and websockets" do
15
+ # Setup variables that we'll use after we turn off EM to validate our
16
+ # test assertions.
17
+ outgoing, received_http, received_ws = messages.dup, [], []
18
+
19
+ # Our WS and Http clients call this when they have received their messages to determine
20
+ # when to turn off EM and make the test assertion at the very bottom.
21
+ succeed = Proc.new do
22
+ EM.stop if received_http.size == messages.size and received_ws.size == messages.size
23
+ end
24
+
25
+ # Setup a publisher
26
+ publish = Proc.new do
27
+ http = EM::HttpRequest.new(url).put(:body => outgoing.pop)
28
+ http.errback { EM.stop }
29
+ http.callback { publish.call unless outgoing.empty? }
30
+ end
31
+
32
+ # Lets have an HTTP Long poll client
33
+ http_long_poll = Proc.new do
34
+ http = EM::HttpRequest.new(url).get(:query => {'cid' => cid})
35
+ http.errback { EM.stop }
36
+ http.callback do
37
+ received_http << http.response
38
+ if received_http.size < messages.size
39
+ http_long_poll.call
40
+ else
41
+ succeed.call
42
+ end
43
+ end
44
+ end
45
+
46
+ # And test a web socket client too, at the same time.
47
+ websocket = Proc.new do
48
+ http = EventMachine::HttpRequest.new(ws_url).get
49
+ http.errback { EM.stop }
50
+ http.stream do |msg|
51
+ received_ws << msg
52
+ succeed.call unless received_ws.size < messages.size
53
+ end
54
+ end
55
+
56
+ # Great, we have all the pieces in order, lets run this thing in the reactor.
57
+ EM.run do
58
+ # Stop the server no matter what happens.
59
+ EM.add_timer(30) { EM.stop }
60
+
61
+ # Start the server
62
+ ::Thin::Server.new(uri.host, uri.port, app).start
63
+
64
+ # Start the http_long_pollr.
65
+ http_long_poll.call
66
+ websocket.call
67
+
68
+ # Wait a sec to let our http_long_poll setup.
69
+ EM.add_timer(1){ publish.call }
70
+ end
71
+
72
+ # When EM stops, these assertions will be made.
73
+ received_http.should =~ messages
74
+ received_ws.should =~ messages
75
+ end
76
+ end
@@ -0,0 +1,17 @@
1
+ require 'firehose'
2
+
3
+ # This file was generated by the `rspec --init` command. Conventionally, all
4
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
5
+ # Require this file using `require "spec_helper.rb"` to ensure that it is only
6
+ # loaded once.
7
+ #
8
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
9
+ RSpec.configure do |config|
10
+ config.treat_symbols_as_metadata_keys_with_true_values = true
11
+ config.run_all_when_everything_filtered = true
12
+ config.filter_run :focus
13
+ config.before(:each) do
14
+ # For now, this resets the AMQP configuration between runs.
15
+ Firehose.reset!
16
+ end
17
+ end
metadata ADDED
@@ -0,0 +1,197 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: firehose
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Brad Gessler
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-04-16 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: eventmachine
16
+ requirement: &70282339770020 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 1.0.0.beta
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70282339770020
25
+ - !ruby/object:Gem::Dependency
26
+ name: amqp
27
+ requirement: &70282339769280 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: 0.9.4
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70282339769280
36
+ - !ruby/object:Gem::Dependency
37
+ name: thin
38
+ requirement: &70282339768720 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70282339768720
47
+ - !ruby/object:Gem::Dependency
48
+ name: websocket-rack
49
+ requirement: &70282339768120 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *70282339768120
58
+ - !ruby/object:Gem::Dependency
59
+ name: rspec
60
+ requirement: &70282339767660 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *70282339767660
69
+ - !ruby/object:Gem::Dependency
70
+ name: rack-test
71
+ requirement: &70282339767140 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *70282339767140
80
+ - !ruby/object:Gem::Dependency
81
+ name: guard-rspec
82
+ requirement: &70282339766600 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: *70282339766600
91
+ - !ruby/object:Gem::Dependency
92
+ name: guard-bundler
93
+ requirement: &70282339765920 !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ type: :development
100
+ prerelease: false
101
+ version_requirements: *70282339765920
102
+ - !ruby/object:Gem::Dependency
103
+ name: thin
104
+ requirement: &70282339752660 !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: *70282339752660
113
+ - !ruby/object:Gem::Dependency
114
+ name: em-http-request
115
+ requirement: &70282339751820 !ruby/object:Gem::Requirement
116
+ none: false
117
+ requirements:
118
+ - - ~>
119
+ - !ruby/object:Gem::Version
120
+ version: 0.3.0
121
+ type: :development
122
+ prerelease: false
123
+ version_requirements: *70282339751820
124
+ - !ruby/object:Gem::Dependency
125
+ name: guard-coffeescript
126
+ requirement: &70282339750760 !ruby/object:Gem::Requirement
127
+ none: false
128
+ requirements:
129
+ - - ! '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: *70282339750760
135
+ description: Firehose is a realtime web application toolkit for building realtime
136
+ Ruby web applications.
137
+ email:
138
+ - brad@bradgessler.com
139
+ executables:
140
+ - firehose
141
+ - firehose-test
142
+ extensions: []
143
+ extra_rdoc_files: []
144
+ files:
145
+ - .gitignore
146
+ - .rbenv-version
147
+ - .rspec
148
+ - Gemfile
149
+ - Procfile
150
+ - README.md
151
+ - Rakefile
152
+ - bin/firehose
153
+ - bin/firehose-test
154
+ - config.ru
155
+ - firehose.gemspec
156
+ - lib/assets/flash/WebSocketMain.swf
157
+ - lib/firehose.rb
158
+ - lib/firehose/goliath.rb
159
+ - lib/firehose/http_publisher.rb
160
+ - lib/firehose/publisher.rb
161
+ - lib/firehose/rack.rb
162
+ - lib/firehose/subscription.rb
163
+ - lib/firehose/thin.rb
164
+ - lib/firehose/version.rb
165
+ - spec/integrations/amqp_resources_spec.rb
166
+ - spec/integrations/goliath_spec.rb
167
+ - spec/integrations/thin_spec.rb
168
+ - spec/spec_helper.rb
169
+ homepage: ''
170
+ licenses: []
171
+ post_install_message:
172
+ rdoc_options: []
173
+ require_paths:
174
+ - lib
175
+ required_ruby_version: !ruby/object:Gem::Requirement
176
+ none: false
177
+ requirements:
178
+ - - ! '>='
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ required_rubygems_version: !ruby/object:Gem::Requirement
182
+ none: false
183
+ requirements:
184
+ - - ! '>='
185
+ - !ruby/object:Gem::Version
186
+ version: '0'
187
+ requirements: []
188
+ rubyforge_project: firehose
189
+ rubygems_version: 1.8.11
190
+ signing_key:
191
+ specification_version: 3
192
+ summary: Build realtime Ruby web applications
193
+ test_files:
194
+ - spec/integrations/amqp_resources_spec.rb
195
+ - spec/integrations/goliath_spec.rb
196
+ - spec/integrations/thin_spec.rb
197
+ - spec/spec_helper.rb