message_channel 1.0.0
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.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/.rspec +3 -0
- data/.travis.yml +6 -0
- data/Gemfile +5 -0
- data/README.adoc +174 -0
- data/README.ja.adoc +175 -0
- data/Rakefile +96 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/message_channel.rb +18 -0
- data/lib/message_channel/base.rb +54 -0
- data/lib/message_channel/druby.rb +182 -0
- data/lib/message_channel/mongodb.rb +131 -0
- data/lib/message_channel/mqtt.rb +79 -0
- data/lib/message_channel/observer.rb +82 -0
- data/lib/message_channel/redis.rb +90 -0
- data/lib/message_channel/version.rb +3 -0
- data/message_channel.gemspec +30 -0
- data/sample/all_1.rb +48 -0
- data/sample/sample.txt +31 -0
- data/sample/sample_1.rb +18 -0
- data/sample/sample_2.rb +21 -0
- data/sample/sample_3.rb +31 -0
- metadata +150 -0
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "message_channel"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require "message_channel/version"
|
2
|
+
require "message_channel/base"
|
3
|
+
|
4
|
+
module MessageChannel
|
5
|
+
|
6
|
+
class Error < StandardError; end
|
7
|
+
|
8
|
+
class << self
|
9
|
+
include MessageChannel::Base
|
10
|
+
end
|
11
|
+
|
12
|
+
autoload :Observer, "message_channel/observer"
|
13
|
+
autoload :Druby, "message_channel/druby"
|
14
|
+
autoload :Mqtt, "message_channel/mqtt"
|
15
|
+
autoload :Redis, "message_channel/redis"
|
16
|
+
autoload :Mongodb, "message_channel/mongodb"
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module MessageChannel
|
2
|
+
class Error < StandardError; end
|
3
|
+
|
4
|
+
module Base
|
5
|
+
|
6
|
+
# uri:
|
7
|
+
# "observer"
|
8
|
+
# "druby://127.0.0.1:8787"
|
9
|
+
# "mqtt://127.0.0.1:1883"
|
10
|
+
# "redis://127.0.0.1:6379/0"
|
11
|
+
# "mongodb://127.0.0.1:27017/test?size=4000&name=_event_queue"
|
12
|
+
|
13
|
+
def new( uri = nil, type: nil, host: nil, port: nil, db: nil, size: nil, name: nil )
|
14
|
+
if uri
|
15
|
+
uris = URI.parse( uri )
|
16
|
+
if uris.scheme.nil? && uris.host.nil? && uris.port.nil? && uris.path
|
17
|
+
type = uris.path
|
18
|
+
params = []
|
19
|
+
else
|
20
|
+
type = uris.scheme || type
|
21
|
+
host = uris.host || host
|
22
|
+
port = uris.port || port
|
23
|
+
params = uris.path.gsub(/^\//, "").split('/')
|
24
|
+
query = Hash[ URI::decode_www_form(uris.query) ] rescue {}
|
25
|
+
size = query[:size] || size
|
26
|
+
name = query[:name] || name
|
27
|
+
end
|
28
|
+
else
|
29
|
+
parans = []
|
30
|
+
end
|
31
|
+
|
32
|
+
options = { host: host, port: port }
|
33
|
+
|
34
|
+
case type
|
35
|
+
when "observer", NilClass
|
36
|
+
MessageChannel::Observer.new( **options )
|
37
|
+
when "druby"
|
38
|
+
MessageChannel::Druby.new( **options )
|
39
|
+
when "mqtt"
|
40
|
+
MessageChannel::Mqtt.new( **options )
|
41
|
+
when "redis"
|
42
|
+
options[:db] = params.shift || db
|
43
|
+
MessageChannel::Redis.new( **options )
|
44
|
+
when "mongodb"
|
45
|
+
options[:db] = params.shift || db
|
46
|
+
options[:size] = size
|
47
|
+
options[:name] = name
|
48
|
+
MessageChannel::Mongodb.new( **options )
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
@@ -0,0 +1,182 @@
|
|
1
|
+
require "drb/drb"
|
2
|
+
require "json"
|
3
|
+
|
4
|
+
module MessageChannel
|
5
|
+
|
6
|
+
class Druby
|
7
|
+
|
8
|
+
class Broker
|
9
|
+
def initialize
|
10
|
+
@mutex = Mutex.new
|
11
|
+
@patterns = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def subscribe( pattern )
|
15
|
+
queue = Queue.new
|
16
|
+
queue_id = queue.object_id
|
17
|
+
@mutex.synchronize do
|
18
|
+
@patterns[queue_id] = [pattern, queue]
|
19
|
+
end
|
20
|
+
queue_id
|
21
|
+
end
|
22
|
+
|
23
|
+
def unsubscribe( queue_id )
|
24
|
+
@mutex.synchronize do
|
25
|
+
@patterns.delete( queue_id ) rescue nil
|
26
|
+
end
|
27
|
+
rescue
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def wait( queue_id )
|
32
|
+
@patterns[queue_id].last.pop
|
33
|
+
end
|
34
|
+
|
35
|
+
def fetch( pattern )
|
36
|
+
queue = Queue.new
|
37
|
+
queue_id = queue.object_id
|
38
|
+
@mutex.synchronize do
|
39
|
+
@patterns[queue_id] = [pattern, queue]
|
40
|
+
end
|
41
|
+
topic, items = * queue.pop
|
42
|
+
rescue
|
43
|
+
nil
|
44
|
+
ensure
|
45
|
+
@mutex.synchronize do
|
46
|
+
@patterns.delete( queue_id ) rescue nil
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def publish( topic, message )
|
51
|
+
@mutex.synchronize do
|
52
|
+
@patterns.each do |queue_id, items|
|
53
|
+
pattern, queue = *items
|
54
|
+
if File.fnmatch( pattern, topic, File::FNM_PATHNAME )
|
55
|
+
queue.push( [topic, message] )
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class Agent
|
63
|
+
def initialize( host: @@host, port: @@port )
|
64
|
+
@@host = host
|
65
|
+
@@port = port
|
66
|
+
@uri = "druby://#{host}:#{port}"
|
67
|
+
|
68
|
+
if !defined?( @@Broker ) || @@Broker.nil?
|
69
|
+
@@Broker = Broker.new
|
70
|
+
DRb.start_service( @uri, @@Broker ) rescue nil
|
71
|
+
end
|
72
|
+
|
73
|
+
@drb = DRbObject.new_with_uri( @uri )
|
74
|
+
@queue_ids = {}
|
75
|
+
end
|
76
|
+
|
77
|
+
def listen_once( pattern )
|
78
|
+
topic, message = * @drb.fetch( pattern )
|
79
|
+
end
|
80
|
+
|
81
|
+
def listen_each( pattern, &block )
|
82
|
+
queue_id = @drb.subscribe( pattern )
|
83
|
+
@queue_ids[pattern] = queue_id
|
84
|
+
while true
|
85
|
+
topic, message = * @drb.wait( queue_id )
|
86
|
+
break if topic.nil?
|
87
|
+
block.call( topic, message )
|
88
|
+
end
|
89
|
+
rescue => error
|
90
|
+
nil
|
91
|
+
ensure
|
92
|
+
@drb.unsubscribe( queue_id )
|
93
|
+
@queue_ids.delete( pattern ) rescue nil
|
94
|
+
end
|
95
|
+
|
96
|
+
def unlisten( pattern )
|
97
|
+
if queue_id = @queue_ids[pattern]
|
98
|
+
@drb.unsubscribe( queue_id )
|
99
|
+
@queue_ids.delete( pattern ) rescue nil
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def notify( topic, message )
|
104
|
+
@drb.publish( topic, message )
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
attr_reader :host, :port
|
109
|
+
|
110
|
+
def initialize( host: nil, port: nil )
|
111
|
+
@host = host || "127.0.0.1"
|
112
|
+
@port = ( port || 8787 ).to_i
|
113
|
+
@agent = Agent.new( host: @host, port: @port )
|
114
|
+
@threads = {}
|
115
|
+
end
|
116
|
+
|
117
|
+
def listen_once( *patterns )
|
118
|
+
queue = Queue.new
|
119
|
+
threads = {}
|
120
|
+
patterns.each do |pattern|
|
121
|
+
threads[pattern] = Thread.start(pattern) do |pattern|
|
122
|
+
agent = Agent.new
|
123
|
+
begin
|
124
|
+
topic, message = * agent.listen_once( pattern )
|
125
|
+
items = JSON.parse( message, symbolize_names: true )
|
126
|
+
queue.push( [topic, items] )
|
127
|
+
rescue => error
|
128
|
+
nil
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
topic, items = queue.pop
|
134
|
+
patterns.each do |pattern|
|
135
|
+
threads[pattern].kill rescue nil
|
136
|
+
threads.delete( pattern ) rescue nil
|
137
|
+
end
|
138
|
+
[topic, items]
|
139
|
+
rescue
|
140
|
+
nil
|
141
|
+
end
|
142
|
+
|
143
|
+
def listen_each( *patterns, &block )
|
144
|
+
patterns.each do |pattern|
|
145
|
+
@threads[pattern] = Thread.start(pattern) do |pattern|
|
146
|
+
begin
|
147
|
+
@agent.listen_each( pattern ) do |topic, message|
|
148
|
+
items = JSON.parse( message, symbolize_names: true )
|
149
|
+
block.call( topic, items )
|
150
|
+
end
|
151
|
+
rescue => error
|
152
|
+
nil
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def listen( *patterns, &block )
|
159
|
+
if block.nil?
|
160
|
+
listen_once( *patterns )
|
161
|
+
else
|
162
|
+
listen_each( *patterns ) do |topic, items|
|
163
|
+
block.call( topic, items )
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def unlisten( **patterns )
|
169
|
+
patterns.each do |pattern|
|
170
|
+
@agent.unlisten( pattern )
|
171
|
+
@threads.delete( pattern )
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def notify( topic, **items )
|
176
|
+
@agent.notify( topic, items.to_json )
|
177
|
+
end
|
178
|
+
|
179
|
+
end
|
180
|
+
|
181
|
+
end
|
182
|
+
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require "mongo"
|
2
|
+
require "json"
|
3
|
+
|
4
|
+
::Mongo::Logger.logger.level = Logger::ERROR
|
5
|
+
|
6
|
+
module MessageChannel
|
7
|
+
class Mongodb
|
8
|
+
SIZE = 8000
|
9
|
+
NAME = "_event_queue"
|
10
|
+
|
11
|
+
def initialize( host: nil, port: nil, db: nil, size: nil, name: nil )
|
12
|
+
@host = host || "127.0.0.1"
|
13
|
+
@port = ( port || 27017 ).to_i
|
14
|
+
@db = db || "test"
|
15
|
+
@size = ( size || SIZE ).to_i
|
16
|
+
@name = name || NAME
|
17
|
+
|
18
|
+
@url = "mongodb://#{ @host }:#{ @port }/#{ @db }"
|
19
|
+
@client = ::Mongo::Client.new( @url )
|
20
|
+
|
21
|
+
@threads = {}
|
22
|
+
@mutex = Mutex.new
|
23
|
+
|
24
|
+
@event_queue = get_event_queue
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_event_queue
|
28
|
+
event_queue = @mutex.synchronize do
|
29
|
+
if @client.database.collection_names.include?( @name )
|
30
|
+
event_queue = @client[ @name ]
|
31
|
+
else
|
32
|
+
event_queue = @client[ @name, capped: true, size: @size ]
|
33
|
+
event_queue.create
|
34
|
+
now = Time.now
|
35
|
+
doc = {
|
36
|
+
topic: "reset",
|
37
|
+
at: now.strftime("%Y%m%d.%H%M%S.%6L"),
|
38
|
+
}
|
39
|
+
event_queue.insert_one( doc )
|
40
|
+
event_queue
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def get_event_tail( event_queue )
|
46
|
+
filter = {}
|
47
|
+
if enum = event_queue.find( {}, { sort: { "$natural" => -1 } } ).to_enum
|
48
|
+
if doc = enum.next rescue nil
|
49
|
+
filter = { "_id"=>{ "$gt"=>doc["_id"] } }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
event_tail = event_queue.find( filter, { cursor_type: :tailable_await } ).to_enum
|
53
|
+
end
|
54
|
+
|
55
|
+
def listen_once( *patterns )
|
56
|
+
queue = Queue.new
|
57
|
+
threads = {}
|
58
|
+
patterns.each do |pattern|
|
59
|
+
threads[pattern] = ::Thread.start(pattern) do |pattern|
|
60
|
+
event_queue = get_event_queue
|
61
|
+
event_tail = get_event_tail( event_queue )
|
62
|
+
begin
|
63
|
+
while doc = event_tail.next
|
64
|
+
items = JSON.parse( doc.to_json, symbolize_names: true )
|
65
|
+
topic = items[:topic]
|
66
|
+
if File.fnmatch( pattern, topic, File::FNM_PATHNAME )
|
67
|
+
items.delete( :_id )
|
68
|
+
items.delete( :topic )
|
69
|
+
queue.push [topic, items]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
ensure
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
topic, items = queue.pop
|
78
|
+
patterns.each do |pattern|
|
79
|
+
threads[pattern].kill rescue nil
|
80
|
+
threads.delete( pattern ) rescue nil
|
81
|
+
end
|
82
|
+
[topic, items]
|
83
|
+
end
|
84
|
+
|
85
|
+
def listen_each( *patterns, &block )
|
86
|
+
patterns.each do |pattern|
|
87
|
+
@threads[pattern] = ::Thread.start(pattern) do |pattern|
|
88
|
+
begin
|
89
|
+
event_queue = get_event_queue
|
90
|
+
event_tail = get_event_tail( event_queue )
|
91
|
+
while doc = event_tail.next
|
92
|
+
items = JSON.parse( doc.to_json, symbolize_names: true )
|
93
|
+
topic = items[:topic]
|
94
|
+
if File.fnmatch( pattern, topic, File::FNM_PATHNAME )
|
95
|
+
items.delete( :_id )
|
96
|
+
items.delete( :topic )
|
97
|
+
block.call( topic, items )
|
98
|
+
end
|
99
|
+
end
|
100
|
+
ensure
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def listen( *patterns, &block )
|
107
|
+
if block.nil?
|
108
|
+
listen_once( *patterns )
|
109
|
+
else
|
110
|
+
listen_each( *patterns ) do |topic, items|
|
111
|
+
block.call( topic, items )
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def unlisten( **patterns )
|
117
|
+
patterns.each do |pattern|
|
118
|
+
@threads[pattern].kill rescue nil
|
119
|
+
@threads.delete( pattern ) rescue nil
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def notify( topic, **items )
|
124
|
+
items[:topic] = topic
|
125
|
+
@event_queue.insert_one( items )
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require "mqtt"
|
2
|
+
require "json"
|
3
|
+
|
4
|
+
module MessageChannel
|
5
|
+
class Mqtt
|
6
|
+
|
7
|
+
def initialize( host: nil, port: nil )
|
8
|
+
@host = host || "127.0.0.1"
|
9
|
+
@port = ( port || 1883 ).to_i
|
10
|
+
@mqtt = MQTT::Client.connect( @host, @port )
|
11
|
+
@threads = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def listen_once( *patterns )
|
15
|
+
queue = Queue.new
|
16
|
+
threads = {}
|
17
|
+
patterns.each do |pattern|
|
18
|
+
threads[pattern] = ::Thread.start(pattern) do |pattern|
|
19
|
+
mqtt = MQTT::Client.connect( @host, @port )
|
20
|
+
begin
|
21
|
+
mqtt.get( pattern ) do |topic, message|
|
22
|
+
items = JSON.parse( message, symbolize_names: true )
|
23
|
+
mqtt.disconnect rescue nil
|
24
|
+
queue.push [topic, items]
|
25
|
+
end
|
26
|
+
ensure
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
topic, items = queue.pop
|
32
|
+
patterns.each do |pattern|
|
33
|
+
threads[pattern].kill rescue nil
|
34
|
+
threads.delete( pattern ) rescue nil
|
35
|
+
end
|
36
|
+
[topic, items]
|
37
|
+
end
|
38
|
+
|
39
|
+
def listen_each( *patterns, &block )
|
40
|
+
patterns.each do |pattern|
|
41
|
+
@threads[pattern] = ::Thread.start(pattern) do |pattern|
|
42
|
+
mqtt = MQTT::Client.connect( @host, @port )
|
43
|
+
begin
|
44
|
+
mqtt.get( pattern ) do |topic, message|
|
45
|
+
items = JSON.parse( message, symbolize_names: true )
|
46
|
+
block.call( topic, items )
|
47
|
+
end
|
48
|
+
ensure
|
49
|
+
mqtt.disconnect rescue nil
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def listen( *patterns, &block )
|
56
|
+
if block.nil?
|
57
|
+
listen_once( *patterns )
|
58
|
+
else
|
59
|
+
listen_each( *patterns ) do |topic, items|
|
60
|
+
block.call( topic, items )
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def unlisten( **patterns )
|
66
|
+
patterns.each do |pattern|
|
67
|
+
@threads[pattern].kill rescue nil
|
68
|
+
@threads.delete( pattern ) rescue nil
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def notify( topic, **items )
|
73
|
+
@mqtt.publish( topic, items.to_json, false )
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|