message_bus 2.1.6 → 2.2.0.pre

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of message_bus might be problematic. Click here for more details.

Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +13 -92
  3. data/.rubocop_todo.yml +659 -0
  4. data/.travis.yml +1 -1
  5. data/CHANGELOG +61 -0
  6. data/Dockerfile +18 -0
  7. data/Gemfile +3 -1
  8. data/Guardfile +0 -1
  9. data/README.md +188 -101
  10. data/Rakefile +12 -1
  11. data/assets/message-bus.js +1 -1
  12. data/docker-compose.yml +46 -0
  13. data/examples/bench/config.ru +8 -9
  14. data/examples/bench/unicorn.conf.rb +1 -1
  15. data/examples/chat/chat.rb +150 -153
  16. data/examples/minimal/config.ru +2 -3
  17. data/lib/message_bus.rb +224 -36
  18. data/lib/message_bus/backends.rb +7 -0
  19. data/lib/message_bus/backends/base.rb +184 -0
  20. data/lib/message_bus/backends/memory.rb +304 -226
  21. data/lib/message_bus/backends/postgres.rb +359 -318
  22. data/lib/message_bus/backends/redis.rb +380 -337
  23. data/lib/message_bus/client.rb +99 -41
  24. data/lib/message_bus/connection_manager.rb +29 -21
  25. data/lib/message_bus/diagnostics.rb +50 -41
  26. data/lib/message_bus/distributed_cache.rb +5 -7
  27. data/lib/message_bus/message.rb +2 -2
  28. data/lib/message_bus/rack/diagnostics.rb +65 -55
  29. data/lib/message_bus/rack/middleware.rb +64 -44
  30. data/lib/message_bus/rack/thin_ext.rb +13 -9
  31. data/lib/message_bus/rails/railtie.rb +2 -0
  32. data/lib/message_bus/timer_thread.rb +2 -2
  33. data/lib/message_bus/version.rb +2 -1
  34. data/message_bus.gemspec +3 -2
  35. data/spec/assets/support/jasmine_helper.rb +1 -1
  36. data/spec/lib/fake_async_middleware.rb +1 -6
  37. data/spec/lib/message_bus/assets/asset_encoding_spec.rb +3 -3
  38. data/spec/lib/message_bus/backend_spec.rb +409 -0
  39. data/spec/lib/message_bus/client_spec.rb +8 -11
  40. data/spec/lib/message_bus/connection_manager_spec.rb +8 -14
  41. data/spec/lib/message_bus/distributed_cache_spec.rb +0 -4
  42. data/spec/lib/message_bus/multi_process_spec.rb +6 -7
  43. data/spec/lib/message_bus/rack/middleware_spec.rb +47 -43
  44. data/spec/lib/message_bus/timer_thread_spec.rb +0 -2
  45. data/spec/lib/message_bus_spec.rb +59 -43
  46. data/spec/spec_helper.rb +16 -4
  47. metadata +12 -9
  48. data/spec/lib/message_bus/backends/postgres_spec.rb +0 -221
  49. data/spec/lib/message_bus/backends/redis_spec.rb +0 -271
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class MessageBus::Message < Struct.new(:global_id, :message_id, :channel , :data)
4
-
3
+ # Represents a published message and its encoding for persistence.
4
+ class MessageBus::Message < Struct.new(:global_id, :message_id, :channel, :data)
5
5
  attr_accessor :site_id, :user_ids, :group_ids, :client_ids
6
6
 
7
7
  def self.decode(encoded)
@@ -1,68 +1,21 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module MessageBus::Rack; end
3
4
 
5
+ # Accepts requests from clients interested in using diagnostics functionality
6
+ # @see MessageBus::Diagnostics
4
7
  class MessageBus::Rack::Diagnostics
8
+ # @param [Proc] app the rack app
9
+ # @param [Hash] config
10
+ # @option config [MessageBus::Instance] :message_bus (`MessageBus`) a specific instance of message_bus
5
11
  def initialize(app, config = {})
6
12
  @app = app
7
13
  @bus = config[:message_bus] || MessageBus
8
14
  end
9
15
 
10
- def js_asset(name)
11
- return generate_script_tag(name) unless @bus.cache_assets
12
- @@asset_cache ||= {}
13
- @@asset_cache[name] ||= generate_script_tag(name)
14
- @@asset_cache[name]
15
- end
16
-
17
- def generate_script_tag(name)
18
- "<script src='/message-bus/_diagnostics/assets/#{name}?#{file_hash(name)}' type='text/javascript'></script>"
19
- end
20
-
21
- def file_hash(asset)
22
- require 'digest/sha1'
23
- Digest::SHA1.hexdigest(asset_contents(asset))
24
- end
25
-
26
- def asset_contents(asset)
27
- File.open(asset_path(asset)).read
28
- end
29
-
30
- def asset_path(asset)
31
- File.expand_path("../../../../assets/#{asset}", __FILE__)
32
- end
33
-
34
- def index
35
- html = <<HTML
36
- <!DOCTYPE html>
37
- <html>
38
- <head>
39
- </head>
40
- <body>
41
- <div id="app"></div>
42
- #{js_asset "jquery-1.8.2.js"}
43
- #{js_asset "handlebars.js"}
44
- #{js_asset "ember.js"}
45
- #{js_asset "message-bus.js"}
46
- #{js_asset "application.handlebars"}
47
- #{js_asset "index.handlebars"}
48
- #{js_asset "application.js"}
49
- </body>
50
- </html>
51
- HTML
52
- return [200, { "content-type" => "text/html;" }, [html]]
53
- end
54
-
55
- def translate_handlebars(name, content)
56
- "Ember.TEMPLATES['#{name}'] = Ember.Handlebars.compile(#{indent(content).inspect});"
57
- end
58
-
59
- # from ember-rails
60
- def indent(string)
61
- string.gsub(/$(.)/m, "\\1 ").strip
62
- end
63
-
16
+ # Process an HTTP request from a subscriber client
17
+ # @param [Rack::Request::Env] env the request environment
64
18
  def call(env)
65
-
66
19
  return @app.call(env) unless env['PATH_INFO'].start_with? '/message-bus/_diagnostics'
67
20
 
68
21
  route = env['PATH_INFO'].split('/message-bus/_diagnostics')[1]
@@ -97,4 +50,61 @@ HTML
97
50
 
98
51
  return [404, {}, ['not found']]
99
52
  end
53
+
54
+ private
55
+
56
+ def js_asset(name)
57
+ return generate_script_tag(name) unless @bus.cache_assets
58
+
59
+ @@asset_cache ||= {}
60
+ @@asset_cache[name] ||= generate_script_tag(name)
61
+ @@asset_cache[name]
62
+ end
63
+
64
+ def generate_script_tag(name)
65
+ "<script src='/message-bus/_diagnostics/assets/#{name}?#{file_hash(name)}' type='text/javascript'></script>"
66
+ end
67
+
68
+ def file_hash(asset)
69
+ require 'digest/sha1'
70
+ Digest::SHA1.hexdigest(asset_contents(asset))
71
+ end
72
+
73
+ def asset_contents(asset)
74
+ File.open(asset_path(asset)).read
75
+ end
76
+
77
+ def asset_path(asset)
78
+ File.expand_path("../../../../assets/#{asset}", __FILE__)
79
+ end
80
+
81
+ def index
82
+ html = <<~HTML
83
+ <!DOCTYPE html>
84
+ <html>
85
+ <head>
86
+ </head>
87
+ <body>
88
+ <div id="app"></div>
89
+ #{js_asset "jquery-1.8.2.js"}
90
+ #{js_asset "handlebars.js"}
91
+ #{js_asset "ember.js"}
92
+ #{js_asset "message-bus.js"}
93
+ #{js_asset "application.handlebars"}
94
+ #{js_asset "index.handlebars"}
95
+ #{js_asset "application.js"}
96
+ </body>
97
+ </html>
98
+ HTML
99
+ return [200, { "content-type" => "text/html;" }, [html]]
100
+ end
101
+
102
+ def translate_handlebars(name, content)
103
+ "Ember.TEMPLATES['#{name}'] = Ember.Handlebars.compile(#{indent(content).inspect});"
104
+ end
105
+
106
+ # from ember-rails
107
+ def indent(string)
108
+ string.gsub(/$(.)/m, "\\1 ").strip
109
+ end
100
110
  end
@@ -1,44 +1,49 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'json'
3
4
 
4
5
  # our little message bus, accepts long polling and polling
5
6
  module MessageBus::Rack; end
6
7
 
8
+ # Accepts requests from subscribers, validates and authenticates them,
9
+ # delivers existing messages from the backlog and informs a
10
+ # `MessageBus::ConnectionManager` of a connection which is remaining open.
7
11
  class MessageBus::Rack::Middleware
8
-
9
- def start_listener
10
- unless @started_listener
11
-
12
- thin = defined?(Thin::Server) && ObjectSpace.each_object(Thin::Server).to_a.first
13
- thin_running = thin && thin.running?
14
-
15
- @subscription = @bus.subscribe do |msg|
16
- run = proc do
17
- begin
18
- @connection_manager.notify_clients(msg) if @connection_manager
19
- rescue
20
- @bus.logger.warn "Failed to notify clients: #{$!} #{$!.backtrace}"
21
- end
22
- end
23
-
24
- if thin_running
25
- EM.next_tick(&run)
26
- else
27
- @bus.timer.queue(&run)
28
- end
29
-
30
- @started_listener = true
31
- end
32
- end
12
+ # @param [Array<MessageBus::Message>] backlog a list of messages for delivery
13
+ # @return [JSON] a JSON representation of the backlog, compliant with the
14
+ # subscriber API specification
15
+ def self.backlog_to_json(backlog)
16
+ m = backlog.map do |msg|
17
+ {
18
+ global_id: msg.global_id,
19
+ message_id: msg.message_id,
20
+ channel: msg.channel,
21
+ data: msg.data
22
+ }
23
+ end.to_a
24
+ JSON.dump(m)
33
25
  end
34
26
 
27
+ # @return [Boolean] whether the message listener (subscriber) is started or not)
28
+ attr_reader :started_listener
29
+
30
+ # Sets up the middleware to receive subscriber client requests and begins
31
+ # listening for messages published on the bus for re-distribution (unless
32
+ # the bus is disabled).
33
+ #
34
+ # @param [Proc] app the rack app
35
+ # @param [Hash] config
36
+ # @option config [MessageBus::Instance] :message_bus (`MessageBus`) a specific instance of message_bus
35
37
  def initialize(app, config = {})
36
38
  @app = app
37
39
  @bus = config[:message_bus] || MessageBus
38
40
  @connection_manager = MessageBus::ConnectionManager.new(@bus)
39
- self.start_listener
41
+ @started_listener = false
42
+ start_listener unless @bus.off?
40
43
  end
41
44
 
45
+ # Stops listening for messages on the bus
46
+ # @return [void]
42
47
  def stop_listener
43
48
  if @subscription
44
49
  @bus.unsubscribe(&@subscription)
@@ -46,20 +51,9 @@ class MessageBus::Rack::Middleware
46
51
  end
47
52
  end
48
53
 
49
- def self.backlog_to_json(backlog)
50
- m = backlog.map do |msg|
51
- {
52
- global_id: msg.global_id,
53
- message_id: msg.message_id,
54
- channel: msg.channel,
55
- data: msg.data
56
- }
57
- end.to_a
58
- JSON.dump(m)
59
- end
60
-
54
+ # Process an HTTP request from a subscriber client
55
+ # @param [Rack::Request::Env] env the request environment
61
56
  def call(env)
62
-
63
57
  return @app.call(env) unless env['PATH_INFO'] =~ /^\/message-bus\//
64
58
 
65
59
  # special debug/test route
@@ -136,9 +130,9 @@ class MessageBus::Rack::Middleware
136
130
  backlog = client.backlog
137
131
 
138
132
  if backlog.length > 0 && !allow_chunked
139
- client.cancel
133
+ client.close
140
134
  @bus.logger.debug "Delivering backlog #{backlog} to client #{client_id} for user #{user_id}"
141
- [200, headers, [self.class.backlog_to_json(backlog)] ]
135
+ [200, headers, [self.class.backlog_to_json(backlog)]]
142
136
  elsif long_polling && env['rack.hijack'] && @bus.rack_hijack_enabled?
143
137
  io = env['rack.hijack'].call
144
138
  # TODO disable client till deliver backlog is called
@@ -182,7 +176,6 @@ class MessageBus::Rack::Middleware
182
176
  else
183
177
  [200, headers, [self.class.backlog_to_json(backlog)]]
184
178
  end
185
-
186
179
  rescue => e
187
180
  if @bus.on_middleware_error && result = @bus.on_middleware_error.call(env, e)
188
181
  result
@@ -191,6 +184,8 @@ class MessageBus::Rack::Middleware
191
184
  end
192
185
  end
193
186
 
187
+ private
188
+
194
189
  def close_db_connection!
195
190
  # IMPORTANT
196
191
  # ConnectionManagement in Rails puts a BodyProxy around stuff
@@ -206,12 +201,37 @@ class MessageBus::Rack::Middleware
206
201
 
207
202
  client.cleanup_timer = @bus.timer.queue(@bus.long_polling_interval.to_f / 1000) {
208
203
  begin
209
- client.cleanup_timer = nil
210
- client.ensure_closed!
204
+ client.close
211
205
  @connection_manager.remove_client(client)
212
206
  rescue
213
207
  @bus.logger.warn "Failed to clean up client properly: #{$!} #{$!.backtrace}"
214
208
  end
215
209
  }
216
210
  end
211
+
212
+ def start_listener
213
+ unless @started_listener
214
+
215
+ thin = defined?(Thin::Server) && ObjectSpace.each_object(Thin::Server).to_a.first
216
+ thin_running = thin && thin.running?
217
+
218
+ @subscription = @bus.subscribe do |msg|
219
+ run = proc do
220
+ begin
221
+ @connection_manager.notify_clients(msg) if @connection_manager
222
+ rescue
223
+ @bus.logger.warn "Failed to notify clients: #{$!} #{$!.backtrace}"
224
+ end
225
+ end
226
+
227
+ if thin_running
228
+ EM.next_tick(&run)
229
+ else
230
+ @bus.timer.queue(&run)
231
+ end
232
+
233
+ @started_listener = true
234
+ end
235
+ end
236
+ end
217
237
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  # there is also another in cramp this is from https://github.com/macournoyer/thin_async/blob/master/lib/thin/async.rb
3
4
  module Thin
4
5
  unless defined?(DeferrableBody)
@@ -21,16 +22,19 @@ module Thin
21
22
  end
22
23
 
23
24
  private
24
- def schedule_dequeue
25
- return unless @body_callback
26
- ::EM.next_tick do
27
- next unless body = @queue.shift
28
- body.each do |chunk|
29
- @body_callback.call(chunk)
30
- end
31
- schedule_dequeue unless @queue.empty?
25
+
26
+ def schedule_dequeue
27
+ return unless @body_callback
28
+
29
+ ::EM.next_tick do
30
+ next unless body = @queue.shift
31
+
32
+ body.each do |chunk|
33
+ @body_callback.call(chunk)
32
34
  end
35
+ schedule_dequeue unless @queue.empty?
33
36
  end
37
+ end
34
38
  end
35
39
  end
36
40
 
@@ -51,6 +55,7 @@ module Thin
51
55
 
52
56
  def send_headers
53
57
  return if @headers_sent
58
+
54
59
  @callback.call [@status, @headers, @body]
55
60
  @headers_sent = true
56
61
  end
@@ -67,6 +72,5 @@ module Thin
67
72
  send_headers
68
73
  ::EM.next_tick { @body.succeed }
69
74
  end
70
-
71
75
  end
72
76
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module MessageBus; module Rails; end; end
3
4
 
4
5
  # rails engine for asset pipeline
@@ -25,6 +26,7 @@ class MessageBus::Rails::Railtie < ::Rails::Railtie
25
26
 
26
27
  def api_only?(config)
27
28
  return false unless config.respond_to?(:api_only)
29
+
28
30
  config.api_only
29
31
  end
30
32
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
- class MessageBus::TimerThread
3
2
 
3
+ class MessageBus::TimerThread
4
4
  attr_reader :jobs
5
5
 
6
6
  class Cancelable
@@ -16,6 +16,7 @@ class MessageBus::TimerThread
16
16
  def initialize(job)
17
17
  @job = job
18
18
  end
19
+
19
20
  def cancel
20
21
  @job[1] = NOOP
21
22
  end
@@ -123,5 +124,4 @@ class MessageBus::TimerThread
123
124
  end
124
125
  end
125
126
  end
126
-
127
127
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module MessageBus
3
- VERSION = "2.1.6"
4
+ VERSION = "2.2.0.pre"
4
5
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  # -*- encoding: utf-8 -*-
3
+
3
4
  require File.expand_path('../lib/message_bus/version', __FILE__)
4
5
 
5
6
  Gem::Specification.new do |gem|
@@ -10,13 +11,13 @@ Gem::Specification.new do |gem|
10
11
  gem.homepage = "https://github.com/SamSaffron/message_bus"
11
12
  gem.license = "MIT"
12
13
  gem.files = `git ls-files`.split($\) +
13
- ["vendor/assets/javascripts/message-bus.js", "vendor/assets/javascripts/message-bus-ajax.js"]
14
+ ["vendor/assets/javascripts/message-bus.js", "vendor/assets/javascripts/message-bus-ajax.js"]
14
15
  gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
15
16
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
16
17
  gem.name = "message_bus"
17
18
  gem.require_paths = ["lib"]
18
19
  gem.version = MessageBus::VERSION
19
- gem.required_ruby_version = ">= 2.2.0"
20
+ gem.required_ruby_version = ">= 2.3.0"
20
21
  gem.add_runtime_dependency 'rack', '>= 1.1.3'
21
22
  gem.add_development_dependency 'redis'
22
23
  gem.add_development_dependency 'pg'
@@ -1,4 +1,4 @@
1
- Jasmine.configure do |config|
1
+ Jasmine.configure do |_config|
2
2
  # patch for travis
3
3
  if ENV['TRAVIS']
4
4
  module ::Phantomjs
@@ -1,7 +1,6 @@
1
1
  require 'http/parser'
2
2
  class FakeAsyncMiddleware
3
-
4
- def initialize(app,config={})
3
+ def initialize(app, config = {})
5
4
  @app = app
6
5
  @bus = config[:message_bus] || MessageBus
7
6
  @simulate_thin_async = false
@@ -31,7 +30,6 @@ class FakeAsyncMiddleware
31
30
  @in_async
32
31
  end
33
32
 
34
-
35
33
  def simulate_thin_async?
36
34
  @simulate_thin_async && @bus.long_polling_enabled?
37
35
  end
@@ -64,7 +62,6 @@ class FakeAsyncMiddleware
64
62
  [parser.status_code, parser.headers, [body]]
65
63
  end
66
64
 
67
-
68
65
  def call_rack_hijack(env)
69
66
  # this is not to spec, the spec actually return, but here we will simply simulate and block
70
67
  result = nil
@@ -106,7 +103,6 @@ class FakeAsyncMiddleware
106
103
 
107
104
  @in_async = false
108
105
  result || [500, {}, ['timeout']]
109
-
110
106
  end
111
107
 
112
108
  def call_thin_async(env)
@@ -147,4 +143,3 @@ class FakeAsyncMiddleware
147
143
  result || [500, {}, ['timeout']]
148
144
  end
149
145
  end
150
-