moleculer 0.1.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 +14 -0
- data/.rspec +3 -0
- data/.rubocop.yml +44 -0
- data/.travis.yml +23 -0
- data/.yardopts +1 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +57 -0
- data/LICENSE.txt +21 -0
- data/README.md +59 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/examples/benchmark_server.rb +21 -0
- data/examples/client-server/client.rb +13 -0
- data/examples/client-server/server.rb +25 -0
- data/lib/moleculer/broker.rb +318 -0
- data/lib/moleculer/configuration.rb +109 -0
- data/lib/moleculer/context.rb +24 -0
- data/lib/moleculer/errors/action_not_found.rb +6 -0
- data/lib/moleculer/errors/invalid_action_response.rb +11 -0
- data/lib/moleculer/errors/local_node_already_registered.rb +6 -0
- data/lib/moleculer/errors/node_not_found.rb +6 -0
- data/lib/moleculer/errors/transporter_already_started.rb +6 -0
- data/lib/moleculer/node.rb +105 -0
- data/lib/moleculer/packets/base.rb +47 -0
- data/lib/moleculer/packets/disconnect.rb +10 -0
- data/lib/moleculer/packets/discover.rb +10 -0
- data/lib/moleculer/packets/event.rb +37 -0
- data/lib/moleculer/packets/heartbeat.rb +18 -0
- data/lib/moleculer/packets/info.rb +97 -0
- data/lib/moleculer/packets/req.rb +57 -0
- data/lib/moleculer/packets/res.rb +43 -0
- data/lib/moleculer/packets.rb +25 -0
- data/lib/moleculer/registry.rb +325 -0
- data/lib/moleculer/serializers/json.rb +23 -0
- data/lib/moleculer/serializers.rb +10 -0
- data/lib/moleculer/service/action.rb +60 -0
- data/lib/moleculer/service/base.rb +117 -0
- data/lib/moleculer/service/event.rb +50 -0
- data/lib/moleculer/service/remote.rb +80 -0
- data/lib/moleculer/service.rb +2 -0
- data/lib/moleculer/support/hash_util.rb +48 -0
- data/lib/moleculer/support/log_proxy.rb +67 -0
- data/lib/moleculer/support/open_struct.rb +15 -0
- data/lib/moleculer/support/string_util.rb +25 -0
- data/lib/moleculer/support.rb +4 -0
- data/lib/moleculer/transporters/redis.rb +207 -0
- data/lib/moleculer/transporters.rb +22 -0
- data/lib/moleculer/version.rb +3 -0
- data/lib/moleculer.rb +103 -0
- data/moleculer.gemspec +50 -0
- metadata +238 -0
@@ -0,0 +1,318 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
|
5
|
+
require_relative "registry"
|
6
|
+
require_relative "transporters"
|
7
|
+
require_relative "support"
|
8
|
+
|
9
|
+
module Moleculer
|
10
|
+
##
|
11
|
+
# The Broker is the primary component of Moleculer. It handles action, events, and communication with remote nodes.
|
12
|
+
# Only a single broker should be run for any given process, and it is automatically started when Moleculer::start or
|
13
|
+
# Moleculer::run is called.
|
14
|
+
class Broker
|
15
|
+
include Moleculer::Support
|
16
|
+
extend Forwardable
|
17
|
+
attr_reader :config
|
18
|
+
|
19
|
+
def_delegators :@config, :node_id, :heartbeat_interval, :services, :service_prefix
|
20
|
+
|
21
|
+
##
|
22
|
+
# @param config [Moleculer::Config] the broker configuration
|
23
|
+
def initialize(config)
|
24
|
+
@config = config
|
25
|
+
|
26
|
+
@config.broker = self
|
27
|
+
|
28
|
+
@logger = @config.logger.get_child("[BROKER]")
|
29
|
+
@registry = Registry.new(@config)
|
30
|
+
@transporter = Transporters.for(@config.transporter).new(@config)
|
31
|
+
@contexts = Concurrent::Map.new
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# Call the provided action.
|
36
|
+
#
|
37
|
+
# @param action_name [String] the action to call.
|
38
|
+
# @param params [Hash] the params with which to call the action
|
39
|
+
# @param meta [Hash] the metadata of the request
|
40
|
+
#
|
41
|
+
# @return [Hash] the return result of the action call
|
42
|
+
def call(action_name, params, meta: {}, node_id: nil, timeout: Moleculer.config.timeout)
|
43
|
+
action = node_id ? @registry.fetch_action_for_node_id(action_name, node_id) : @registry.fetch_action(action_name)
|
44
|
+
|
45
|
+
context = Context.new(
|
46
|
+
broker: self,
|
47
|
+
action: action,
|
48
|
+
params: params,
|
49
|
+
meta: meta,
|
50
|
+
timeout: timeout,
|
51
|
+
)
|
52
|
+
|
53
|
+
future = Concurrent::Promises.resolvable_future
|
54
|
+
|
55
|
+
@contexts[context.id] = {
|
56
|
+
context: context,
|
57
|
+
called_at: Time.now,
|
58
|
+
future: future,
|
59
|
+
}
|
60
|
+
|
61
|
+
action.execute(context, self)
|
62
|
+
|
63
|
+
future.value!(context.timeout)
|
64
|
+
end
|
65
|
+
|
66
|
+
def emit(event_name, payload)
|
67
|
+
@logger.debug("emitting event '#{event_name}'")
|
68
|
+
events = @registry.fetch_events_for_emit(event_name)
|
69
|
+
|
70
|
+
events.each { |e| e.execute(payload, self) }
|
71
|
+
end
|
72
|
+
|
73
|
+
def run
|
74
|
+
self_read, self_write = IO.pipe
|
75
|
+
|
76
|
+
%w[INT TERM].each do |sig|
|
77
|
+
trap sig do
|
78
|
+
self_write.puts(sig)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
begin
|
83
|
+
start
|
84
|
+
|
85
|
+
while (readable_io = IO.select([self_read]))
|
86
|
+
signal = readable_io.first[0].gets.strip
|
87
|
+
handle_signal(signal)
|
88
|
+
end
|
89
|
+
rescue Interrupt
|
90
|
+
stop
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def start
|
95
|
+
@logger.info "starting"
|
96
|
+
@transporter.connect
|
97
|
+
register_local_node
|
98
|
+
start_subscribers
|
99
|
+
publish_discover
|
100
|
+
publish_info
|
101
|
+
start_heartbeat
|
102
|
+
self
|
103
|
+
end
|
104
|
+
|
105
|
+
def stop
|
106
|
+
@logger.info "stopping"
|
107
|
+
publish(:disconnect)
|
108
|
+
@transporter.disconnect
|
109
|
+
exit 0
|
110
|
+
end
|
111
|
+
|
112
|
+
def wait_for_services(*services)
|
113
|
+
until (services = @registry.missing_services(*services)) && services.empty?
|
114
|
+
@logger.info "waiting for services '#{services.join(', ')}'"
|
115
|
+
sleep 0.1
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def local_node
|
120
|
+
@registry.local_node
|
121
|
+
end
|
122
|
+
|
123
|
+
##
|
124
|
+
# Processes an incoming message and passes it to the appropriate channel for handling
|
125
|
+
#
|
126
|
+
# @param [String] channel the channel in which the message came in on
|
127
|
+
# @param [Hash] message the raw deserialized message
|
128
|
+
def process_message(channel, message)
|
129
|
+
subscribers[channel] << Packets.for(channel.split(".")[1]).new(message) if subscribers[channel]
|
130
|
+
rescue StandardError => e
|
131
|
+
@logger.error e
|
132
|
+
end
|
133
|
+
|
134
|
+
def process_response(packet)
|
135
|
+
context = @contexts.delete(packet.id)
|
136
|
+
context[:future].fulfill(packet.data)
|
137
|
+
end
|
138
|
+
|
139
|
+
def process_event(packet)
|
140
|
+
@logger.debug("processing event '#{packet.event}'")
|
141
|
+
events = @registry.fetch_events_for_node_id(packet.event, node_id)
|
142
|
+
|
143
|
+
events.each { |e| e.execute(packet.data) }
|
144
|
+
rescue StandardError => e
|
145
|
+
@logger.error e
|
146
|
+
end
|
147
|
+
|
148
|
+
def process_request(packet)
|
149
|
+
@logger.debug "processing request #{packet.id}"
|
150
|
+
action = @registry.fetch_action_for_node_id(packet.action, node_id)
|
151
|
+
node = @registry.fetch_node(packet.sender)
|
152
|
+
|
153
|
+
context = Context.new(
|
154
|
+
id: packet.id,
|
155
|
+
broker: self,
|
156
|
+
action: action,
|
157
|
+
params: packet.params,
|
158
|
+
meta: packet.meta,
|
159
|
+
timeout: @config.timeout,
|
160
|
+
)
|
161
|
+
|
162
|
+
response = action.execute(context, self)
|
163
|
+
|
164
|
+
publish_res(
|
165
|
+
id: context.id,
|
166
|
+
success: true,
|
167
|
+
data: response,
|
168
|
+
error: {},
|
169
|
+
meta: context.meta,
|
170
|
+
stream: false,
|
171
|
+
node: node,
|
172
|
+
)
|
173
|
+
end
|
174
|
+
|
175
|
+
private
|
176
|
+
|
177
|
+
def handle_signal(sig)
|
178
|
+
case sig
|
179
|
+
when "INT", "TERM"
|
180
|
+
raise Interrupt
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def publish(packet_type, message = {})
|
185
|
+
packet = Packets.for(packet_type).new(message)
|
186
|
+
@transporter.publish(packet)
|
187
|
+
end
|
188
|
+
|
189
|
+
def publish_event(event_data)
|
190
|
+
publish_to_node(:event, event_data.delete(:node), event_data)
|
191
|
+
end
|
192
|
+
|
193
|
+
def publish_heartbeat
|
194
|
+
publish(:heartbeat)
|
195
|
+
end
|
196
|
+
|
197
|
+
##
|
198
|
+
# Publishes the discover packet
|
199
|
+
def publish_discover
|
200
|
+
publish(:discover)
|
201
|
+
end
|
202
|
+
|
203
|
+
def publish_info(node_id = nil)
|
204
|
+
return publish(:info, @registry.local_node.as_json) unless node_id
|
205
|
+
|
206
|
+
node = @registry.safe_fetch_node(node_id) || node_id
|
207
|
+
publish_to_node(:info, node, @registry.local_node.as_json)
|
208
|
+
end
|
209
|
+
|
210
|
+
def publish_req(request_data)
|
211
|
+
publish_to_node(:req, request_data.delete(:node), request_data)
|
212
|
+
end
|
213
|
+
|
214
|
+
def publish_res(response_data)
|
215
|
+
publish_to_node(:res, response_data.delete(:node), response_data)
|
216
|
+
end
|
217
|
+
|
218
|
+
def publish_to_node(packet_type, node, message = {})
|
219
|
+
packet = Packets.for(packet_type).new(message.merge(node: node))
|
220
|
+
@transporter.publish(packet)
|
221
|
+
end
|
222
|
+
|
223
|
+
def register_local_node
|
224
|
+
@logger.info "registering #{services.length} local services"
|
225
|
+
node = Node.new(
|
226
|
+
node_id: node_id,
|
227
|
+
services: services,
|
228
|
+
local: true,
|
229
|
+
)
|
230
|
+
@registry.register_node(node)
|
231
|
+
end
|
232
|
+
|
233
|
+
def register_or_update_remote_node(info_packet)
|
234
|
+
node = Node.from_remote_info(info_packet)
|
235
|
+
@registry.register_node(node)
|
236
|
+
end
|
237
|
+
|
238
|
+
def register_local_services
|
239
|
+
services.each do |service|
|
240
|
+
register_service(service)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def register_service(service)
|
245
|
+
@registry.register_local_service(service)
|
246
|
+
end
|
247
|
+
|
248
|
+
def start_heartbeat
|
249
|
+
Concurrent::TimerTask.new(execution_interval: heartbeat_interval) do
|
250
|
+
publish_heartbeat
|
251
|
+
@registry.expire_nodes
|
252
|
+
end.execute
|
253
|
+
end
|
254
|
+
|
255
|
+
def start_subscribers
|
256
|
+
subscribe_to_info
|
257
|
+
subscribe_to_res
|
258
|
+
subscribe_to_req
|
259
|
+
subscribe_to_events
|
260
|
+
subscribe_to_discover
|
261
|
+
subscribe_to_disconnect
|
262
|
+
subscribe_to_heartbeat
|
263
|
+
end
|
264
|
+
|
265
|
+
def subscribe_to_events
|
266
|
+
subscribe("MOL.EVENT.#{node_id}") do |packet|
|
267
|
+
process_event(packet)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
def subscribe_to_info
|
272
|
+
subscribe("MOL.INFO.#{node_id}") do |packet|
|
273
|
+
register_or_update_remote_node(packet)
|
274
|
+
end
|
275
|
+
subscribe("MOL.INFO") do |packet|
|
276
|
+
register_or_update_remote_node(packet)
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
def subscribe_to_res
|
281
|
+
subscribe("MOL.RES.#{node_id}") do |packet|
|
282
|
+
process_response(packet)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
def subscribe_to_req
|
287
|
+
subscribe("MOL.REQ.#{node_id}") do |packet|
|
288
|
+
process_request(packet)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
def subscribe_to_discover
|
293
|
+
subscribe("MOL.DISCOVER") do |packet|
|
294
|
+
publish_info(packet.sender) unless packet.sender == node_id
|
295
|
+
end
|
296
|
+
subscribe("MOL.DISCOVER.#{node_id}") do |packet|
|
297
|
+
publish_info(packet.sender)
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
def subscribe_to_heartbeat
|
302
|
+
subscribe("MOL.HEARTBEAT") do |packet|
|
303
|
+
node = @registry.safe_fetch_node(packet.sender)
|
304
|
+
node.beat
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
def subscribe_to_disconnect
|
309
|
+
subscribe("MOL.DISCONNECT") do |packet|
|
310
|
+
@registry.remove_node(packet.sender)
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
def subscribe(channel, &block)
|
315
|
+
@transporter.subscribe(channel, &block)
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module Moleculer
|
2
|
+
##
|
3
|
+
# Handles Moleculer configuration
|
4
|
+
# @private
|
5
|
+
class Configuration
|
6
|
+
##
|
7
|
+
# @private
|
8
|
+
class ServiceList
|
9
|
+
def initialize(configuration)
|
10
|
+
@configuration = configuration
|
11
|
+
@services = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def <<(service)
|
15
|
+
service.broker = @configuration.broker
|
16
|
+
@services << service
|
17
|
+
end
|
18
|
+
|
19
|
+
def length
|
20
|
+
@services.length
|
21
|
+
end
|
22
|
+
|
23
|
+
def first
|
24
|
+
@services.first
|
25
|
+
end
|
26
|
+
|
27
|
+
def map(&block)
|
28
|
+
@services.map(&block)
|
29
|
+
end
|
30
|
+
|
31
|
+
def include?(service)
|
32
|
+
@services.include?(service)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private_constant :ServiceList
|
37
|
+
|
38
|
+
class << self
|
39
|
+
attr_reader :accessors
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def config_accessor(attribute, default = nil, &block)
|
44
|
+
@accessors ||= {}
|
45
|
+
@accessors[attribute.to_sym] = { default: default, block: block }
|
46
|
+
|
47
|
+
class_eval <<-method
|
48
|
+
def #{attribute}
|
49
|
+
@#{attribute} ||= default_for("#{attribute}".to_sym)
|
50
|
+
end
|
51
|
+
method
|
52
|
+
|
53
|
+
instance_eval do
|
54
|
+
attr_writer attribute.to_sym
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
config_accessor :log_file
|
60
|
+
config_accessor :log_level, :debug
|
61
|
+
config_accessor :logger do |c|
|
62
|
+
logger = Ougai::Logger.new(c.log_file || STDOUT)
|
63
|
+
logger.formatter = Ougai::Formatters::Readable.new("MOL")
|
64
|
+
logger.level = c.log_level
|
65
|
+
Moleculer::Support::LogProxy.new(logger)
|
66
|
+
end
|
67
|
+
config_accessor :heartbeat_interval, 5
|
68
|
+
config_accessor :timeout, 5
|
69
|
+
config_accessor :transporter, "redis://localhost"
|
70
|
+
config_accessor :serializer, :json
|
71
|
+
config_accessor :node_id, "#{Socket.gethostname.downcase}-#{Process.pid}"
|
72
|
+
config_accessor :service_prefix
|
73
|
+
|
74
|
+
attr_accessor :broker
|
75
|
+
|
76
|
+
def initialize(options={})
|
77
|
+
options.each do |option, value|
|
78
|
+
send("#{option}=".to_sym, value)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def services
|
83
|
+
@services ||= ServiceList.new(self)
|
84
|
+
end
|
85
|
+
|
86
|
+
def services=(array)
|
87
|
+
@services = ServiceList.new(self)
|
88
|
+
array.each { |s| @services << s}
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def accessors
|
94
|
+
self.class.accessors
|
95
|
+
end
|
96
|
+
|
97
|
+
def default_for(attribute)
|
98
|
+
accessors[attribute][:default] || (has_block_for?(attribute) ? block_for(attribute).call(self) : nil)
|
99
|
+
end
|
100
|
+
|
101
|
+
def block_for(attribute)
|
102
|
+
accessors[attribute][:block]
|
103
|
+
end
|
104
|
+
|
105
|
+
def has_block_for?(attribute)
|
106
|
+
accessors[attribute][:block] && true
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Moleculer
|
2
|
+
class Context
|
3
|
+
attr_reader :request_id,
|
4
|
+
:action,
|
5
|
+
:params,
|
6
|
+
:meta,
|
7
|
+
:level,
|
8
|
+
:timeout,
|
9
|
+
:id
|
10
|
+
|
11
|
+
def initialize(broker:, action:, params:, meta:, parent_id: nil, level: 1, timeout:, id: nil)
|
12
|
+
@id = id ? id : SecureRandom.uuid
|
13
|
+
@broker = broker
|
14
|
+
@action = action
|
15
|
+
@request_id = SecureRandom.uuid
|
16
|
+
@parent_id = parent_id
|
17
|
+
@params = params
|
18
|
+
@meta = meta
|
19
|
+
@level = level
|
20
|
+
@timeout = timeout
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Moleculer
|
2
|
+
module Errors
|
3
|
+
##
|
4
|
+
# Raised when an action does not return a hash
|
5
|
+
class InvalidActionResponse < StandardError
|
6
|
+
def initialize(response)
|
7
|
+
super "Action must return a Hash, instead it returned a #{response.class.name}"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "socket"
|
4
|
+
|
5
|
+
module Moleculer
|
6
|
+
##
|
7
|
+
# Nodes are a representation of communicating apps within the same event bus.
|
8
|
+
# A node is something that emits/listens to events within the bus and
|
9
|
+
# communicates accordingly.
|
10
|
+
class Node
|
11
|
+
class << self
|
12
|
+
def from_remote_info(info_packet)
|
13
|
+
new(
|
14
|
+
services: info_packet.services,
|
15
|
+
node_id: info_packet.sender,
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :id,
|
21
|
+
:services
|
22
|
+
|
23
|
+
def initialize(options = {})
|
24
|
+
@id = options.fetch(:node_id)
|
25
|
+
@local = options.fetch(:local, false)
|
26
|
+
@hostname = options.fetch(:hostname, Socket.gethostname)
|
27
|
+
|
28
|
+
svcs = options.fetch(:services)
|
29
|
+
# TODO: move this up to from_remote_info
|
30
|
+
svcs.map! { |service| Service.from_remote_info(service, self) } if svcs.first.is_a? Hash
|
31
|
+
@services = Hash[svcs.map { |s| [s.service_name, s] }]
|
32
|
+
end
|
33
|
+
|
34
|
+
def register_service(service)
|
35
|
+
@services[service.name] = service
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# @return [Hash] returns a key, value mapping of available actions on this node
|
40
|
+
# TODO: refactor this into a list object
|
41
|
+
def actions
|
42
|
+
unless @actions
|
43
|
+
@actions = {}
|
44
|
+
|
45
|
+
@services.each_value { |s| s.actions.each { |key, value| @actions[key] = value } }
|
46
|
+
end
|
47
|
+
@actions
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# @return [Hash] returns a key value mapping of events on this node
|
52
|
+
# TODO: refactor this into a list object
|
53
|
+
def events
|
54
|
+
unless @events
|
55
|
+
@events = {}
|
56
|
+
@services.each_value do |s|
|
57
|
+
s.events.each do |key, value|
|
58
|
+
@events[key] ||= []
|
59
|
+
@events[key] << value
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
@events
|
64
|
+
end
|
65
|
+
|
66
|
+
##
|
67
|
+
# @return [Time] the time of the last heartbeat, or Time#now if the node is local.
|
68
|
+
def last_heartbeat_at
|
69
|
+
return Time.now if local?
|
70
|
+
|
71
|
+
@last_heartbeat_at || Time.now
|
72
|
+
end
|
73
|
+
|
74
|
+
##
|
75
|
+
# Updates the last heartbeat to Time#now
|
76
|
+
def beat
|
77
|
+
@last_heartbeat_at = Time.now
|
78
|
+
end
|
79
|
+
|
80
|
+
def local?
|
81
|
+
@local
|
82
|
+
end
|
83
|
+
|
84
|
+
def as_json
|
85
|
+
{
|
86
|
+
config: {},
|
87
|
+
seq: 1,
|
88
|
+
ipList: [],
|
89
|
+
hostname: @hostname,
|
90
|
+
services: @services.values.map(&:as_json),
|
91
|
+
client: client_attrubutes
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def client_attrubutes
|
98
|
+
{
|
99
|
+
type: "Ruby",
|
100
|
+
version: Moleculer::VERSION,
|
101
|
+
lang_version: RUBY_VERSION
|
102
|
+
}
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require_relative "../support"
|
2
|
+
|
3
|
+
module Moleculer
|
4
|
+
module Packets
|
5
|
+
##
|
6
|
+
# @abstract Subclass for packet types.
|
7
|
+
class Base
|
8
|
+
include Support
|
9
|
+
##
|
10
|
+
# The protocol version
|
11
|
+
attr_reader :ver
|
12
|
+
|
13
|
+
##
|
14
|
+
# The sender of the packet
|
15
|
+
attr_reader :sender
|
16
|
+
|
17
|
+
def self.packet_name
|
18
|
+
name.split("::").last.upcase
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# @param data [Hash] the raw packet data
|
23
|
+
# @options data [String] :ver the protocol version, defaults to `'3'`
|
24
|
+
# @options data [String] :sender the packet sender, defaults to `Moleculer#node_id`
|
25
|
+
def initialize(data = {})
|
26
|
+
@ver = HashUtil.fetch(data, :ver, "3")
|
27
|
+
@sender = HashUtil.fetch(data, :sender, Moleculer.config.node_id)
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# The publishing topic for the packet. This is used to publish packets to the moleculer network. Override as
|
32
|
+
# needed.
|
33
|
+
#
|
34
|
+
# @return [String] the pub/sub topic to publish to
|
35
|
+
def topic
|
36
|
+
"MOL.#{self.class.packet_name}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def as_json
|
40
|
+
{
|
41
|
+
ver: ver,
|
42
|
+
sender: sender,
|
43
|
+
}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require_relative "base"
|
2
|
+
|
3
|
+
module Moleculer
|
4
|
+
module Packets
|
5
|
+
##
|
6
|
+
# Represents a EVENT packet
|
7
|
+
class Event < Base
|
8
|
+
attr_reader :event,
|
9
|
+
:data,
|
10
|
+
:broadcast,
|
11
|
+
:groups
|
12
|
+
|
13
|
+
def initialize(data)
|
14
|
+
super(data)
|
15
|
+
|
16
|
+
@event = HashUtil.fetch(data, :event)
|
17
|
+
@data = HashUtil.fetch(data, :data)
|
18
|
+
@broadcast = HashUtil.fetch(data, :broadcast)
|
19
|
+
@groups = HashUtil.fetch(data, :groups, [])
|
20
|
+
@node = HashUtil.fetch(data, :node, nil)
|
21
|
+
end
|
22
|
+
|
23
|
+
def as_json
|
24
|
+
super.merge(
|
25
|
+
event: @event,
|
26
|
+
data: @data,
|
27
|
+
broadcast: @broadcast,
|
28
|
+
groups: @groups,
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
def topic
|
33
|
+
"#{super}.#{@node.id}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|