rjr 0.9.0 → 0.11.7
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/rjr-client +150 -0
- data/bin/rjr-client-launcher +38 -0
- data/bin/rjr-server +54 -32
- data/lib/rjr/amqp_node.rb +95 -37
- data/lib/rjr/common.rb +60 -10
- data/lib/rjr/dispatcher.rb +98 -16
- data/lib/rjr/em_adapter.rb +110 -0
- data/lib/rjr/inspect.rb +66 -0
- data/lib/rjr/local_node.rb +1 -0
- data/lib/rjr/message.rb +123 -3
- data/lib/rjr/missing_node.rb +17 -0
- data/lib/rjr/multi_node.rb +3 -0
- data/lib/rjr/node.rb +79 -67
- data/lib/rjr/tcp_node.rb +146 -53
- data/lib/rjr/tcp_node2.rb +4 -0
- data/lib/rjr/thread_pool2.rb +271 -0
- data/lib/rjr/util.rb +104 -0
- data/lib/rjr/web_node.rb +115 -23
- data/lib/rjr/ws_node.rb +162 -34
- data/lib/rjr.rb +5 -10
- data/specs/dispatcher_spec.rb +81 -0
- data/specs/em_adapter_spec.rb +85 -0
- data/specs/inspect_spec.rb +60 -0
- data/specs/message_spec.rb +58 -0
- data/specs/multi_node_spec.rb +5 -4
- data/specs/node_spec.rb +140 -4
- data/specs/tcp_node_spec.rb +1 -0
- data/specs/thread_pool_spec.rb +41 -0
- data/specs/util_spec.rb +46 -0
- data/specs/web_node_spec.rb +1 -0
- data/specs/ws_node_spec.rb +1 -1
- metadata +24 -8
- data/lib/rjr/web_socket.rb +0 -589
data/lib/rjr/dispatcher.rb
CHANGED
@@ -7,6 +7,7 @@
|
|
7
7
|
# Licensed under the Apache License, Version 2.0
|
8
8
|
|
9
9
|
require 'rjr/common'
|
10
|
+
require 'json'
|
10
11
|
|
11
12
|
module RJR
|
12
13
|
|
@@ -14,9 +15,11 @@ module RJR
|
|
14
15
|
class Request
|
15
16
|
# name of the method which request is for
|
16
17
|
attr_accessor :method
|
18
|
+
alias :rjr_method :method
|
17
19
|
|
18
20
|
# array of arguments which to pass to the rpc method handler
|
19
21
|
attr_accessor :method_args
|
22
|
+
alias :rjr_method_args :method_args
|
20
23
|
|
21
24
|
# hash of keys/values corresponding to optional headers received as part of of the request
|
22
25
|
attr_accessor :headers
|
@@ -46,15 +49,17 @@ class Request
|
|
46
49
|
# @option args [Symbol] :rjr_node_type type of the rjr node which request was received on
|
47
50
|
# @option args [Callable] :handler callable object registered to the specified method which to invoke request on with arguments
|
48
51
|
def initialize(args = {})
|
49
|
-
@method = args[:method]
|
50
|
-
@
|
51
|
-
@
|
52
|
+
@method = args[:method] || args['method']
|
53
|
+
@rjr_method = @method
|
54
|
+
@method_args = args[:method_args] || args['method_args']
|
55
|
+
@rjr_method_args = @method_args
|
56
|
+
@headers = args[:headers] || args['headers']
|
52
57
|
@client_ip = args[:client_ip]
|
53
58
|
@client_port = args[:client_port]
|
54
59
|
@rjr_callback = args[:rjr_callback]
|
55
60
|
@rjr_node = args[:rjr_node]
|
56
|
-
@rjr_node_id = args[:rjr_node_id]
|
57
|
-
@rjr_node_type = args[:rjr_node_type]
|
61
|
+
@rjr_node_id = args[:rjr_node_id] || args['rjr_node_id']
|
62
|
+
@rjr_node_type = args[:rjr_node_type] || args['rjr_node_type']
|
58
63
|
@handler = args[:handler]
|
59
64
|
end
|
60
65
|
|
@@ -62,6 +67,7 @@ class Request
|
|
62
67
|
# method parameters in the local scope
|
63
68
|
def handle
|
64
69
|
RJR::Logger.info "Dispatching '#{@method}' request with parameters (#{@method_args.join(',')}) on #{@rjr_node_type}-node(#{@rjr_node_id})"
|
70
|
+
# TODO compare arity of method to number of args ?
|
65
71
|
retval = instance_exec(*@method_args, &@handler)
|
66
72
|
RJR::Logger.info "#{@method} request with parameters (#{@method_args.join(',')}) returning #{retval}"
|
67
73
|
return retval
|
@@ -100,17 +106,17 @@ class Result
|
|
100
106
|
@error_message = nil
|
101
107
|
@error_class = nil
|
102
108
|
|
103
|
-
if args.has_key?(:result)
|
109
|
+
if args.has_key?(:result) || args.has_key?('result')
|
104
110
|
@success = true
|
105
111
|
@failed = false
|
106
|
-
@result = args[:result]
|
112
|
+
@result = args[:result] || args['result']
|
107
113
|
|
108
|
-
elsif args.has_key?(:error_code)
|
114
|
+
elsif args.has_key?(:error_code) || args.has_key?('error_code')
|
109
115
|
@success = false
|
110
116
|
@failed = true
|
111
|
-
@error_code = args[:error_code]
|
112
|
-
@error_msg = args[:error_msg]
|
113
|
-
@error_class = args[:error_class]
|
117
|
+
@error_code = args[:error_code] || args['error_code']
|
118
|
+
@error_msg = args[:error_msg] || args['error_msg']
|
119
|
+
@error_class = args[:error_class] || args['error_class']
|
114
120
|
|
115
121
|
end
|
116
122
|
end
|
@@ -177,20 +183,79 @@ class Handler
|
|
177
183
|
def handle(args = {})
|
178
184
|
return Result.method_not_found(args[:missing_name]) if @method_name.nil?
|
179
185
|
|
186
|
+
result = nil
|
180
187
|
begin
|
181
188
|
request = Request.new args.merge(:method => @method_name,
|
182
189
|
:handler => @handler_proc)
|
183
190
|
retval = request.handle
|
184
|
-
|
191
|
+
result = Result.new(:result => retval)
|
185
192
|
|
186
193
|
rescue Exception => e
|
187
194
|
RJR::Logger.warn ["Exception Raised in #{method_name} handler #{e}"] + e.backtrace
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
:error_class => e.class)
|
195
|
+
result = Result.new(:error_code => -32000,
|
196
|
+
:error_msg => e.to_s,
|
197
|
+
:error_class => e.class)
|
192
198
|
|
193
199
|
end
|
200
|
+
|
201
|
+
DispatcherStat << DispatcherStat.new(request, result)
|
202
|
+
return result
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# Tracks high level dispatcher states
|
207
|
+
class DispatcherStat
|
208
|
+
# Request invoked
|
209
|
+
attr_reader :request
|
210
|
+
|
211
|
+
# Result returned
|
212
|
+
attr_reader :result
|
213
|
+
|
214
|
+
# Initialized the stat w/ the corresponding request/result
|
215
|
+
def initialize(request, result)
|
216
|
+
@request = request
|
217
|
+
@result = result
|
218
|
+
end
|
219
|
+
|
220
|
+
# Global stats registry
|
221
|
+
def self.stats
|
222
|
+
@stats ||= []
|
223
|
+
end
|
224
|
+
|
225
|
+
# Reinit the stats registry
|
226
|
+
def self.reset
|
227
|
+
@stats = []
|
228
|
+
end
|
229
|
+
|
230
|
+
# Add stat to the global registry
|
231
|
+
def self.<<(s)
|
232
|
+
@stats ||= []
|
233
|
+
@stats << s
|
234
|
+
self
|
235
|
+
end
|
236
|
+
|
237
|
+
# Convert stat to json representation and return it
|
238
|
+
def to_json(*a)
|
239
|
+
{
|
240
|
+
'json_class' => self.class.name,
|
241
|
+
'data' =>
|
242
|
+
{:request => {:method => request.method,
|
243
|
+
:method_args => request.method_args,
|
244
|
+
:headers => request.headers,
|
245
|
+
:rjr_node_type => request.rjr_node_type,
|
246
|
+
:rjr_node_id => request.rjr_node_id
|
247
|
+
},
|
248
|
+
:result => {:result => result.result,
|
249
|
+
:error_code => result.error_code,
|
250
|
+
:error_msg => result.error_msg,
|
251
|
+
:error_class => result.error_class} }
|
252
|
+
}.to_json(*a)
|
253
|
+
end
|
254
|
+
|
255
|
+
# Create new stat from json representation
|
256
|
+
def self.json_create(o)
|
257
|
+
stat = new(Request.new(o['data']['request']), Result.new(o['data']['result']))
|
258
|
+
return stat
|
194
259
|
end
|
195
260
|
end
|
196
261
|
|
@@ -231,11 +296,28 @@ class Dispatcher
|
|
231
296
|
method_names = Array(method_names) unless method_names.is_a?(Array)
|
232
297
|
@@handlers ||= {}
|
233
298
|
method_names.each { |method_name|
|
299
|
+
# TODO support registering multiple handlers per method? (and in dispatch_request below)
|
234
300
|
@@handlers[method_name] = Handler.new args.merge(:method => method_name,
|
235
301
|
:handler => handler)
|
236
302
|
}
|
237
303
|
end
|
238
304
|
|
305
|
+
# Clear registered method handlers
|
306
|
+
def self.clear!
|
307
|
+
@@handlers = {}
|
308
|
+
end
|
309
|
+
|
310
|
+
# Return boolean indicating if handler for the specifed method has been registered
|
311
|
+
def self.has_handler_for?(method_name)
|
312
|
+
@@handlers ||= {}
|
313
|
+
!@@handlers.find { |k,v| k == method_name }.nil?
|
314
|
+
end
|
315
|
+
|
316
|
+
# Return the handler for the specified method
|
317
|
+
def self.handler_for(method_name)
|
318
|
+
@@handlers[method_name]
|
319
|
+
end
|
320
|
+
|
239
321
|
# Helper used by RJR nodes to dispatch requests received via transports to
|
240
322
|
# registered handlers.
|
241
323
|
def self.dispatch_request(method_name, args = {})
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# EventMachine Adapter
|
2
|
+
#
|
3
|
+
# Copyright (C) 2012 Mohammed Morsi <mo@morsi.org>
|
4
|
+
# Licensed under the Apache License, Version 2.0
|
5
|
+
|
6
|
+
require 'singleton'
|
7
|
+
require 'eventmachine'
|
8
|
+
|
9
|
+
# EventMachine wrapper / helper interface, ties reactor
|
10
|
+
# lifecycle to an instance of this class.
|
11
|
+
#
|
12
|
+
# TODO move to the RJR namespace
|
13
|
+
class EMManager
|
14
|
+
|
15
|
+
# Run reactor in its own interally managed thread
|
16
|
+
attr_accessor :reactor_thread
|
17
|
+
|
18
|
+
# EMManager initializer
|
19
|
+
def initialize
|
20
|
+
@em_lock = Mutex.new
|
21
|
+
end
|
22
|
+
|
23
|
+
# Start the eventmachine reactor thread if not running
|
24
|
+
def start
|
25
|
+
@em_lock.synchronize{
|
26
|
+
# TODO on event of the process ending this thread will be
|
27
|
+
# shutdown before a local finalizer can be run,
|
28
|
+
# would be good to gracefully shut this down / wait for completion
|
29
|
+
@reactor_thread = Thread.new {
|
30
|
+
begin
|
31
|
+
EventMachine.run
|
32
|
+
rescue Exception => e
|
33
|
+
# TODO option to autorestart the reactor on errors ?
|
34
|
+
puts "Critical exception #{e}\n#{e.backtrace.join("\n")}"
|
35
|
+
ensure
|
36
|
+
@reactor_thread = nil
|
37
|
+
end
|
38
|
+
} unless @reactor_thread
|
39
|
+
}
|
40
|
+
sleep 0.01 until EventMachine.reactor_running? # XXX hack but needed
|
41
|
+
end
|
42
|
+
|
43
|
+
# Schedule a new job to be run in event machine
|
44
|
+
# @param [Callable] bl callback to be invoked by eventmachine
|
45
|
+
def schedule(&bl)
|
46
|
+
EventMachine.schedule &bl
|
47
|
+
end
|
48
|
+
|
49
|
+
# Schedule a job to be run once after a specified interval in event machine
|
50
|
+
# @param [Integer] seconds int interval which to wait before invoking specified block
|
51
|
+
# @param [Callable] bl callback to be invoked by eventmachine
|
52
|
+
def add_timer(seconds, &bl)
|
53
|
+
EventMachine.add_timer(seconds, &bl)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Schedule a block to be run periodically in event machine
|
57
|
+
# @param [Integer] seconds int interval which to invoke specified block
|
58
|
+
# @param [Callable] bl callback to be invoked by eventmachine
|
59
|
+
def add_periodic_timer(seconds, &bl)
|
60
|
+
EventMachine.add_periodic_timer(seconds, &bl)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Return boolean indicating if event machine reactor is running
|
64
|
+
def running?
|
65
|
+
@em_lock.synchronize{
|
66
|
+
EventMachine.reactor_running?
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
# Block until reactor thread is terminated
|
71
|
+
def join
|
72
|
+
th = nil
|
73
|
+
@em_lock.synchronize{
|
74
|
+
th = @reactor_thread
|
75
|
+
}
|
76
|
+
th.join unless th.nil?
|
77
|
+
end
|
78
|
+
|
79
|
+
# Terminate the event machine reactor under all conditions
|
80
|
+
def halt
|
81
|
+
@em_lock.synchronize{
|
82
|
+
EventMachine.stop_event_loop
|
83
|
+
}
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
# Provides an interface which to access a shared EMManager
|
89
|
+
#
|
90
|
+
# EMManager operations may be invoked on this class after
|
91
|
+
# the 'init' method is called
|
92
|
+
#
|
93
|
+
# EMAdapter.init
|
94
|
+
# EMAdapter.start
|
95
|
+
class EMAdapter
|
96
|
+
# Initialize EM subsystem
|
97
|
+
def self.init
|
98
|
+
if @em_manager.nil?
|
99
|
+
@em_manager = EMManager.new
|
100
|
+
end
|
101
|
+
|
102
|
+
@em_manager.start
|
103
|
+
end
|
104
|
+
|
105
|
+
# Delegates all methods invoked on calls to EMManager
|
106
|
+
def self.method_missing(method_id, *args, &bl)
|
107
|
+
@em_manager.send method_id, *args, &bl
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
data/lib/rjr/inspect.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# JSON-RPC method definitions providing access to inspect the internal
|
2
|
+
# node state.
|
3
|
+
#
|
4
|
+
# Note this isn't included in the top level rjr module by default,
|
5
|
+
# manually include this module to incorporate these additional rjr method
|
6
|
+
# definitions into your node
|
7
|
+
#
|
8
|
+
# Copyright (C) 2013 Mohammed Morsi <mo@morsi.org>
|
9
|
+
# Licensed under the Apache License, Version 2.0
|
10
|
+
|
11
|
+
require 'rjr/util'
|
12
|
+
include RJR::Definitions
|
13
|
+
|
14
|
+
# Helper method to process user params / select stats
|
15
|
+
def select_stats(*filter)
|
16
|
+
lf = []
|
17
|
+
while q = filter.shift
|
18
|
+
lf <<
|
19
|
+
case q
|
20
|
+
when 'on_node' then
|
21
|
+
n = filter.shift
|
22
|
+
lambda { |ds| ds.request.rjr_node_type.to_s == n}
|
23
|
+
|
24
|
+
when "for_method" then
|
25
|
+
m = filter.shift
|
26
|
+
lambda { |ds| ds.request.rjr_method == m}
|
27
|
+
|
28
|
+
when 'successful' then
|
29
|
+
lambda { |ds| ds.result.success }
|
30
|
+
|
31
|
+
when 'failed' then
|
32
|
+
lambda { |ds| ds.result.failed }
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
RJR::DispatcherStat.stats.select { |ds| lf.all? { |lf| lf.call(ds) } }
|
38
|
+
end
|
39
|
+
|
40
|
+
rjr_method \
|
41
|
+
"rjr::dispatches" =>
|
42
|
+
# Retrieve all the dispatches this node served matching the specified criteri
|
43
|
+
lambda { |*filter| select_stats(*filter) },
|
44
|
+
|
45
|
+
"rjr::num_dispatches" =>
|
46
|
+
# Retrieve the number of dispatches this node served matching the specified criteria
|
47
|
+
lambda { |*filter| select_stats(*filter).size },
|
48
|
+
|
49
|
+
"rjr::status" =>
|
50
|
+
# Retrieve the overall status of this node
|
51
|
+
lambda {
|
52
|
+
{
|
53
|
+
# event machine
|
54
|
+
:event_machine => { :running => EMAdapter.running?,
|
55
|
+
:has_jobs => EMAdapter.has_jobs?,
|
56
|
+
:thread_status => EMAdapter.reactor_thread.status,
|
57
|
+
:jobs => EMAdapter.em_jobs },
|
58
|
+
|
59
|
+
# thread pool
|
60
|
+
:thread_pool => { :running => ThreadPool2Manager.thread_pool.running?,
|
61
|
+
:inspect => ThreadPool2Manager.thread_pool.inspect },
|
62
|
+
}
|
63
|
+
}
|
64
|
+
|
65
|
+
#:log =>
|
66
|
+
# lambda {},
|
data/lib/rjr/local_node.rb
CHANGED
data/lib/rjr/message.rb
CHANGED
@@ -82,8 +82,9 @@ class RequestMessage
|
|
82
82
|
# @return [true,false] indicating if message is request message
|
83
83
|
def self.is_request_message?(message)
|
84
84
|
begin
|
85
|
-
#
|
86
|
-
JSON.parse(message)
|
85
|
+
# FIXME log error
|
86
|
+
parsed = JSON.parse(message)
|
87
|
+
parsed.has_key?('method') && parsed.has_key?('id')
|
87
88
|
rescue Exception => e
|
88
89
|
false
|
89
90
|
end
|
@@ -174,7 +175,8 @@ class ResponseMessage
|
|
174
175
|
json = JSON.parse(message)
|
175
176
|
json.has_key?('result') || json.has_key?('error')
|
176
177
|
rescue Exception => e
|
177
|
-
#
|
178
|
+
# FIXME log error
|
179
|
+
#puts e.to_s
|
178
180
|
false
|
179
181
|
end
|
180
182
|
end
|
@@ -200,4 +202,122 @@ class ResponseMessage
|
|
200
202
|
end
|
201
203
|
end
|
202
204
|
|
205
|
+
# Message sent to a jsonrpc node to invoke a rpc method but
|
206
|
+
# indicate the result should _not_ be returned
|
207
|
+
class NotificationMessage
|
208
|
+
# Message string received from the source
|
209
|
+
attr_accessor :json_message
|
210
|
+
|
211
|
+
# Method source is invoking on the destination
|
212
|
+
attr_accessor :jr_method
|
213
|
+
|
214
|
+
# Arguments source is passing to destination method
|
215
|
+
attr_accessor :jr_args
|
216
|
+
|
217
|
+
# Optional headers to add to json outside of standard json-rpc request
|
218
|
+
attr_accessor :headers
|
219
|
+
|
220
|
+
# RJR Notification Message initializer
|
221
|
+
#
|
222
|
+
# This should be invoked with one of two argument sets. If creating a new message
|
223
|
+
# to send to the server, specify :method, :args, and :headers to include in the message
|
224
|
+
# If handling an new request message sent from the client, simply specify :message and
|
225
|
+
# optionally any additional headers (they will be merged with the headers contained in
|
226
|
+
# the message)
|
227
|
+
#
|
228
|
+
# No message id will be generated in accordance w/ the jsonrpc standard
|
229
|
+
#
|
230
|
+
# @param [Hash] args options to set on request
|
231
|
+
# @option args [String] :message json string received from sender
|
232
|
+
# @option args [Hash] :headers optional headers to set in request and subsequent messages
|
233
|
+
# @option args [String] :method method to invoke on server
|
234
|
+
# @option args [Array<Object>] :args to pass to server method, all must be convertable to/from json
|
235
|
+
def initialize(args = {})
|
236
|
+
if args.has_key?(:message)
|
237
|
+
begin
|
238
|
+
notification = JSON.parse(args[:message])
|
239
|
+
@json_message = args[:message]
|
240
|
+
@jr_method = notification['method']
|
241
|
+
@jr_args = notification['params']
|
242
|
+
@headers = args.has_key?(:headers) ? {}.merge!(args[:headers]) : {}
|
243
|
+
|
244
|
+
notification.keys.select { |k|
|
245
|
+
!['jsonrpc', 'method', 'params'].include?(k)
|
246
|
+
}.each { |k| @headers[k] = notification[k] }
|
247
|
+
|
248
|
+
rescue Exception => e
|
249
|
+
#puts "Exception Parsing Notification #{e}"
|
250
|
+
raise e
|
251
|
+
end
|
252
|
+
|
253
|
+
elsif args.has_key?(:method)
|
254
|
+
@jr_method = args[:method]
|
255
|
+
@jr_args = args[:args]
|
256
|
+
@headers = args[:headers]
|
257
|
+
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
# Class helper to determine if the specified string is a valid json-rpc
|
262
|
+
# notification
|
263
|
+
#
|
264
|
+
# @param [String] message string message to check
|
265
|
+
# @return [true,false] indicating if message is a notification message
|
266
|
+
def self.is_notification_message?(message)
|
267
|
+
begin
|
268
|
+
# FIXME log error
|
269
|
+
parsed = JSON.parse(message)
|
270
|
+
parsed.has_key?('method') && !parsed.has_key?('id')
|
271
|
+
rescue Exception => e
|
272
|
+
false
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# Convert notification message to string json format
|
277
|
+
def to_s
|
278
|
+
notification = { 'jsonrpc' => '2.0',
|
279
|
+
'method' => @jr_method,
|
280
|
+
'params' => @jr_args }
|
281
|
+
notification.merge!(@headers) unless @headers.nil?
|
282
|
+
notification.to_json.to_s
|
283
|
+
end
|
284
|
+
|
285
|
+
end
|
286
|
+
|
287
|
+
# Helper utilities for messages
|
288
|
+
class MessageUtil
|
289
|
+
# Retrieve and return a single json message from a data string.
|
290
|
+
#
|
291
|
+
# Returns the message and remaining portion of the data string,
|
292
|
+
# if message is found, else nil
|
293
|
+
#
|
294
|
+
# XXX really don't like having to do this, but a quick solution
|
295
|
+
# to the issue of multiple messages appearing in one tcp data packet.
|
296
|
+
#
|
297
|
+
# TODO efficiency can probably be optimized
|
298
|
+
# in the case closing '}' hasn't arrived yet
|
299
|
+
def self.retrieve_json(data)
|
300
|
+
return nil if data.nil? || data.empty?
|
301
|
+
start = 0
|
302
|
+
start += 1 until start == data.length || data[start] == '{'
|
303
|
+
on = mi = 0
|
304
|
+
start.upto(data.length - 1).each { |i|
|
305
|
+
if data[i] == '{'
|
306
|
+
on += 1
|
307
|
+
elsif data[i] == '}'
|
308
|
+
on -= 1
|
309
|
+
end
|
310
|
+
|
311
|
+
if on == 0
|
312
|
+
mi = i
|
313
|
+
break
|
314
|
+
end
|
315
|
+
}
|
316
|
+
|
317
|
+
return nil if mi == 0
|
318
|
+
return data[start..mi], data[(mi+1)..-1]
|
319
|
+
end
|
320
|
+
|
321
|
+
end
|
322
|
+
|
203
323
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# RJR Missing Node Endpoint
|
2
|
+
#
|
3
|
+
# Provides a entity able to be associated with a rjr endpoint
|
4
|
+
# if the corresponding node cannot be loaded for whatever reason
|
5
|
+
#
|
6
|
+
# Copyright (C) 2012 Mohammed Morsi <mo@morsi.org>
|
7
|
+
# Licensed under the Apache License, Version 2.0
|
8
|
+
|
9
|
+
require 'rjr/node'
|
10
|
+
|
11
|
+
module RJR
|
12
|
+
class MissingNode < RJR::Node
|
13
|
+
def method_missing(method_id, *args, &bl)
|
14
|
+
raise "rjr node #{node_id} is missing a dependency - cannot invoke #{method_id}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/rjr/multi_node.rb
CHANGED
@@ -34,6 +34,9 @@ module RJR
|
|
34
34
|
# # invoke requests as you normally would via any protocol
|
35
35
|
#
|
36
36
|
class MultiNode < RJR::Node
|
37
|
+
# Return the nodes
|
38
|
+
attr_reader :nodes
|
39
|
+
|
37
40
|
# MultiNode initializer
|
38
41
|
# @param [Hash] args the options to create the tcp node with
|
39
42
|
# @option args [Array<RJR::Node>] :nodes array of nodes to use to listen to new requests on
|