rjr 0.9.0 → 0.11.7
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.
- 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
|