message_bus 2.2.3 → 3.3.1

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.

Potentially problematic release.


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

Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +9 -1
  3. data/.travis.yml +1 -1
  4. data/CHANGELOG +39 -0
  5. data/Gemfile +8 -3
  6. data/Guardfile +1 -0
  7. data/README.md +62 -11
  8. data/Rakefile +5 -0
  9. data/assets/message-bus.js +64 -78
  10. data/examples/bench/config.ru +1 -0
  11. data/examples/bench/puma.rb +1 -0
  12. data/examples/bench/unicorn.conf.rb +1 -0
  13. data/examples/chat/Gemfile +1 -0
  14. data/examples/chat/chat.rb +2 -0
  15. data/examples/chat/config.ru +2 -0
  16. data/examples/diagnostics/Gemfile +1 -0
  17. data/examples/diagnostics/config.ru +1 -0
  18. data/examples/minimal/Gemfile +1 -0
  19. data/examples/minimal/config.ru +1 -0
  20. data/lib/message_bus.rb +33 -0
  21. data/lib/message_bus/backends/redis.rb +11 -8
  22. data/lib/message_bus/client.rb +36 -8
  23. data/lib/message_bus/diagnostics.rb +1 -1
  24. data/lib/message_bus/em_ext.rb +1 -0
  25. data/lib/message_bus/http_client.rb +2 -1
  26. data/lib/message_bus/http_client/channel.rb +1 -0
  27. data/lib/message_bus/rack/diagnostics.rb +5 -4
  28. data/lib/message_bus/rack/middleware.rb +8 -4
  29. data/lib/message_bus/rails/railtie.rb +15 -13
  30. data/lib/message_bus/version.rb +1 -1
  31. data/package.json +20 -0
  32. data/spec/assets/message-bus.spec.js +0 -9
  33. data/spec/assets/support/jasmine_helper.rb +1 -0
  34. data/spec/fixtures/test/Gemfile +1 -0
  35. data/spec/fixtures/test/config.ru +1 -0
  36. data/spec/helpers.rb +1 -0
  37. data/spec/integration/http_client_spec.rb +2 -0
  38. data/spec/lib/fake_async_middleware.rb +3 -2
  39. data/spec/lib/message_bus/assets/asset_encoding_spec.rb +1 -0
  40. data/spec/lib/message_bus/backend_spec.rb +2 -0
  41. data/spec/lib/message_bus/client_spec.rb +208 -23
  42. data/spec/lib/message_bus/connection_manager_spec.rb +3 -1
  43. data/spec/lib/message_bus/distributed_cache_spec.rb +2 -0
  44. data/spec/lib/message_bus/multi_process_spec.rb +2 -0
  45. data/spec/lib/message_bus/rack/middleware_spec.rb +63 -0
  46. data/spec/lib/message_bus/timer_thread_spec.rb +2 -0
  47. data/spec/lib/message_bus_spec.rb +34 -0
  48. data/spec/performance/publish.rb +2 -0
  49. data/spec/spec_helper.rb +3 -1
  50. metadata +4 -3
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  $LOAD_PATH.unshift File.expand_path('../../../lib', __FILE__)
2
3
 
3
4
  require 'message_bus'
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'message_bus'
2
3
  on_worker_boot do
3
4
  MessageBus.after_fork
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'message_bus'
2
3
  after_fork do |_server, _worker|
3
4
  MessageBus.after_fork
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  gem 'puma'
2
3
  gem 'redis'
3
4
  gem 'sinatra'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  $LOAD_PATH.unshift File.expand_path('../../../lib', __FILE__)
2
4
  require 'message_bus'
3
5
  require 'sinatra'
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require './chat'
2
4
  run Chat
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  source 'https://rubygems.org'
2
3
 
3
4
  gem 'message_bus', path: '../..'
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'message_bus'
2
3
 
3
4
  MessageBus.configure(backend: :redis, url: ENV['REDISURL'])
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  source 'https://rubygems.org'
2
3
 
3
4
  gem 'message_bus'
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'message_bus'
2
3
 
3
4
  # MessageBus.long_polling_interval = 1000 * 2
@@ -149,6 +149,19 @@ module MessageBus::Implementation
149
149
  @config[:long_polling_interval] || 25 * 1000
150
150
  end
151
151
 
152
+ # @param [String] route Message bus will listen to requests on this route.
153
+ # @return [void]
154
+ def base_route=(route)
155
+ configure(base_route: route.gsub(Regexp.new('\A(?!/)|(?<!/)\Z|//+'), "/"))
156
+ end
157
+
158
+ # @return [String] the route that message bus will respond to. If not
159
+ # explicitly set, defaults to "/". Requests to "#{base_route}message-bus/*" will be handled
160
+ # by the message bus server.
161
+ def base_route
162
+ @config[:base_route] || "/"
163
+ end
164
+
152
165
  # @return [Boolean] whether the bus is disabled or not
153
166
  def off?
154
167
  @off
@@ -554,6 +567,26 @@ module MessageBus::Implementation
554
567
  @config[:keepalive_interval] || 60
555
568
  end
556
569
 
570
+ # Registers a client message filter that allows messages to be filtered from the client.
571
+ #
572
+ # @param [String,Regexp] channel_prefix channel prefix to match against a message's channel
573
+ #
574
+ # @yieldparam [MessageBus::Message] message published to the channel that matched the prefix provided
575
+ # @yieldreturn [Boolean] whether the message should be published to the client
576
+ # @return [void]
577
+ def register_client_message_filter(channel_prefix, &blk)
578
+ if blk
579
+ configure(client_message_filters: []) if !@config[:client_message_filters]
580
+ @config[:client_message_filters] << [channel_prefix, blk]
581
+ end
582
+ end
583
+
584
+ # @return [Array] returns a hash of message filters that have been registered
585
+ def client_message_filters
586
+ configure(client_message_filters: []) if !@config[:client_message_filters]
587
+ @config[:client_message_filters]
588
+ end
589
+
557
590
  private
558
591
 
559
592
  ENCODE_SITE_TOKEN = "$|$"
@@ -251,11 +251,11 @@ LUA
251
251
 
252
252
  # (see Base#global_unsubscribe)
253
253
  def global_unsubscribe
254
- if @redis_global
255
- # new connection to avoid deadlock
256
- new_redis_connection.publish(redis_channel_name, UNSUB_MESSAGE)
257
- @redis_global.disconnect
258
- @redis_global = nil
254
+ begin
255
+ new_redis = new_redis_connection
256
+ new_redis.publish(redis_channel_name, UNSUB_MESSAGE)
257
+ ensure
258
+ new_redis&.disconnect!
259
259
  end
260
260
  end
261
261
 
@@ -278,13 +278,13 @@ LUA
278
278
  end
279
279
 
280
280
  begin
281
- @redis_global = new_redis_connection
281
+ global_redis = new_redis_connection
282
282
 
283
283
  if highest_id
284
284
  clear_backlog.call(&blk)
285
285
  end
286
286
 
287
- @redis_global.subscribe(redis_channel_name) do |on|
287
+ global_redis.subscribe(redis_channel_name) do |on|
288
288
  on.subscribe do
289
289
  if highest_id
290
290
  clear_backlog.call(&blk)
@@ -298,7 +298,7 @@ LUA
298
298
 
299
299
  on.message do |_c, m|
300
300
  if m == UNSUB_MESSAGE
301
- @redis_global.unsubscribe
301
+ global_redis.unsubscribe
302
302
  return
303
303
  end
304
304
  m = MessageBus::Message.decode m
@@ -320,7 +320,10 @@ LUA
320
320
  rescue => error
321
321
  @logger.warn "#{error} subscribe failed, reconnecting in 1 second. Call stack #{error.backtrace}"
322
322
  sleep 1
323
+ global_redis&.disconnect!
323
324
  retry
325
+ ensure
326
+ global_redis&.disconnect!
324
327
  end
325
328
  end
326
329
 
@@ -128,15 +128,43 @@ class MessageBus::Client
128
128
  # @return [Boolean] whether or not the client has permission to receive the
129
129
  # passed message
130
130
  def allowed?(msg)
131
- allowed = !msg.user_ids || msg.user_ids.include?(self.user_id)
132
- allowed &&= !msg.client_ids || msg.client_ids.include?(self.client_id)
133
- allowed && (
134
- msg.group_ids.nil? ||
135
- msg.group_ids.length == 0 ||
136
- (
137
- msg.group_ids - self.group_ids
131
+ client_allowed = !msg.client_ids || msg.client_ids.length == 0 || msg.client_ids.include?(self.client_id)
132
+
133
+ user_allowed = false
134
+ group_allowed = false
135
+
136
+ # this is an inconsistency we should fix anyway, publishing `user_ids: nil` should work same as groups
137
+ has_users = msg.user_ids && msg.user_ids.length > 0
138
+ has_groups = msg.group_ids && msg.group_ids.length > 0
139
+
140
+ if has_users
141
+ user_allowed = msg.user_ids.include?(self.user_id)
142
+ end
143
+
144
+ if has_groups
145
+ group_allowed = (
146
+ msg.group_ids - (self.group_ids || [])
138
147
  ).length < msg.group_ids.length
139
- )
148
+ end
149
+
150
+ has_permission = client_allowed && (user_allowed || group_allowed || (!has_users && !has_groups))
151
+
152
+ return has_permission if !has_permission
153
+
154
+ filters_allowed = true
155
+
156
+ len = @bus.client_message_filters.length
157
+ while len > 0
158
+ len -= 1
159
+ channel_prefix, blk = @bus.client_message_filters[len]
160
+
161
+ if msg.channel.start_with?(channel_prefix)
162
+ filters_allowed = blk.call(msg)
163
+ break if !filters_allowed
164
+ end
165
+ end
166
+
167
+ filters_allowed
140
168
  end
141
169
 
142
170
  # @return [Array<MessageBus::Message>] the set of messages the client is due
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # MessageBus diagnostics are used for troubleshooting the bus and optimising its configuration
2
3
  # @see MessageBus::Rack::Diagnostics
3
4
  class MessageBus::Diagnostics
@@ -14,7 +15,6 @@ class MessageBus::Diagnostics
14
15
  # process to process comms
15
16
  bus.subscribe('/_diagnostics/hup') do |msg|
16
17
  if Process.pid == msg.data["pid"] && hostname == msg.data["hostname"]
17
- $shutdown = true
18
18
  sleep 4
19
19
  Process.kill("HUP", $$)
20
20
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module EM
2
3
  def self.reactor_pid
3
4
  @reactor_pid
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'securerandom'
2
3
  require 'net/http'
3
4
  require 'json'
@@ -245,7 +246,7 @@ module MessageBus
245
246
  request.body = poll_payload
246
247
 
247
248
  if @enable_long_polling
248
- buffer = ''
249
+ buffer = +""
249
250
 
250
251
  http.request(request) do |response|
251
252
  response.read_body do |chunk|
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module MessageBus
2
3
  class HTTPClient
3
4
  # @private
@@ -16,9 +16,9 @@ class MessageBus::Rack::Diagnostics
16
16
  # Process an HTTP request from a subscriber client
17
17
  # @param [Rack::Request::Env] env the request environment
18
18
  def call(env)
19
- return @app.call(env) unless env['PATH_INFO'].start_with? '/message-bus/_diagnostics'
19
+ return @app.call(env) unless env['PATH_INFO'].start_with? "#{@bus.base_route}message-bus/_diagnostics"
20
20
 
21
- route = env['PATH_INFO'].split('/message-bus/_diagnostics')[1]
21
+ route = env['PATH_INFO'].split("#{@bus.base_route}message-bus/_diagnostics")[1]
22
22
 
23
23
  if @bus.is_admin_lookup.nil? || !@bus.is_admin_lookup.call(env)
24
24
  return [403, {}, ['not allowed']]
@@ -45,7 +45,7 @@ class MessageBus::Rack::Diagnostics
45
45
  return [200, { 'Content-Type' => 'application/javascript;charset=UTF-8' }, [content]]
46
46
  end
47
47
 
48
- return [404, {}, ['not found']]
48
+ [404, {}, ['not found']]
49
49
  end
50
50
 
51
51
  private
@@ -92,6 +92,7 @@ class MessageBus::Rack::Diagnostics
92
92
  </body>
93
93
  </html>
94
94
  HTML
95
- return [200, { "content-type" => "text/html;" }, [html]]
95
+
96
+ [200, { "content-type" => "text/html;" }, [html]]
96
97
  end
97
98
  end
@@ -39,6 +39,10 @@ class MessageBus::Rack::Middleware
39
39
  @bus = config[:message_bus] || MessageBus
40
40
  @connection_manager = MessageBus::ConnectionManager.new(@bus)
41
41
  @started_listener = false
42
+ @base_route = "#{@bus.base_route}message-bus/"
43
+ @base_route_length = @base_route.length
44
+ @diagnostics_route = "#{@base_route}_diagnostics"
45
+ @broadcast_route = "#{@base_route}broadcast"
42
46
  start_listener unless @bus.off?
43
47
  end
44
48
 
@@ -54,7 +58,7 @@ class MessageBus::Rack::Middleware
54
58
  # Process an HTTP request from a subscriber client
55
59
  # @param [Rack::Request::Env] env the request environment
56
60
  def call(env)
57
- return @app.call(env) unless env['PATH_INFO'] =~ /^\/message-bus\//
61
+ return @app.call(env) unless env['PATH_INFO'].start_with? @base_route
58
62
 
59
63
  handle_request(env)
60
64
  end
@@ -63,18 +67,18 @@ class MessageBus::Rack::Middleware
63
67
 
64
68
  def handle_request(env)
65
69
  # special debug/test route
66
- if @bus.allow_broadcast? && env['PATH_INFO'] == '/message-bus/broadcast'
70
+ if @bus.allow_broadcast? && env['PATH_INFO'] == @broadcast_route
67
71
  parsed = Rack::Request.new(env)
68
72
  @bus.publish parsed["channel"], parsed["data"]
69
73
  return [200, { "Content-Type" => "text/html" }, ["sent"]]
70
74
  end
71
75
 
72
- if env['PATH_INFO'].start_with? '/message-bus/_diagnostics'
76
+ if env['PATH_INFO'].start_with? @diagnostics_route
73
77
  diags = MessageBus::Rack::Diagnostics.new(@app, message_bus: @bus)
74
78
  return diags.call(env)
75
79
  end
76
80
 
77
- client_id = env['PATH_INFO'].split("/")[2]
81
+ client_id = env['PATH_INFO'][@base_route_length..-1].split("/")[0]
78
82
  return [404, {}, ["not found"]] unless client_id
79
83
 
80
84
  user_id = @bus.user_id_lookup.call(env) if @bus.user_id_lookup
@@ -11,27 +11,29 @@ class MessageBus::Rails::Railtie < ::Rails::Railtie
11
11
  # the Rails app is configured that might be ActionDispatch::Session::CookieStore, or potentially
12
12
  # ActionDispatch::Session::ActiveRecordStore.
13
13
  #
14
- # To handle either case, we insert it before ActionDispatch::Flash.
15
- #
16
- # For APIs or apps that have ActionDispatch::Flash deleted from the middleware
17
- # stack we just push MessageBus to the bottom.
18
- if api_only?(app.config) || flash_middleware_deleted?(app.middleware)
19
- app.middleware.use(MessageBus::Rack::Middleware)
20
- else
21
- app.middleware.insert_before(ActionDispatch::Flash, MessageBus::Rack::Middleware)
14
+ # given https://github.com/rails/rails/commit/fedde239dcee256b417dc9bcfe5fef603bf0d952#diff-533a9a9cc17a8a899cb830626089e5f9
15
+ # there is no way of walking the stack for operations
16
+ if !skip_middleware?(app.config)
17
+ if api_only?(app.config)
18
+ app.middleware.use(MessageBus::Rack::Middleware)
19
+ else
20
+ app.middleware.insert_before(ActionDispatch::Flash, MessageBus::Rack::Middleware)
21
+ end
22
22
  end
23
23
 
24
24
  MessageBus.logger = Rails.logger
25
25
  end
26
26
 
27
+ def skip_middleware?(config)
28
+ return false if !config.respond_to?(:skip_message_bus_middleware)
29
+
30
+ config.skip_message_bus_middleware
31
+ end
32
+
27
33
  def api_only?(config)
28
- return false unless config.respond_to?(:api_only)
34
+ return false if !config.respond_to?(:api_only)
29
35
 
30
36
  config.api_only
31
37
  end
32
38
 
33
- def flash_middleware_deleted?(middleware)
34
- ops = middleware.instance_variable_get(:@operations)
35
- ops.any? { |m| m[0] == :delete && m[1].include?(ActionDispatch::Flash) }
36
- end
37
39
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MessageBus
4
- VERSION = "2.2.3"
4
+ VERSION = "3.3.1"
5
5
  end
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "message-bus-client",
3
+ "version": "3.3.0",
4
+ "description": "A message bus client in Javascript",
5
+ "main": "assets/message-bus.js",
6
+ "keywords": "es6, modules",
7
+ "files": ["assets/message-bus.js"],
8
+ "jsnext:main": "assets/message-bus.js",
9
+ "module": "assets/message-bus.js",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/discourse/message_bus.git"
13
+ },
14
+ "author": "Sam Saffron, Robin Ward",
15
+ "license": "MIT",
16
+ "bugs": {
17
+ "url": "https://github.com/discourse/message_bus/issues"
18
+ },
19
+ "homepage": "https://github.com/discourse/message_bus#readme"
20
+ }
@@ -75,15 +75,6 @@ describe("Messagebus", function() {
75
75
  })
76
76
  });
77
77
 
78
- it('removes itself from root namespace when noConflict is called', function(){
79
- expect(window.MessageBus).not.toBeUndefined();
80
- var mb = window.MessageBus;
81
- expect(mb).toEqual(window.MessageBus.noConflict());
82
- expect(window.MessageBus).toBeUndefined();
83
- // reset it so afterEach has something to work on
84
- window.MessageBus = mb;
85
- });
86
-
87
78
  it('respects minPollInterval setting with defaults', function(){
88
79
  expect(MessageBus.minPollInterval).toEqual(100);
89
80
  MessageBus.minPollInterval = 1000;
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  Jasmine.configure do |_config|
2
3
  # patch for travis
3
4
  if ENV['TRAVIS']
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  source 'https://rubygems.org'
2
3
 
3
4
  gem 'message_bus'
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'message_bus'
2
3
 
3
4
  MessageBus.config[:backend] = :memory
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  def wait_for(timeout_milliseconds = 2000)
2
3
  timeout = (timeout_milliseconds + 0.0) / 1000
3
4
  finish = Time.now + timeout
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../spec_helper'
2
4
  require 'message_bus/http_client'
3
5
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'http/parser'
2
3
  class FakeAsyncMiddleware
3
4
  def initialize(app, config = {})
@@ -53,7 +54,7 @@ class FakeAsyncMiddleware
53
54
 
54
55
  def translate_io_result(io)
55
56
  data = io.string
56
- body = ""
57
+ body = +""
57
58
 
58
59
  parser = Http::Parser.new
59
60
  parser.on_body = proc { |chunk| body << chunk }
@@ -112,7 +113,7 @@ class FakeAsyncMiddleware
112
113
  # more judo with deferrable body, at this point we just have headers
113
114
  r[2].callback do
114
115
  # even more judo cause rack test does not call each like the spec says
115
- body = ""
116
+ body = +""
116
117
  r[2].each do |m|
117
118
  body << m
118
119
  end