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.
- 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
|
-
|