message_bus 2.1.6 → 2.2.0.pre
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +13 -92
- data/.rubocop_todo.yml +659 -0
- data/.travis.yml +1 -1
- data/CHANGELOG +61 -0
- data/Dockerfile +18 -0
- data/Gemfile +3 -1
- data/Guardfile +0 -1
- data/README.md +188 -101
- data/Rakefile +12 -1
- data/assets/message-bus.js +1 -1
- data/docker-compose.yml +46 -0
- data/examples/bench/config.ru +8 -9
- data/examples/bench/unicorn.conf.rb +1 -1
- data/examples/chat/chat.rb +150 -153
- data/examples/minimal/config.ru +2 -3
- data/lib/message_bus.rb +224 -36
- data/lib/message_bus/backends.rb +7 -0
- data/lib/message_bus/backends/base.rb +184 -0
- data/lib/message_bus/backends/memory.rb +304 -226
- data/lib/message_bus/backends/postgres.rb +359 -318
- data/lib/message_bus/backends/redis.rb +380 -337
- data/lib/message_bus/client.rb +99 -41
- data/lib/message_bus/connection_manager.rb +29 -21
- data/lib/message_bus/diagnostics.rb +50 -41
- data/lib/message_bus/distributed_cache.rb +5 -7
- data/lib/message_bus/message.rb +2 -2
- data/lib/message_bus/rack/diagnostics.rb +65 -55
- data/lib/message_bus/rack/middleware.rb +64 -44
- data/lib/message_bus/rack/thin_ext.rb +13 -9
- data/lib/message_bus/rails/railtie.rb +2 -0
- data/lib/message_bus/timer_thread.rb +2 -2
- data/lib/message_bus/version.rb +2 -1
- data/message_bus.gemspec +3 -2
- data/spec/assets/support/jasmine_helper.rb +1 -1
- data/spec/lib/fake_async_middleware.rb +1 -6
- data/spec/lib/message_bus/assets/asset_encoding_spec.rb +3 -3
- data/spec/lib/message_bus/backend_spec.rb +409 -0
- data/spec/lib/message_bus/client_spec.rb +8 -11
- data/spec/lib/message_bus/connection_manager_spec.rb +8 -14
- data/spec/lib/message_bus/distributed_cache_spec.rb +0 -4
- data/spec/lib/message_bus/multi_process_spec.rb +6 -7
- data/spec/lib/message_bus/rack/middleware_spec.rb +47 -43
- data/spec/lib/message_bus/timer_thread_spec.rb +0 -2
- data/spec/lib/message_bus_spec.rb +59 -43
- data/spec/spec_helper.rb +16 -4
- metadata +12 -9
- data/spec/lib/message_bus/backends/postgres_spec.rb +0 -221
- data/spec/lib/message_bus/backends/redis_spec.rb +0 -271
data/lib/message_bus/message.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
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
|
-
|
11
|
-
|
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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
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
|
-
|
50
|
-
|
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.
|
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.
|
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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
data/lib/message_bus/version.rb
CHANGED
data/message_bus.gemspec
CHANGED
@@ -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
|
-
|
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.
|
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,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
|
-
|