message_bus 0.0.1
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 +7 -0
- data/LICENSE +22 -0
- data/README.md +64 -0
- data/lib/message_bus/client.rb +81 -0
- data/lib/message_bus/connection_manager.rb +66 -0
- data/lib/message_bus/diagnostics.rb +53 -0
- data/lib/message_bus/message.rb +17 -0
- data/lib/message_bus/message_handler.rb +26 -0
- data/lib/message_bus/rack/diagnostics.rb +98 -0
- data/lib/message_bus/rack/middleware.rb +171 -0
- data/lib/message_bus/rails/railtie.rb +9 -0
- data/lib/message_bus/reliable_pub_sub.rb +262 -0
- data/lib/message_bus/version.rb +3 -0
- data/lib/message_bus.rb +272 -0
- metadata +112 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0b1d52c269db2dcec90068d0ceb828b969666a64
|
4
|
+
data.tar.gz: e2e46e33612cde225c94c17367599b63f3c824f4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 523d379b630f2c3d23db73835bde99b5fda6003f448ce7d9f3d66c169bbd18775d251aad51dc269bfe987e62279d49475712f5bab32db1baaf2c404fa6110a5c
|
7
|
+
data.tar.gz: 42aa9744353b2a70fb7dfe6422a30eaaa721deb161695f0ea084cec809acde201af451cc253f801d3623257ad7f700fb8921d482d2d61fe21909542ef8e4074a
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Sam Saffron
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# MessageBus
|
2
|
+
|
3
|
+
A reliable, robust messaging bus for Ruby processes and web clients built on Redis.
|
4
|
+
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
gem 'message_bus'
|
11
|
+
|
12
|
+
And then execute:
|
13
|
+
|
14
|
+
$ bundle
|
15
|
+
|
16
|
+
Or install it yourself as:
|
17
|
+
|
18
|
+
$ gem install message_bus
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
Server to Server messaging
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
message_id = MessageBus.publish "/channel", "message"
|
26
|
+
|
27
|
+
# in another process / spot
|
28
|
+
|
29
|
+
MessageBus.subscribe "/channel" do |msg|
|
30
|
+
# block called in a backgroud thread when message is recieved
|
31
|
+
end
|
32
|
+
|
33
|
+
MessageBus.backlog "/channel", id
|
34
|
+
# returns all messages after the id
|
35
|
+
|
36
|
+
|
37
|
+
# messages can be targetted at particular users or groups
|
38
|
+
MessageBus.publish "/channel", user_ids: [1,2,3], group_ids: [4,5,6]
|
39
|
+
|
40
|
+
# message bus determines the user ids and groups based on env
|
41
|
+
|
42
|
+
MessageBus.user_id_lookup do |env|
|
43
|
+
# return the user id here
|
44
|
+
end
|
45
|
+
|
46
|
+
MessageBus.group_ids_lookup do |env|
|
47
|
+
# return the group ids the user belongs to
|
48
|
+
# can be nil or []
|
49
|
+
end
|
50
|
+
|
51
|
+
```
|
52
|
+
|
53
|
+
|
54
|
+
## Similar projects
|
55
|
+
|
56
|
+
Faye - http://faye.jcoglan.com/
|
57
|
+
|
58
|
+
## Contributing
|
59
|
+
|
60
|
+
1. Fork it
|
61
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
62
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
63
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
64
|
+
5. Create new Pull Request
|
@@ -0,0 +1,81 @@
|
|
1
|
+
class MessageBus::Client
|
2
|
+
attr_accessor :client_id, :user_id, :group_ids, :connect_time, :subscribed_sets, :site_id, :cleanup_timer, :async_response
|
3
|
+
def initialize(opts)
|
4
|
+
self.client_id = opts[:client_id]
|
5
|
+
self.user_id = opts[:user_id]
|
6
|
+
self.group_ids = opts[:group_ids] || []
|
7
|
+
self.site_id = opts[:site_id]
|
8
|
+
self.connect_time = Time.now
|
9
|
+
@subscriptions = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def close
|
13
|
+
return unless @async_response
|
14
|
+
write_and_close "[]"
|
15
|
+
end
|
16
|
+
|
17
|
+
def closed
|
18
|
+
!@async_response
|
19
|
+
end
|
20
|
+
|
21
|
+
def subscribe(channel, last_seen_id)
|
22
|
+
last_seen_id ||= MessageBus.last_id(channel)
|
23
|
+
@subscriptions[channel] = last_seen_id
|
24
|
+
end
|
25
|
+
|
26
|
+
def subscriptions
|
27
|
+
@subscriptions
|
28
|
+
end
|
29
|
+
|
30
|
+
def <<(msg)
|
31
|
+
write_and_close messages_to_json([msg])
|
32
|
+
end
|
33
|
+
|
34
|
+
def subscriptions
|
35
|
+
@subscriptions
|
36
|
+
end
|
37
|
+
|
38
|
+
def allowed?(msg)
|
39
|
+
allowed = !msg.user_ids || msg.user_ids.include?(self.user_id)
|
40
|
+
allowed && (
|
41
|
+
msg.group_ids.nil? ||
|
42
|
+
msg.group_ids.length == 0 ||
|
43
|
+
(
|
44
|
+
msg.group_ids - self.group_ids
|
45
|
+
).length < msg.group_ids.length
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
def backlog
|
50
|
+
r = []
|
51
|
+
@subscriptions.each do |k,v|
|
52
|
+
next if v.to_i < 0
|
53
|
+
messages = MessageBus.backlog(k,v)
|
54
|
+
messages.each do |msg|
|
55
|
+
r << msg if allowed?(msg)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
# stats message for all newly subscribed
|
59
|
+
status_message = nil
|
60
|
+
@subscriptions.each do |k,v|
|
61
|
+
if v.to_i == -1
|
62
|
+
status_message ||= {}
|
63
|
+
status_message[k] = MessageBus.last_id(k)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
r << MessageBus::Message.new(-1, -1, '/__status', status_message) if status_message
|
67
|
+
r
|
68
|
+
end
|
69
|
+
|
70
|
+
protected
|
71
|
+
|
72
|
+
def write_and_close(data)
|
73
|
+
@async_response << data
|
74
|
+
@async_response.done
|
75
|
+
@async_response = nil
|
76
|
+
end
|
77
|
+
|
78
|
+
def messages_to_json(msgs)
|
79
|
+
MessageBus::Rack::Middleware.backlog_to_json(msgs)
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'json' unless defined? ::JSON
|
2
|
+
|
3
|
+
class MessageBus::ConnectionManager
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@clients = {}
|
7
|
+
@subscriptions = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def notify_clients(msg)
|
11
|
+
begin
|
12
|
+
site_subs = @subscriptions[msg.site_id]
|
13
|
+
subscription = site_subs[msg.channel] if site_subs
|
14
|
+
|
15
|
+
return unless subscription
|
16
|
+
|
17
|
+
subscription.each do |client_id|
|
18
|
+
client = @clients[client_id]
|
19
|
+
if client && client.allowed?(msg)
|
20
|
+
client << msg
|
21
|
+
# turns out you can delete from a set while itereating
|
22
|
+
remove_client(client)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
rescue => e
|
26
|
+
MessageBus.logger.error "notify clients crash #{e} : #{e.backtrace}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def add_client(client)
|
31
|
+
@clients[client.client_id] = client
|
32
|
+
@subscriptions[client.site_id] ||= {}
|
33
|
+
client.subscriptions.each do |k,v|
|
34
|
+
subscribe_client(client, k)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def remove_client(c)
|
39
|
+
@clients.delete c.client_id
|
40
|
+
@subscriptions[c.site_id].each do |k, set|
|
41
|
+
set.delete c.client_id
|
42
|
+
end
|
43
|
+
c.cleanup_timer.cancel
|
44
|
+
end
|
45
|
+
|
46
|
+
def lookup_client(client_id)
|
47
|
+
@clients[client_id]
|
48
|
+
end
|
49
|
+
|
50
|
+
def subscribe_client(client,channel)
|
51
|
+
set = @subscriptions[client.site_id][channel]
|
52
|
+
unless set
|
53
|
+
set = Set.new
|
54
|
+
@subscriptions[client.site_id][channel] = set
|
55
|
+
end
|
56
|
+
set << client.client_id
|
57
|
+
end
|
58
|
+
|
59
|
+
def stats
|
60
|
+
{
|
61
|
+
client_count: @clients.length,
|
62
|
+
subscriptions: @subscriptions
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class MessageBus::Diagnostics
|
2
|
+
def self.full_process_path
|
3
|
+
begin
|
4
|
+
system = `uname`.strip
|
5
|
+
if system == "Darwin"
|
6
|
+
`ps -o "comm=" -p #{Process.pid}`
|
7
|
+
elsif system == "FreeBSD"
|
8
|
+
`ps -o command -p #{Process.pid}`.split("\n",2)[1].strip()
|
9
|
+
else
|
10
|
+
info = `ps -eo "%p|$|%a" | grep '^\\s*#{Process.pid}'`
|
11
|
+
info.strip.split('|$|')[1]
|
12
|
+
end
|
13
|
+
rescue
|
14
|
+
# skip it ... not linux or something weird
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.hostname
|
19
|
+
begin
|
20
|
+
`hostname`.strip
|
21
|
+
rescue
|
22
|
+
# skip it
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.enable
|
27
|
+
full_path = full_process_path
|
28
|
+
start_time = Time.now.to_f
|
29
|
+
hostname = self.hostname
|
30
|
+
|
31
|
+
# it may make sense to add a channel per machine/host to streamline
|
32
|
+
# process to process comms
|
33
|
+
MessageBus.subscribe('/_diagnostics/hup') do |msg|
|
34
|
+
if Process.pid == msg.data["pid"] && hostname == msg.data["hostname"]
|
35
|
+
$shutdown = true
|
36
|
+
sleep 4
|
37
|
+
Process.kill("HUP", $$)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
MessageBus.subscribe('/_diagnostics/discover') do |msg|
|
42
|
+
MessageBus.on_connect.call msg.site_id if MessageBus.on_connect
|
43
|
+
MessageBus.publish '/_diagnostics/process-discovery', {
|
44
|
+
pid: Process.pid,
|
45
|
+
process_name: $0,
|
46
|
+
full_path: full_path,
|
47
|
+
uptime: (Time.now.to_f - start_time).to_i,
|
48
|
+
hostname: hostname
|
49
|
+
}, user_ids: [msg.data["user_id"]]
|
50
|
+
MessageBus.on_disconnect.call msg.site_id if MessageBus.on_disconnect
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class MessageBus::Message < Struct.new(:global_id, :message_id, :channel , :data)
|
2
|
+
|
3
|
+
attr_accessor :site_id, :user_ids, :group_ids
|
4
|
+
|
5
|
+
def self.decode(encoded)
|
6
|
+
s1 = encoded.index("|")
|
7
|
+
s2 = encoded.index("|", s1+1)
|
8
|
+
s3 = encoded.index("|", s2+1)
|
9
|
+
|
10
|
+
MessageBus::Message.new encoded[0..s1].to_i, encoded[s1+1..s2].to_i, encoded[s2+1..s3-1].gsub("$$123$$", "|"), encoded[s3+1..-1]
|
11
|
+
end
|
12
|
+
|
13
|
+
# only tricky thing to encode is pipes in a channel name ... do a straight replace
|
14
|
+
def encode
|
15
|
+
global_id.to_s << "|" << message_id.to_s << "|" << channel.gsub("|","$$123$$") << "|" << data
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class MessageBus::MessageHandler
|
2
|
+
def self.load_handlers(path)
|
3
|
+
Dir.glob("#{path}/*.rb").each do |f|
|
4
|
+
load "#{f}"
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.handle(name,&blk)
|
9
|
+
raise ArgumentError.new("expecting block") unless block_given?
|
10
|
+
raise ArgumentError.new("name") unless name
|
11
|
+
|
12
|
+
@@handlers ||= {}
|
13
|
+
@@handlers[name] = blk
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.call(site_id, name, data, current_user_id)
|
17
|
+
begin
|
18
|
+
MessageBus.on_connect.call(site_id) if MessageBus.on_connect
|
19
|
+
@@handlers[name].call(data,current_user_id)
|
20
|
+
ensure
|
21
|
+
MessageBus.on_disconnect.call(site_id) if MessageBus.on_disconnect
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module MessageBus::Rack; end
|
2
|
+
|
3
|
+
class MessageBus::Rack::Diagnostics
|
4
|
+
def initialize(app, config = {})
|
5
|
+
@app = app
|
6
|
+
end
|
7
|
+
|
8
|
+
def js_asset(name)
|
9
|
+
return generate_script_tag(name) unless MessageBus.cache_assets
|
10
|
+
@@asset_cache ||= {}
|
11
|
+
@@asset_cache[name] ||= generate_script_tag(name)
|
12
|
+
@@asset_cache[name]
|
13
|
+
end
|
14
|
+
|
15
|
+
def generate_script_tag(name)
|
16
|
+
"<script src='/message-bus/_diagnostics/assets/#{name}?#{file_hash(name)}' type='text/javascript'></script>"
|
17
|
+
end
|
18
|
+
|
19
|
+
def file_hash(asset)
|
20
|
+
require 'digest/sha1'
|
21
|
+
Digest::SHA1.hexdigest(asset_contents(asset))
|
22
|
+
end
|
23
|
+
|
24
|
+
def asset_contents(asset)
|
25
|
+
File.open(asset_path(asset)).read
|
26
|
+
end
|
27
|
+
|
28
|
+
def asset_path(asset)
|
29
|
+
File.expand_path("../../../../assets/#{asset}", __FILE__)
|
30
|
+
end
|
31
|
+
|
32
|
+
def index
|
33
|
+
html = <<HTML
|
34
|
+
<!DOCTYPE html>
|
35
|
+
<html>
|
36
|
+
<head>
|
37
|
+
</head>
|
38
|
+
<body>
|
39
|
+
<div id="app"></div>
|
40
|
+
#{js_asset "jquery-1.8.2.js"}
|
41
|
+
#{js_asset "handlebars.js"}
|
42
|
+
#{js_asset "ember.js"}
|
43
|
+
#{js_asset "message-bus.js"}
|
44
|
+
#{js_asset "application.handlebars"}
|
45
|
+
#{js_asset "index.handlebars"}
|
46
|
+
#{js_asset "application.js"}
|
47
|
+
</body>
|
48
|
+
</html>
|
49
|
+
HTML
|
50
|
+
return [200, {"content-type" => "text/html;"}, [html]]
|
51
|
+
end
|
52
|
+
|
53
|
+
def translate_handlebars(name, content)
|
54
|
+
"Ember.TEMPLATES['#{name}'] = Ember.Handlebars.compile(#{indent(content).inspect});"
|
55
|
+
end
|
56
|
+
|
57
|
+
# from ember-rails
|
58
|
+
def indent(string)
|
59
|
+
string.gsub(/$(.)/m, "\\1 ").strip
|
60
|
+
end
|
61
|
+
|
62
|
+
def call(env)
|
63
|
+
|
64
|
+
return @app.call(env) unless env['PATH_INFO'].start_with? '/message-bus/_diagnostics'
|
65
|
+
|
66
|
+
route = env['PATH_INFO'].split('/message-bus/_diagnostics')[1]
|
67
|
+
|
68
|
+
if MessageBus.is_admin_lookup.nil? || !MessageBus.is_admin_lookup.call(env)
|
69
|
+
return [403, {}, ['not allowed']]
|
70
|
+
end
|
71
|
+
|
72
|
+
return index unless route
|
73
|
+
|
74
|
+
if route == '/discover'
|
75
|
+
user_id = MessageBus.user_id_lookup.call(env)
|
76
|
+
MessageBus.publish('/_diagnostics/discover', user_id: user_id)
|
77
|
+
return [200, {}, ['ok']]
|
78
|
+
end
|
79
|
+
|
80
|
+
if route =~ /^\/hup\//
|
81
|
+
hostname, pid = route.split('/hup/')[1].split('/')
|
82
|
+
MessageBus.publish('/_diagnostics/hup', {hostname: hostname, pid: pid.to_i})
|
83
|
+
return [200, {}, ['ok']]
|
84
|
+
end
|
85
|
+
|
86
|
+
asset = route.split('/assets/')[1]
|
87
|
+
if asset && !asset !~ /\//
|
88
|
+
content = asset_contents(asset)
|
89
|
+
split = asset.split('.')
|
90
|
+
if split[1] == 'handlebars'
|
91
|
+
content = translate_handlebars(split[0],content)
|
92
|
+
end
|
93
|
+
return [200, {'content-type' => 'text/javascript;'}, [content]]
|
94
|
+
end
|
95
|
+
|
96
|
+
return [404, {}, ['not found']]
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
# our little message bus, accepts long polling and web sockets
|
2
|
+
require 'thin'
|
3
|
+
require 'eventmachine'
|
4
|
+
|
5
|
+
module MessageBus::Rack; end
|
6
|
+
|
7
|
+
class MessageBus::Rack::Middleware
|
8
|
+
|
9
|
+
def self.start_listener
|
10
|
+
unless @started_listener
|
11
|
+
MessageBus.subscribe do |msg|
|
12
|
+
if EM.reactor_running?
|
13
|
+
EM.next_tick do
|
14
|
+
@@connection_manager.notify_clients(msg) if @@connection_manager
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
@started_listener = true
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(app, config = {})
|
23
|
+
@app = app
|
24
|
+
@@connection_manager = MessageBus::ConnectionManager.new
|
25
|
+
self.class.start_listener
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.backlog_to_json(backlog)
|
29
|
+
m = backlog.map do |msg|
|
30
|
+
{
|
31
|
+
:global_id => msg.global_id,
|
32
|
+
:message_id => msg.message_id,
|
33
|
+
:channel => msg.channel,
|
34
|
+
:data => msg.data
|
35
|
+
}
|
36
|
+
end.to_a
|
37
|
+
JSON.dump(m)
|
38
|
+
end
|
39
|
+
|
40
|
+
def call(env)
|
41
|
+
|
42
|
+
return @app.call(env) unless env['PATH_INFO'] =~ /^\/message-bus/
|
43
|
+
|
44
|
+
# special debug/test route
|
45
|
+
if ::MessageBus.allow_broadcast? && env['PATH_INFO'] == '/message-bus/broadcast'
|
46
|
+
parsed = Rack::Request.new(env)
|
47
|
+
::MessageBus.publish parsed["channel"], parsed["data"]
|
48
|
+
return [200,{"Content-Type" => "text/html"},["sent"]]
|
49
|
+
end
|
50
|
+
|
51
|
+
if env['PATH_INFO'].start_with? '/message-bus/_diagnostics'
|
52
|
+
diags = MessageBus::Rack::Diagnostics.new(@app)
|
53
|
+
return diags.call(env)
|
54
|
+
end
|
55
|
+
|
56
|
+
client_id = env['PATH_INFO'].split("/")[2]
|
57
|
+
return [404, {}, ["not found"]] unless client_id
|
58
|
+
|
59
|
+
user_id = MessageBus.user_id_lookup.call(env) if MessageBus.user_id_lookup
|
60
|
+
group_ids = MessageBus.group_ids_lookup.call(env) if MessageBus.group_ids_lookup
|
61
|
+
site_id = MessageBus.site_id_lookup.call(env) if MessageBus.site_id_lookup
|
62
|
+
|
63
|
+
client = MessageBus::Client.new(client_id: client_id, user_id: user_id, site_id: site_id, group_ids: group_ids)
|
64
|
+
|
65
|
+
connection = env['em.connection']
|
66
|
+
|
67
|
+
request = Rack::Request.new(env)
|
68
|
+
request.POST.each do |k,v|
|
69
|
+
client.subscribe(k, v)
|
70
|
+
end
|
71
|
+
|
72
|
+
backlog = client.backlog
|
73
|
+
headers = {}
|
74
|
+
headers["Cache-Control"] = "must-revalidate, private, max-age=0"
|
75
|
+
headers["Content-Type"] ="application/json; charset=utf-8"
|
76
|
+
|
77
|
+
if backlog.length > 0
|
78
|
+
[200, headers, [self.class.backlog_to_json(backlog)] ]
|
79
|
+
elsif MessageBus.long_polling_enabled? && env['QUERY_STRING'] !~ /dlp=t/ && EM.reactor_running?
|
80
|
+
response = Thin::AsyncResponse.new(env)
|
81
|
+
response.headers["Cache-Control"] = "must-revalidate, private, max-age=0"
|
82
|
+
response.headers["Content-Type"] ="application/json; charset=utf-8"
|
83
|
+
response.status = 200
|
84
|
+
client.async_response = response
|
85
|
+
|
86
|
+
@@connection_manager.add_client(client)
|
87
|
+
|
88
|
+
client.cleanup_timer = ::EM::Timer.new(MessageBus.long_polling_interval.to_f / 1000) {
|
89
|
+
client.close
|
90
|
+
@@connection_manager.remove_client(client)
|
91
|
+
}
|
92
|
+
|
93
|
+
throw :async
|
94
|
+
else
|
95
|
+
[200, headers, ["[]"]]
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# there is also another in cramp this is from https://github.com/macournoyer/thin_async/blob/master/lib/thin/async.rb
|
102
|
+
module Thin
|
103
|
+
unless defined?(DeferrableBody)
|
104
|
+
# Based on version from James Tucker <raggi@rubyforge.org>
|
105
|
+
class DeferrableBody
|
106
|
+
include ::EM::Deferrable
|
107
|
+
|
108
|
+
def initialize
|
109
|
+
@queue = []
|
110
|
+
end
|
111
|
+
|
112
|
+
def call(body)
|
113
|
+
@queue << body
|
114
|
+
schedule_dequeue
|
115
|
+
end
|
116
|
+
|
117
|
+
def each(&blk)
|
118
|
+
@body_callback = blk
|
119
|
+
schedule_dequeue
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
def schedule_dequeue
|
124
|
+
return unless @body_callback
|
125
|
+
::EM.next_tick do
|
126
|
+
next unless body = @queue.shift
|
127
|
+
body.each do |chunk|
|
128
|
+
@body_callback.call(chunk)
|
129
|
+
end
|
130
|
+
schedule_dequeue unless @queue.empty?
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Response whos body is sent asynchronously.
|
137
|
+
class AsyncResponse
|
138
|
+
include Rack::Response::Helpers
|
139
|
+
|
140
|
+
attr_reader :headers, :callback, :closed
|
141
|
+
attr_accessor :status
|
142
|
+
|
143
|
+
def initialize(env, status=200, headers={})
|
144
|
+
@callback = env['async.callback']
|
145
|
+
@body = DeferrableBody.new
|
146
|
+
@status = status
|
147
|
+
@headers = headers
|
148
|
+
@headers_sent = false
|
149
|
+
end
|
150
|
+
|
151
|
+
def send_headers
|
152
|
+
return if @headers_sent
|
153
|
+
@callback.call [@status, @headers, @body]
|
154
|
+
@headers_sent = true
|
155
|
+
end
|
156
|
+
|
157
|
+
def write(body)
|
158
|
+
send_headers
|
159
|
+
@body.call(body.respond_to?(:each) ? body : [body])
|
160
|
+
end
|
161
|
+
alias :<< :write
|
162
|
+
|
163
|
+
# Tell Thin the response is complete and the connection can be closed.
|
164
|
+
def done
|
165
|
+
@closed = true
|
166
|
+
send_headers
|
167
|
+
::EM.next_tick { @body.succeed }
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module MessageBus; module Rails; end; end
|
2
|
+
|
3
|
+
class MessageBus::Rails::Railtie < ::Rails::Railtie
|
4
|
+
initializer "message_bus.configure_init" do |app|
|
5
|
+
MessageBus::MessageHandler.load_handlers("#{Rails.root}/app/message_handlers")
|
6
|
+
app.middleware.use MessageBus::Rack::Middleware
|
7
|
+
MessageBus.logger = Rails.logger
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,262 @@
|
|
1
|
+
require 'redis'
|
2
|
+
# the heart of the message bus, it acts as 2 things
|
3
|
+
#
|
4
|
+
# 1. A channel multiplexer
|
5
|
+
# 2. Backlog storage per-multiplexed channel.
|
6
|
+
#
|
7
|
+
# ids are all sequencially increasing numbers starting at 0
|
8
|
+
#
|
9
|
+
|
10
|
+
|
11
|
+
class MessageBus::ReliablePubSub
|
12
|
+
|
13
|
+
class NoMoreRetries < StandardError; end
|
14
|
+
class BackLogOutOfOrder < StandardError
|
15
|
+
attr_accessor :highest_id
|
16
|
+
|
17
|
+
def initialize(highest_id)
|
18
|
+
@highest_id = highest_id
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def max_publish_retries=(val)
|
23
|
+
@max_publish_retries = val
|
24
|
+
end
|
25
|
+
|
26
|
+
def max_publish_retries
|
27
|
+
@max_publish_retries ||= 10
|
28
|
+
end
|
29
|
+
|
30
|
+
def max_publish_wait=(ms)
|
31
|
+
@max_publish_wait = ms
|
32
|
+
end
|
33
|
+
|
34
|
+
def max_publish_wait
|
35
|
+
@max_publish_wait ||= 500
|
36
|
+
end
|
37
|
+
|
38
|
+
# max_backlog_size is per multiplexed channel
|
39
|
+
def initialize(redis_config = {}, max_backlog_size = 1000)
|
40
|
+
@redis_config = redis_config
|
41
|
+
@max_backlog_size = 1000
|
42
|
+
# we can store a ton here ...
|
43
|
+
@max_global_backlog_size = 100000
|
44
|
+
end
|
45
|
+
|
46
|
+
# amount of global backlog we can spin through
|
47
|
+
def max_global_backlog_size=(val)
|
48
|
+
@max_global_backlog_size = val
|
49
|
+
end
|
50
|
+
|
51
|
+
# per channel backlog size
|
52
|
+
def max_backlog_size=(val)
|
53
|
+
@max_backlog_size = val
|
54
|
+
end
|
55
|
+
|
56
|
+
def new_redis_connection
|
57
|
+
::Redis.new(@redis_config)
|
58
|
+
end
|
59
|
+
|
60
|
+
def redis_channel_name
|
61
|
+
db = @redis_config[:db] || 0
|
62
|
+
"discourse_#{db}"
|
63
|
+
end
|
64
|
+
|
65
|
+
# redis connection used for publishing messages
|
66
|
+
def pub_redis
|
67
|
+
@pub_redis ||= new_redis_connection
|
68
|
+
end
|
69
|
+
|
70
|
+
def backlog_key(channel)
|
71
|
+
"__mb_backlog_n_#{channel}"
|
72
|
+
end
|
73
|
+
|
74
|
+
def backlog_id_key(channel)
|
75
|
+
"__mb_backlog_id_n_#{channel}"
|
76
|
+
end
|
77
|
+
|
78
|
+
def global_id_key
|
79
|
+
"__mb_global_id_n"
|
80
|
+
end
|
81
|
+
|
82
|
+
def global_backlog_key
|
83
|
+
"__mb_global_backlog_n"
|
84
|
+
end
|
85
|
+
|
86
|
+
# use with extreme care, will nuke all of the data
|
87
|
+
def reset!
|
88
|
+
pub_redis.keys("__mb_*").each do |k|
|
89
|
+
pub_redis.del k
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def publish(channel, data)
|
94
|
+
redis = pub_redis
|
95
|
+
backlog_id_key = backlog_id_key(channel)
|
96
|
+
backlog_key = backlog_key(channel)
|
97
|
+
|
98
|
+
global_id = nil
|
99
|
+
backlog_id = nil
|
100
|
+
|
101
|
+
redis.multi do |m|
|
102
|
+
global_id = m.incr(global_id_key)
|
103
|
+
backlog_id = m.incr(backlog_id_key)
|
104
|
+
end
|
105
|
+
|
106
|
+
global_id = global_id.value
|
107
|
+
backlog_id = backlog_id.value
|
108
|
+
|
109
|
+
msg = MessageBus::Message.new global_id, backlog_id, channel, data
|
110
|
+
payload = msg.encode
|
111
|
+
|
112
|
+
redis.zadd backlog_key, backlog_id, payload
|
113
|
+
redis.zadd global_backlog_key, global_id, backlog_id.to_s << "|" << channel
|
114
|
+
|
115
|
+
redis.publish redis_channel_name, payload
|
116
|
+
|
117
|
+
if backlog_id > @max_backlog_size
|
118
|
+
redis.zremrangebyscore backlog_key, 1, backlog_id - @max_backlog_size
|
119
|
+
end
|
120
|
+
|
121
|
+
if global_id > @max_global_backlog_size
|
122
|
+
redis.zremrangebyscore global_backlog_key, 1, backlog_id - @max_backlog_size
|
123
|
+
end
|
124
|
+
|
125
|
+
backlog_id
|
126
|
+
end
|
127
|
+
|
128
|
+
def last_id(channel)
|
129
|
+
redis = pub_redis
|
130
|
+
backlog_id_key = backlog_id_key(channel)
|
131
|
+
redis.get(backlog_id_key).to_i
|
132
|
+
end
|
133
|
+
|
134
|
+
def backlog(channel, last_id = nil)
|
135
|
+
redis = pub_redis
|
136
|
+
backlog_key = backlog_key(channel)
|
137
|
+
items = redis.zrangebyscore backlog_key, last_id.to_i + 1, "+inf"
|
138
|
+
|
139
|
+
items.map do |i|
|
140
|
+
MessageBus::Message.decode(i)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def global_backlog(last_id = nil)
|
145
|
+
last_id = last_id.to_i
|
146
|
+
redis = pub_redis
|
147
|
+
|
148
|
+
items = redis.zrangebyscore global_backlog_key, last_id.to_i + 1, "+inf"
|
149
|
+
|
150
|
+
items.map! do |i|
|
151
|
+
pipe = i.index "|"
|
152
|
+
message_id = i[0..pipe].to_i
|
153
|
+
channel = i[pipe+1..-1]
|
154
|
+
m = get_message(channel, message_id)
|
155
|
+
m
|
156
|
+
end
|
157
|
+
|
158
|
+
items.compact!
|
159
|
+
items
|
160
|
+
end
|
161
|
+
|
162
|
+
def get_message(channel, message_id)
|
163
|
+
redis = pub_redis
|
164
|
+
backlog_key = backlog_key(channel)
|
165
|
+
|
166
|
+
items = redis.zrangebyscore backlog_key, message_id, message_id
|
167
|
+
if items && items[0]
|
168
|
+
MessageBus::Message.decode(items[0])
|
169
|
+
else
|
170
|
+
nil
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def subscribe(channel, last_id = nil)
|
175
|
+
# trivial implementation for now,
|
176
|
+
# can cut down on connections if we only have one global subscriber
|
177
|
+
raise ArgumentError unless block_given?
|
178
|
+
|
179
|
+
if last_id
|
180
|
+
# we need to translate this to a global id, at least give it a shot
|
181
|
+
# we are subscribing on global and global is always going to be bigger than local
|
182
|
+
# so worst case is a replay of a few messages
|
183
|
+
message = get_message(channel, last_id)
|
184
|
+
if message
|
185
|
+
last_id = message.global_id
|
186
|
+
end
|
187
|
+
end
|
188
|
+
global_subscribe(last_id) do |m|
|
189
|
+
yield m if m.channel == channel
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def process_global_backlog(highest_id, raise_error, &blk)
|
194
|
+
global_backlog(highest_id).each do |old|
|
195
|
+
if highest_id + 1 == old.global_id
|
196
|
+
yield old
|
197
|
+
highest_id = old.global_id
|
198
|
+
else
|
199
|
+
raise BackLogOutOfOrder.new(highest_id) if raise_error
|
200
|
+
if old.global_id > highest_id
|
201
|
+
yield old
|
202
|
+
highest_id = old.global_id
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
highest_id
|
207
|
+
end
|
208
|
+
|
209
|
+
def global_subscribe(last_id=nil, &blk)
|
210
|
+
raise ArgumentError unless block_given?
|
211
|
+
highest_id = last_id
|
212
|
+
|
213
|
+
clear_backlog = lambda do
|
214
|
+
retries = 4
|
215
|
+
begin
|
216
|
+
highest_id = process_global_backlog(highest_id, retries > 0, &blk)
|
217
|
+
rescue BackLogOutOfOrder => e
|
218
|
+
highest_id = e.highest_id
|
219
|
+
retries -= 1
|
220
|
+
sleep(rand(50) / 1000.0)
|
221
|
+
retry
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
|
226
|
+
begin
|
227
|
+
redis = new_redis_connection
|
228
|
+
|
229
|
+
if highest_id
|
230
|
+
clear_backlog.call(&blk)
|
231
|
+
end
|
232
|
+
|
233
|
+
redis.subscribe(redis_channel_name) do |on|
|
234
|
+
on.subscribe do
|
235
|
+
if highest_id
|
236
|
+
clear_backlog.call(&blk)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
on.message do |c,m|
|
240
|
+
m = MessageBus::Message.decode m
|
241
|
+
|
242
|
+
# we have 2 options
|
243
|
+
#
|
244
|
+
# 1. message came in the correct order GREAT, just deal with it
|
245
|
+
# 2. message came in the incorrect order COMPLICATED, wait a tiny bit and clear backlog
|
246
|
+
|
247
|
+
if highest_id.nil? || m.global_id == highest_id + 1
|
248
|
+
highest_id = m.global_id
|
249
|
+
yield m
|
250
|
+
else
|
251
|
+
clear_backlog.call(&blk)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
rescue => error
|
256
|
+
MessageBus.logger.warn "#{error} subscribe failed, reconnecting in 1 second. Call stack #{error.backtrace}"
|
257
|
+
sleep 1
|
258
|
+
retry
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
end
|
data/lib/message_bus.rb
ADDED
@@ -0,0 +1,272 @@
|
|
1
|
+
# require 'thin'
|
2
|
+
# require 'eventmachine'
|
3
|
+
# require 'rack'
|
4
|
+
# require 'redis'
|
5
|
+
|
6
|
+
require "message_bus/version"
|
7
|
+
require "message_bus/message"
|
8
|
+
require "message_bus/reliable_pub_sub"
|
9
|
+
require "message_bus/client"
|
10
|
+
require "message_bus/connection_manager"
|
11
|
+
require "message_bus/message_handler"
|
12
|
+
require "message_bus/diagnostics"
|
13
|
+
require "message_bus/rack/middleware"
|
14
|
+
require "message_bus/rack/diagnostics"
|
15
|
+
|
16
|
+
# we still need to take care of the logger
|
17
|
+
if defined?(::Rails)
|
18
|
+
require 'message_bus/rails/railtie'
|
19
|
+
end
|
20
|
+
|
21
|
+
module MessageBus; end
|
22
|
+
module MessageBus::Implementation
|
23
|
+
|
24
|
+
def cache_assets=(val)
|
25
|
+
@cache_assets = val
|
26
|
+
end
|
27
|
+
|
28
|
+
def cache_assets
|
29
|
+
if defined? @cache_assets
|
30
|
+
@cache_assets
|
31
|
+
else
|
32
|
+
true
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def logger=(logger)
|
37
|
+
@logger = logger
|
38
|
+
end
|
39
|
+
|
40
|
+
def logger
|
41
|
+
return @logger if @logger
|
42
|
+
require 'logger'
|
43
|
+
@logger = Logger.new(STDOUT)
|
44
|
+
end
|
45
|
+
|
46
|
+
def long_polling_enabled?
|
47
|
+
@long_polling_enabled == false ? false : true
|
48
|
+
end
|
49
|
+
|
50
|
+
def long_polling_enabled=(val)
|
51
|
+
@long_polling_enabled = val
|
52
|
+
end
|
53
|
+
|
54
|
+
def long_polling_interval=(millisecs)
|
55
|
+
@long_polling_interval = millisecs
|
56
|
+
end
|
57
|
+
|
58
|
+
def long_polling_interval
|
59
|
+
@long_polling_interval || 30 * 1000
|
60
|
+
end
|
61
|
+
|
62
|
+
def off
|
63
|
+
@off = true
|
64
|
+
end
|
65
|
+
|
66
|
+
def on
|
67
|
+
@off = false
|
68
|
+
end
|
69
|
+
|
70
|
+
# Allow us to inject a redis db
|
71
|
+
def redis_config=(config)
|
72
|
+
@redis_config = config
|
73
|
+
end
|
74
|
+
|
75
|
+
def redis_config
|
76
|
+
@redis_config ||= {}
|
77
|
+
end
|
78
|
+
|
79
|
+
def site_id_lookup(&blk)
|
80
|
+
@site_id_lookup = blk if blk
|
81
|
+
@site_id_lookup
|
82
|
+
end
|
83
|
+
|
84
|
+
def user_id_lookup(&blk)
|
85
|
+
@user_id_lookup = blk if blk
|
86
|
+
@user_id_lookup
|
87
|
+
end
|
88
|
+
|
89
|
+
def group_ids_lookup(&blk)
|
90
|
+
@group_ids_lookup = blk if blk
|
91
|
+
@group_ids_lookup
|
92
|
+
end
|
93
|
+
|
94
|
+
def is_admin_lookup(&blk)
|
95
|
+
@is_admin_lookup = blk if blk
|
96
|
+
@is_admin_lookup
|
97
|
+
end
|
98
|
+
|
99
|
+
def on_connect(&blk)
|
100
|
+
@on_connect = blk if blk
|
101
|
+
@on_connect
|
102
|
+
end
|
103
|
+
|
104
|
+
def on_disconnect(&blk)
|
105
|
+
@on_disconnect = blk if blk
|
106
|
+
@on_disconnect
|
107
|
+
end
|
108
|
+
|
109
|
+
def allow_broadcast=(val)
|
110
|
+
@allow_broadcast = val
|
111
|
+
end
|
112
|
+
|
113
|
+
def allow_broadcast?
|
114
|
+
@allow_broadcast ||=
|
115
|
+
if defined? ::Rails
|
116
|
+
::Rails.env.test? || ::Rails.env.development?
|
117
|
+
else
|
118
|
+
false
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def reliable_pub_sub
|
123
|
+
@reliable_pub_sub ||= MessageBus::ReliablePubSub.new redis_config
|
124
|
+
end
|
125
|
+
|
126
|
+
def enable_diagnostics
|
127
|
+
MessageBus::Diagnostics.enable
|
128
|
+
end
|
129
|
+
|
130
|
+
def publish(channel, data, opts = nil)
|
131
|
+
return if @off
|
132
|
+
|
133
|
+
user_ids = nil
|
134
|
+
group_ids = nil
|
135
|
+
if opts
|
136
|
+
user_ids = opts[:user_ids]
|
137
|
+
group_ids = opts[:group_ids]
|
138
|
+
end
|
139
|
+
|
140
|
+
encoded_data = JSON.dump({
|
141
|
+
data: data,
|
142
|
+
user_ids: user_ids,
|
143
|
+
group_ids: group_ids
|
144
|
+
})
|
145
|
+
|
146
|
+
reliable_pub_sub.publish(encode_channel_name(channel), encoded_data)
|
147
|
+
end
|
148
|
+
|
149
|
+
def blocking_subscribe(channel=nil, &blk)
|
150
|
+
if channel
|
151
|
+
reliable_pub_sub.subscribe(encode_channel_name(channel), &blk)
|
152
|
+
else
|
153
|
+
reliable_pub_sub.global_subscribe(&blk)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
ENCODE_SITE_TOKEN = "$|$"
|
158
|
+
|
159
|
+
# encode channel name to include site
|
160
|
+
def encode_channel_name(channel)
|
161
|
+
if MessageBus.site_id_lookup
|
162
|
+
raise ArgumentError.new channel if channel.include? ENCODE_SITE_TOKEN
|
163
|
+
"#{channel}#{ENCODE_SITE_TOKEN}#{MessageBus.site_id_lookup.call}"
|
164
|
+
else
|
165
|
+
channel
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def decode_channel_name(channel)
|
170
|
+
channel.split(ENCODE_SITE_TOKEN)
|
171
|
+
end
|
172
|
+
|
173
|
+
def subscribe(channel=nil, &blk)
|
174
|
+
subscribe_impl(channel, nil, &blk)
|
175
|
+
end
|
176
|
+
|
177
|
+
# subscribe only on current site
|
178
|
+
def local_subscribe(channel=nil, &blk)
|
179
|
+
site_id = MessageBus.site_id_lookup.call if MessageBus.site_id_lookup
|
180
|
+
subscribe_impl(channel, site_id, &blk)
|
181
|
+
end
|
182
|
+
|
183
|
+
def backlog(channel=nil, last_id)
|
184
|
+
old =
|
185
|
+
if channel
|
186
|
+
reliable_pub_sub.backlog(encode_channel_name(channel), last_id)
|
187
|
+
else
|
188
|
+
reliable_pub_sub.global_backlog(encode_channel_name(channel), last_id)
|
189
|
+
end
|
190
|
+
|
191
|
+
old.each{ |m|
|
192
|
+
decode_message!(m)
|
193
|
+
}
|
194
|
+
old
|
195
|
+
end
|
196
|
+
|
197
|
+
|
198
|
+
def last_id(channel)
|
199
|
+
reliable_pub_sub.last_id(encode_channel_name(channel))
|
200
|
+
end
|
201
|
+
|
202
|
+
protected
|
203
|
+
|
204
|
+
def decode_message!(msg)
|
205
|
+
channel, site_id = decode_channel_name(msg.channel)
|
206
|
+
msg.channel = channel
|
207
|
+
msg.site_id = site_id
|
208
|
+
parsed = JSON.parse(msg.data)
|
209
|
+
msg.data = parsed["data"]
|
210
|
+
msg.user_ids = parsed["user_ids"]
|
211
|
+
msg.group_ids = parsed["group_ids"]
|
212
|
+
end
|
213
|
+
|
214
|
+
def subscribe_impl(channel, site_id, &blk)
|
215
|
+
@subscriptions ||= {}
|
216
|
+
@subscriptions[site_id] ||= {}
|
217
|
+
@subscriptions[site_id][channel] ||= []
|
218
|
+
@subscriptions[site_id][channel] << blk
|
219
|
+
ensure_subscriber_thread
|
220
|
+
end
|
221
|
+
|
222
|
+
def ensure_subscriber_thread
|
223
|
+
@mutex ||= Mutex.new
|
224
|
+
@mutex.synchronize do
|
225
|
+
return if @subscriber_thread
|
226
|
+
@subscriber_thread = Thread.new do
|
227
|
+
reliable_pub_sub.global_subscribe do |msg|
|
228
|
+
begin
|
229
|
+
decode_message!(msg)
|
230
|
+
|
231
|
+
globals = @subscriptions[nil]
|
232
|
+
locals = @subscriptions[msg.site_id] if msg.site_id
|
233
|
+
|
234
|
+
global_globals = globals[nil] if globals
|
235
|
+
local_globals = locals[nil] if locals
|
236
|
+
|
237
|
+
globals = globals[msg.channel] if globals
|
238
|
+
locals = locals[msg.channel] if locals
|
239
|
+
|
240
|
+
multi_each(globals,locals, global_globals, local_globals) do |c|
|
241
|
+
begin
|
242
|
+
c.call msg
|
243
|
+
rescue => e
|
244
|
+
MessageBus.logger.warn "failed to deliver message, skipping #{msg.inspect}\n ex: #{e} backtrace: #{e.backtrace}"
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
rescue => e
|
249
|
+
MessageBus.logger.warn "failed to process message #{msg.inspect}\n ex: #{e} backtrace: #{e.backtrace}"
|
250
|
+
end
|
251
|
+
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def multi_each(*args,&block)
|
258
|
+
args.each do |a|
|
259
|
+
a.each(&block) if a
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
end
|
264
|
+
|
265
|
+
module MessageBus
|
266
|
+
extend MessageBus::Implementation
|
267
|
+
end
|
268
|
+
|
269
|
+
# allows for multiple buses per app
|
270
|
+
class MessageBus::Instance
|
271
|
+
include MessageBus::Implementation
|
272
|
+
end
|
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: message_bus
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sam Saffron
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-05-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rack
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.1.3
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.1.3
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: thin
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: eventmachine
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: redis
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: A message bus built on websockets
|
70
|
+
email:
|
71
|
+
- sam.saffron@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- README.md
|
77
|
+
- LICENSE
|
78
|
+
- lib/message_bus.rb
|
79
|
+
- lib/message_bus/rack/diagnostics.rb
|
80
|
+
- lib/message_bus/rack/middleware.rb
|
81
|
+
- lib/message_bus/message.rb
|
82
|
+
- lib/message_bus/connection_manager.rb
|
83
|
+
- lib/message_bus/message_handler.rb
|
84
|
+
- lib/message_bus/rails/railtie.rb
|
85
|
+
- lib/message_bus/version.rb
|
86
|
+
- lib/message_bus/diagnostics.rb
|
87
|
+
- lib/message_bus/client.rb
|
88
|
+
- lib/message_bus/reliable_pub_sub.rb
|
89
|
+
homepage: ''
|
90
|
+
licenses: []
|
91
|
+
metadata: {}
|
92
|
+
post_install_message:
|
93
|
+
rdoc_options: []
|
94
|
+
require_paths:
|
95
|
+
- lib
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - '>='
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - '>='
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
requirements: []
|
107
|
+
rubyforge_project:
|
108
|
+
rubygems_version: 2.0.2
|
109
|
+
signing_key:
|
110
|
+
specification_version: 4
|
111
|
+
summary: ''
|
112
|
+
test_files: []
|