firehose 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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