firehose 1.1.1 → 1.2.0

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.
Files changed (48) hide show
  1. checksums.yaml +15 -0
  2. data/.rbenv-version +1 -1
  3. data/README.md +1 -1
  4. data/config/rainbows.rb +4 -3
  5. data/lib/firehose.rb +6 -8
  6. data/lib/firehose/assets.rb +1 -3
  7. data/lib/firehose/cli.rb +6 -8
  8. data/lib/firehose/client.rb +2 -84
  9. data/lib/firehose/client/consumer.rb +94 -0
  10. data/lib/firehose/client/producer.rb +106 -0
  11. data/lib/firehose/logging.rb +1 -4
  12. data/lib/{rainbows_em_swf_policy.rb → firehose/patches/rainbows.rb} +4 -2
  13. data/lib/firehose/patches/swf_policy_request.rb +26 -0
  14. data/lib/{thin_em_swf_policy.rb → firehose/patches/thin.rb} +2 -1
  15. data/lib/firehose/rack.rb +12 -39
  16. data/lib/firehose/rack/app.rb +42 -0
  17. data/lib/firehose/rack/{consumer_app.rb → consumer.rb} +7 -5
  18. data/lib/firehose/rack/{ping_app.rb → ping.rb} +4 -2
  19. data/lib/firehose/rack/{publisher_app.rb → publisher.rb} +3 -3
  20. data/lib/firehose/server.rb +16 -42
  21. data/lib/firehose/server/app.rb +53 -0
  22. data/lib/firehose/server/channel.rb +80 -0
  23. data/lib/firehose/server/publisher.rb +134 -0
  24. data/lib/firehose/server/subscriber.rb +50 -0
  25. data/lib/firehose/version.rb +2 -2
  26. data/spec/integrations/integration_test_helper.rb +2 -2
  27. data/spec/integrations/shared_examples.rb +3 -3
  28. data/spec/lib/{client_spec.rb → client/consumer_spec.rb} +0 -0
  29. data/spec/lib/{producer_spec.rb → client/producer_spec.rb} +13 -13
  30. data/spec/lib/firehose_spec.rb +7 -0
  31. data/spec/lib/rack/{consumer_app_spec.rb → consumer_spec.rb} +2 -2
  32. data/spec/lib/rack/{ping_app_spec.rb → ping_spec.rb} +3 -3
  33. data/spec/lib/rack/{publisher_app_spec.rb → publisher_spec.rb} +3 -3
  34. data/spec/lib/server/app_spec.rb +1 -0
  35. data/spec/lib/{channel_spec.rb → server/channel_spec.rb} +4 -4
  36. data/spec/lib/{publisher_spec.rb → server/publisher_spec.rb} +9 -9
  37. data/spec/lib/{subscriber_spec.rb → server/subscriber_spec.rb} +4 -4
  38. data/spec/spec_helper.rb +0 -5
  39. metadata +38 -77
  40. data/lib/firehose/channel.rb +0 -84
  41. data/lib/firehose/default.rb +0 -8
  42. data/lib/firehose/producer.rb +0 -104
  43. data/lib/firehose/publisher.rb +0 -127
  44. data/lib/firehose/subscriber.rb +0 -54
  45. data/lib/firehose/swf_policy_request.rb +0 -23
  46. data/spec/lib/broker_spec.rb +0 -30
  47. data/spec/lib/consumer_spec.rb +0 -66
  48. data/spec/lib/default_spec.rb +0 -7
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ Yzc5MDg5MDIyZWJmYjY1ZTZjM2FiYTMzNDIxNjczYmYwZWUzNTI2Yg==
5
+ data.tar.gz: !binary |-
6
+ YjU0ODhhNDk3MWMwZTg2NTJkMDE3Y2M1MGE2YjY1OWQzN2ZkNjdkYg==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ YTAwMDFmZDY3OWUxMjRhNDQ3ZDJkZmQzMmQyODZkZGFiZmUwMzQ0NWRkYjdm
10
+ OWFmMzVmOThmY2VmY2ZjNjJmMzViNzRiN2MyMjIzMDM0N2U0OGU0MDc0N2Iz
11
+ YjIwMDMyYWM3OTgzZDJmY2E1MDZmODZmY2QyYTVmYzEyYTExZmU=
12
+ data.tar.gz: !binary |-
13
+ M2ZlMTNiNGZjZTk1NDMyZDM4Y2EyY2M5MWFjYmEwY2E3YWQ1YmM2NTQ2OTU4
14
+ ZjEwNjgwZmQ1NjQyNDBmM2YyMWQwYzMxM2Y1MDc1NWQ1YzczM2M5ZTVmYWU3
15
+ YTk4ZjVmYTFhYTA3MDIwOGYzZjBkNjc3NDNmOWQ5OTA5NWFjNWM=
data/.rbenv-version CHANGED
@@ -1 +1 @@
1
- 1.9.3-p125
1
+ 1.9.3-p392
data/README.md CHANGED
@@ -112,7 +112,7 @@ While you can certainly make your own PUT requests when publishing messages, Fir
112
112
  require 'firehose'
113
113
  require 'json'
114
114
  json = {'hello'=> 'world'}.to_json
115
- firehose = Firehose::Producer.new('//127.0.0.1:7474')
115
+ firehose = Firehose::Client::Producer::Http.new('//127.0.0.1:7474')
116
116
  firehose.publish(json).to("/my/messages/path")
117
117
  ```
118
118
 
data/config/rainbows.rb CHANGED
@@ -1,5 +1,6 @@
1
- # TODO - Dunno what a lot of this stuff is... Tune with benchmarks
2
- # http://rainbows.rubyforge.org/Rainbows/Configurator.html
1
+ # Configuration from http://rainbows.rubyforge.org/Rainbows/Configurator.html. Don't juse
2
+ # blindly copy and paste this configuration! Be sure you have read and understand the Rainbows
3
+ # configuration documentation for your environment.
3
4
 
4
5
  Rainbows! do
5
6
  use :EventMachine # concurrency model
@@ -16,5 +17,5 @@ working_directory ENV['WORKING_DIRECTORY'] if ENV['WORKING_DIRECTORY']
16
17
  logger Firehose.logger
17
18
 
18
19
  after_fork do |server, worker|
19
- require 'rainbows_em_swf_policy'
20
+ require 'firehose/patches/rainbows'
20
21
  end if ENV['RACK_ENV'] == 'development'
data/lib/firehose.rb CHANGED
@@ -1,24 +1,22 @@
1
1
  ENV['RACK_ENV'] ||= 'development' # TODO - Lets not rock out envs like its 1999.
2
2
 
3
+ require 'uri'
3
4
  require 'firehose/version'
4
- require 'em-hiredis' # TODO Move this into a Redis module so that we can auto-load it. Lots of the CLI tools don't need this.
5
5
  require 'firehose/logging'
6
6
 
7
7
  # TODO - Figure if we need to have an if/else for Rails::Engine loading and Firehose::Assets::Sprockets.auto_detect
8
8
  require 'firehose/rails' if defined?(::Rails::Engine)
9
9
 
10
10
  module Firehose
11
- autoload :Subscriber, 'firehose/subscriber'
12
- autoload :Publisher, 'firehose/publisher'
13
- autoload :Producer, 'firehose/producer' # TODO Move this into the Firehose::Client namespace.
14
- autoload :Default, 'firehose/default'
11
+ autoload :Server, 'firehose/server'
12
+ autoload :Client, 'firehose/client'
15
13
  autoload :Assets, 'firehose/assets'
16
14
  autoload :Rack, 'firehose/rack'
17
15
  autoload :CLI, 'firehose/cli'
18
- autoload :Client, 'firehose/client'
19
- autoload :Server, 'firehose/server'
20
- autoload :Channel, 'firehose/channel'
21
16
  autoload :SwfPolicyRequest, 'firehose/swf_policy_request'
17
+
18
+ # Default URI for the Firehose server. Consider the port "well-known" and bindable from other apps.
19
+ URI = URI.parse("//0.0.0.0:7474").freeze
22
20
  end
23
21
 
24
22
  # Detect if Sprockets is loaded. If it is, lets configure Firehose to use it!
@@ -5,6 +5,7 @@ module Firehose
5
5
  File.join File.expand_path('../../assets', __FILE__), segs
6
6
  end
7
7
 
8
+ # Integrate Firehose ./lib/assets files into a sprocket-enabled environment.
8
9
  module Sprockets
9
10
  # Drop flash and javascript paths to Firehose assets into a sprockets environment.
10
11
  def self.configure(env)
@@ -22,11 +23,8 @@ module Firehose
22
23
 
23
24
  def self.manifest
24
25
  paths = []
25
-
26
26
  paths << Firehose::Assets.path('/flash/firehose/WebSocketMainInsecure.swf')
27
-
28
27
  paths << File.basename(Firehose::Assets.path('/javascripts/firehose/firehose.js.coffee'), '.coffee')
29
-
30
28
  paths
31
29
  end
32
30
  end
data/lib/firehose/cli.rb CHANGED
@@ -2,7 +2,7 @@ require 'thor'
2
2
  require 'eventmachine'
3
3
  require 'uri'
4
4
 
5
- # Enable native
5
+ # Enable native async-io libs
6
6
  EM.kqueue if EM.kqueue?
7
7
  EM.epoll if EM.epoll?
8
8
 
@@ -20,12 +20,12 @@ module Firehose
20
20
  end
21
21
 
22
22
  desc "server", "Start an instance of a server."
23
- method_option :port, :type => :numeric, :default => ENV['PORT'] || Firehose::Default::URI.port, :required => false, :aliases => '-p'
24
- method_option :host, :type => :string, :default => ENV['HOST'] || Firehose::Default::URI.host, :required => false, :aliases => '-h'
23
+ method_option :port, :type => :numeric, :default => ENV['PORT'] || Firehose::URI.port, :required => false, :aliases => '-p'
24
+ method_option :host, :type => :string, :default => ENV['HOST'] || Firehose::URI.host, :required => false, :aliases => '-h'
25
25
  method_option :server, :type => :string, :default => ENV['SERVER'] ||'rainbows', :required => false, :aliases => '-s'
26
26
  def server
27
27
  begin
28
- Firehose::Server.new(options).start
28
+ Firehose::Server::App.new(options).start
29
29
  rescue => e
30
30
  Firehose.logger.error "#{e.message}: #{e.backtrace}"
31
31
  raise e
@@ -44,11 +44,10 @@ module Firehose
44
44
  method_option :ttl, :type => :numeric, :aliases => '-t'
45
45
  method_option :times, :type => :numeric, :aliases => '-n', :default => 1
46
46
  method_option :interval, :type => :numeric, :aliases => '-i'
47
-
48
47
  def publish(uri, payload=nil)
49
48
  payload ||= $stdin.read
50
- client = Firehose::Producer.new(uri)
51
- path = URI.parse(uri).path
49
+ client = Firehose::Client::Producer::Http.new(uri)
50
+ path = ::URI.parse(uri).path
52
51
  times = options[:times]
53
52
  ttl = options[:ttl]
54
53
 
@@ -70,6 +69,5 @@ module Firehose
70
69
  end
71
70
  end
72
71
  end
73
-
74
72
  end
75
73
  end
@@ -4,89 +4,7 @@ require 'faye/websocket'
4
4
  module Firehose
5
5
  # Ruby clients that connect to Firehose to either publish or consume messages.
6
6
  module Client
7
- # TODO - Move the Firehose producer.rb file/class in here and rename to Firehose::Client::Producer::Http.new() ..
8
- module Producer
9
- end
10
-
11
- # TODO - Test this libs. I had to throw these quickly into our app so that we could get
12
- # some stress testing out of the way.
13
- # TODO - Replace the integration test clients with these guys. You'll want to refactor each
14
- # transport to use on(:message), on(:conncect), and on(:disconnect) callbacks.
15
- module Consumer
16
- TransportNotSupportedError = Class.new(RuntimeError)
17
-
18
- # Build up a benchmark client based on a given URI. Accepts ws:// and http:// for now.
19
- def self.parse(uri)
20
- case transport = URI.parse(uri).scheme
21
- when 'ws'
22
- Consumer::WebSocket.new(uri)
23
- when 'http'
24
- Consumer::HttpLongPoll.new(uri)
25
- else
26
- raise TransportNotSupportedError.new("Transport #{transport.inspect} not supported.")
27
- end
28
- end
29
-
30
- # Connect to Firehose via WebSockets and consume messages.
31
- class WebSocket
32
- attr_reader :url, :logger
33
-
34
- def initialize(url, logger = Firehose.logger)
35
- @url, @logger = url, logger
36
- end
37
-
38
- def request
39
- ws = Faye::WebSocket::Client.new(url)
40
- ws.onmessage = lambda do |event|
41
- logger.info "WS | #{event.data[0...40].inspect}"
42
- end
43
- ws.onclose = lambda do |event|
44
- logger.info "WS | Closed"
45
- end
46
- ws.onerror do
47
- logger.error "WS | Failed"
48
- end
49
- end
50
- end
51
-
52
- # Connect to Firehose via HTTP Long Polling and consume messages.
53
- class HttpLongPoll
54
- JITTER = 0.003
55
-
56
- attr_reader :url, :logger
57
-
58
- def initialize(url, logger = Firehose.logger)
59
- @url, @logger = url, logger
60
- end
61
-
62
- def request(last_sequence=0)
63
- http = EM::HttpRequest.new(url, :inactivity_timeout => 0).get(:query => {'last_message_sequence' => last_sequence})
64
- http.callback do
65
- case status = http.response_header.status
66
- when 200
67
- next_sequence = http.response_header['Pragma'].to_i
68
- logger.info "HTTP 200 | Next Sequence: #{next_sequence} - #{http.response[0...40].inspect}"
69
- EM::add_timer(jitter) { request next_sequence }
70
- when 204
71
- logger.info "HTTP 204 | Last Sequence #{last_sequence}"
72
- EM::add_timer(jitter) { request last_sequence }
73
- else
74
- logger.error "HTTP #{status} | Failed"
75
- end
76
- end
77
- http.errback do
78
- logger.error "Connection Failed"
79
- end
80
- end
81
-
82
-
83
- private
84
- # Random jitter between long poll requests.
85
- def jitter
86
- rand*JITTER
87
- end
88
- end
89
-
90
- end
7
+ autoload :Consumer, 'firehose/client/consumer'
8
+ autoload :Producer, 'firehose/client/producer'
91
9
  end
92
10
  end
@@ -0,0 +1,94 @@
1
+ require 'json'
2
+
3
+ # TODO - Spec this thing out. Unfortunately its not tested at all, mostly because the JSON client
4
+ # is more important (and tested). Still, this should be tested, at least used by the specs
5
+ # to test HTTP functionality of the server.
6
+ module Firehose
7
+ module Client
8
+ module Consumer
9
+ # TODO - Test this libs. I had to throw these quickly into our app so that we could get
10
+ # some stress testing out of the way.
11
+ # TODO - Replace the integration test clients with these guys. You'll want to refactor each
12
+ # transport to use on(:message), on(:conncect), and on(:disconnect) callbacks.
13
+ TransportNotSupportedError = Class.new(RuntimeError)
14
+
15
+ # Build up a benchmark client based on a given URI. Accepts ws:// and http:// for now.
16
+ def self.parse(uri)
17
+ case transport = ::URI.parse(uri).scheme
18
+ # TODO - Fix ws:// transport! See class WebSocket below to udnerstand
19
+ # why this doesn't work and support is dropped from the CLI.
20
+ # when 'ws'
21
+ # Consumer::WebSocket.new(uri)
22
+ when 'http'
23
+ Consumer::HttpLongPoll.new(uri)
24
+ else
25
+ raise TransportNotSupportedError.new("Transport #{transport.inspect} not supported.")
26
+ end
27
+ end
28
+
29
+ # TODO - This won't even work. Dropping ws:// above until this is tested. This thing
30
+ # should be sending message sequences to Firehose.
31
+ # Connect to Firehose via WebSockets and consume messages.
32
+ class WebSocket
33
+ attr_reader :url, :logger
34
+
35
+ def initialize(url, logger = Firehose.logger)
36
+ @url, @logger = url, logger
37
+ end
38
+
39
+ def request
40
+ ws = Faye::WebSocket::Client.new(url)
41
+ ws.onmessage = lambda do |event|
42
+ logger.info "WS | #{event.data[0...40].inspect}"
43
+ end
44
+ ws.onclose = lambda do |event|
45
+ logger.info "WS | Closed"
46
+ end
47
+ ws.onerror do
48
+ logger.error "WS | Failed"
49
+ end
50
+ end
51
+ end
52
+
53
+ # Connect to Firehose via HTTP Long Polling and consume messages.
54
+ class HttpLongPoll
55
+ JITTER = 0.003
56
+
57
+ attr_reader :url, :logger
58
+
59
+ def initialize(url, logger = Firehose.logger)
60
+ @url, @logger = url, logger
61
+ end
62
+
63
+ def request(last_sequence=0)
64
+ http = EM::HttpRequest.new(url, :inactivity_timeout => 0).get(:query => {'last_message_sequence' => last_sequence})
65
+ http.callback do
66
+ case status = http.response_header.status
67
+ when 200
68
+ json = JSON.parse(http.response)
69
+ next_sequence = json['last_sequence'].to_i
70
+ message = json['message']
71
+
72
+ logger.info "HTTP 200 | Next Sequence: #{next_sequence} - #{message[0...40].inspect}"
73
+ EM::add_timer(jitter) { request next_sequence }
74
+ when 204
75
+ logger.info "HTTP 204 | Last Sequence #{last_sequence}"
76
+ EM::add_timer(jitter) { request last_sequence }
77
+ else
78
+ logger.error "HTTP #{status} | Failed"
79
+ end
80
+ end
81
+ http.errback do
82
+ logger.error "Connection Failed"
83
+ end
84
+ end
85
+
86
+ private
87
+ # Random jitter between long poll requests.
88
+ def jitter
89
+ rand*JITTER
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,106 @@
1
+ require "faraday"
2
+ require "uri"
3
+
4
+ module Firehose
5
+ module Client
6
+ module Producer
7
+ # Publish messages to Firehose via an HTTP interface.
8
+ class Http
9
+ # Exception gets raised when a 202 is _not_ received from the server after a message is published.
10
+ PublishError = Class.new(RuntimeError)
11
+ TimeoutError = Class.new(Faraday::Error::TimeoutError)
12
+ Timeout = 1 # How many seconds should we wait for a publish to take?
13
+
14
+ # A DSL for publishing requests. This doesn't so much, but lets us call
15
+ # Firehose::Client::Producer::Http#publish('message').to('channel'). Slick eh? If you don't like it,
16
+ # just all Firehose::Client::Producer::Http#put('message', 'channel')
17
+ class Builder
18
+ def initialize(producer, message)
19
+ @producer, @message = producer, message
20
+ self
21
+ end
22
+
23
+ def to(channel, opts={}, &callback)
24
+ @producer.put(@message, channel, opts, &callback)
25
+ end
26
+ end
27
+
28
+ # URI for the Firehose server. This URI does not include the path of the channel.
29
+ attr_reader :uri
30
+
31
+ def initialize(uri = Firehose::URI)
32
+ @uri = ::URI.parse(uri.to_s)
33
+ @uri.scheme ||= 'http'
34
+ end
35
+
36
+ # A DSL for publishing messages.
37
+ def publish(message)
38
+ Builder.new(self, message)
39
+ end
40
+
41
+ # Publish the message via HTTP.
42
+ def put(message, channel, opts, &block)
43
+ ttl = opts[:ttl]
44
+
45
+ response = conn.put do |req|
46
+ req.options[:timeout] = Timeout
47
+ if conn.path_prefix.nil? || conn.path_prefix == '/'
48
+ # This avoids a double / if the channel starts with a / too (which is expected).
49
+ req.path = channel
50
+ else
51
+ if conn.path_prefix =~ /\/\Z/ || channel =~ /\A\//
52
+ req.path = [conn.path_prefix, channel].compact.join
53
+ else
54
+ # Add a / so the prefix and channel aren't just rammed together.
55
+ req.path = [conn.path_prefix, channel].compact.join('/')
56
+ end
57
+ end
58
+ req.body = message
59
+ req.headers['Cache-Control'] = "max-age=#{ttl.to_i}" if ttl
60
+ end
61
+ response.on_complete do
62
+ case response.status
63
+ when 202 # Fire off the callback if everything worked out OK.
64
+ block.call(response) if block
65
+ else
66
+ error_handler.call PublishError.new("Could not publish #{message.inspect} to '#{uri.to_s}/#{channel}': #{response.inspect}")
67
+ end
68
+ end
69
+
70
+ # Hide Faraday with this Timeout exception, and through the error handler.
71
+ rescue Faraday::Error::TimeoutError => e
72
+ error_handler.call TimeoutError.new(e)
73
+ end
74
+
75
+ # Handle errors that could happen while publishing a message.
76
+ def on_error(&block)
77
+ @error_handler = block
78
+ end
79
+
80
+ # Raise an exception if an error occurs when connecting to the Firehose.
81
+ def error_handler
82
+ @error_handler || Proc.new{ |e| raise e }
83
+ end
84
+
85
+ # What adapter should Firehose use to PUT the message? List of adapters is
86
+ # available at https://github.com/technoweenie/faraday.
87
+ def self.adapter=(adapter)
88
+ @adapter = adapter
89
+ end
90
+
91
+ # Use :net_http for the default Faraday adapter.
92
+ def self.adapter
93
+ @adapter ||= Faraday.default_adapter
94
+ end
95
+
96
+ private
97
+ # Build out a Faraday connection
98
+ def conn
99
+ @conn ||= Faraday.new(:url => uri.to_s) do |builder|
100
+ builder.adapter self.class.adapter
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -1,8 +1,7 @@
1
- # Sets up logging
2
-
3
1
  require 'logger'
4
2
 
5
3
  module Firehose
4
+ # Sets up logging
6
5
  def self.logger
7
6
  @logger ||= Logger.new($stdout)
8
7
  end
@@ -28,8 +27,6 @@ module Firehose
28
27
  end
29
28
  end
30
29
 
31
- EM::Hiredis.logger = Firehose.logger
32
-
33
30
  # stdout gets "lost" in Foreman if this isn't here
34
31
  # https://github.com/ddollar/foreman/wiki/Missing-Output
35
32
  $stdout.sync = true if ENV['RACK_ENV'] == 'development' || ENV['SYNC_LOGGING']