firehose 1.1.1 → 1.2.0

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