rjr 0.12.2 → 0.15.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/README.md +49 -36
  2. data/Rakefile +2 -0
  3. data/bin/rjr-client +11 -9
  4. data/bin/rjr-server +12 -10
  5. data/examples/amqp.rb +29 -0
  6. data/examples/client.rb +32 -0
  7. data/examples/complete.rb +36 -0
  8. data/examples/local.rb +29 -0
  9. data/examples/server.rb +26 -0
  10. data/examples/tcp.rb +29 -0
  11. data/examples/web.rb +22 -0
  12. data/examples/ws.rb +29 -0
  13. data/lib/rjr/common.rb +7 -12
  14. data/lib/rjr/dispatcher.rb +171 -239
  15. data/lib/rjr/em_adapter.rb +33 -66
  16. data/lib/rjr/message.rb +43 -12
  17. data/lib/rjr/node.rb +197 -103
  18. data/lib/rjr/nodes/amqp.rb +216 -0
  19. data/lib/rjr/nodes/easy.rb +159 -0
  20. data/lib/rjr/nodes/local.rb +118 -0
  21. data/lib/rjr/{missing_node.rb → nodes/missing.rb} +4 -2
  22. data/lib/rjr/nodes/multi.rb +79 -0
  23. data/lib/rjr/nodes/tcp.rb +211 -0
  24. data/lib/rjr/nodes/web.rb +197 -0
  25. data/lib/rjr/nodes/ws.rb +187 -0
  26. data/lib/rjr/stats.rb +70 -0
  27. data/lib/rjr/thread_pool.rb +178 -123
  28. data/site/index.html +45 -0
  29. data/site/jquery-latest.js +9404 -0
  30. data/site/jrw.js +297 -0
  31. data/site/json.js +199 -0
  32. data/specs/dispatcher_spec.rb +244 -198
  33. data/specs/em_adapter_spec.rb +52 -80
  34. data/specs/message_spec.rb +223 -197
  35. data/specs/node_spec.rb +67 -163
  36. data/specs/nodes/amqp_spec.rb +82 -0
  37. data/specs/nodes/easy_spec.rb +13 -0
  38. data/specs/nodes/local_spec.rb +72 -0
  39. data/specs/nodes/multi_spec.rb +65 -0
  40. data/specs/nodes/tcp_spec.rb +75 -0
  41. data/specs/nodes/web_spec.rb +77 -0
  42. data/specs/nodes/ws_spec.rb +78 -0
  43. data/specs/stats_spec.rb +59 -0
  44. data/specs/thread_pool_spec.rb +44 -35
  45. metadata +40 -30
  46. data/lib/rjr/amqp_node.rb +0 -330
  47. data/lib/rjr/inspect.rb +0 -65
  48. data/lib/rjr/local_node.rb +0 -150
  49. data/lib/rjr/multi_node.rb +0 -65
  50. data/lib/rjr/tcp_node.rb +0 -323
  51. data/lib/rjr/thread_pool2.rb +0 -272
  52. data/lib/rjr/util.rb +0 -104
  53. data/lib/rjr/web_node.rb +0 -266
  54. data/lib/rjr/ws_node.rb +0 -289
  55. data/lib/rjr.rb +0 -16
  56. data/specs/amqp_node_spec.rb +0 -31
  57. data/specs/inspect_spec.rb +0 -60
  58. data/specs/local_node_spec.rb +0 -43
  59. data/specs/multi_node_spec.rb +0 -45
  60. data/specs/tcp_node_spec.rb +0 -33
  61. data/specs/util_spec.rb +0 -46
  62. data/specs/web_node_spec.rb +0 -32
  63. data/specs/ws_node_spec.rb +0 -32
  64. /data/lib/rjr/{tcp_node2.rb → nodes/tcp2.rb} +0 -0
  65. /data/lib/rjr/{udp_node.rb → nodes/udp.rb} +0 -0
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rjr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.2
4
+ version: 0.15.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,24 +9,24 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-04-18 00:00:00.000000000 Z
12
+ date: 2013-05-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
- - - ~>
19
+ - - ! '>='
20
20
  - !ruby/object:Gem::Version
21
- version: 1.3.0
21
+ version: 2.0.0
22
22
  type: :development
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  none: false
26
26
  requirements:
27
- - - ~>
27
+ - - ! '>='
28
28
  - !ruby/object:Gem::Version
29
- version: 1.3.0
29
+ version: 2.0.0
30
30
  - !ruby/object:Gem::Dependency
31
31
  name: eventmachine
32
32
  requirement: !ruby/object:Gem::Requirement
@@ -68,40 +68,50 @@ executables:
68
68
  extensions: []
69
69
  extra_rdoc_files: []
70
70
  files:
71
+ - examples/web.rb
72
+ - examples/tcp.rb
73
+ - examples/server.rb
74
+ - examples/ws.rb
75
+ - examples/local.rb
76
+ - examples/complete.rb
77
+ - examples/amqp.rb
78
+ - examples/client.rb
71
79
  - lib/rjr/semaphore.rb
72
- - lib/rjr/udp_node.rb
73
80
  - lib/rjr/em_adapter.rb
74
81
  - lib/rjr/errors.rb
75
- - lib/rjr/missing_node.rb
76
- - lib/rjr/inspect.rb
77
- - lib/rjr/ws_node.rb
78
- - lib/rjr/tcp_node.rb
79
- - lib/rjr/web_node.rb
80
82
  - lib/rjr/node.rb
81
- - lib/rjr/thread_pool2.rb
82
83
  - lib/rjr/common.rb
83
84
  - lib/rjr/thread_pool.rb
84
85
  - lib/rjr/message.rb
85
- - lib/rjr/tcp_node2.rb
86
- - lib/rjr/multi_node.rb
87
- - lib/rjr/local_node.rb
86
+ - lib/rjr/stats.rb
87
+ - lib/rjr/nodes/web.rb
88
+ - lib/rjr/nodes/udp.rb
89
+ - lib/rjr/nodes/tcp.rb
90
+ - lib/rjr/nodes/ws.rb
91
+ - lib/rjr/nodes/tcp2.rb
92
+ - lib/rjr/nodes/local.rb
93
+ - lib/rjr/nodes/amqp.rb
94
+ - lib/rjr/nodes/easy.rb
95
+ - lib/rjr/nodes/missing.rb
96
+ - lib/rjr/nodes/multi.rb
88
97
  - lib/rjr/dispatcher.rb
89
- - lib/rjr/util.rb
90
- - lib/rjr/amqp_node.rb
91
- - lib/rjr.rb
92
98
  - specs/dispatcher_spec.rb
93
99
  - specs/node_spec.rb
94
- - specs/amqp_node_spec.rb
95
- - specs/tcp_node_spec.rb
96
- - specs/multi_node_spec.rb
97
- - specs/util_spec.rb
98
- - specs/local_node_spec.rb
99
- - specs/web_node_spec.rb
100
- - specs/ws_node_spec.rb
101
- - specs/inspect_spec.rb
100
+ - specs/stats_spec.rb
101
+ - specs/nodes/multi_spec.rb
102
+ - specs/nodes/tcp_spec.rb
103
+ - specs/nodes/web_spec.rb
104
+ - specs/nodes/local_spec.rb
105
+ - specs/nodes/amqp_spec.rb
106
+ - specs/nodes/ws_spec.rb
107
+ - specs/nodes/easy_spec.rb
102
108
  - specs/thread_pool_spec.rb
103
109
  - specs/message_spec.rb
104
110
  - specs/em_adapter_spec.rb
111
+ - site/index.html
112
+ - site/jquery-latest.js
113
+ - site/json.js
114
+ - site/jrw.js
105
115
  - LICENSE
106
116
  - Rakefile
107
117
  - README.md
@@ -127,9 +137,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
127
137
  - !ruby/object:Gem::Version
128
138
  version: 1.3.3
129
139
  requirements:
130
- - amqp gem is needed to use the amqp node
131
- - eventmachine_httpserver and em-http-request gems are needed to use the web node
132
- - em-websocket and em-websocket-client gems are needed to use the web socket node
140
+ - The amqp gem and a running rabbitmq server is needed to use the amqp node
141
+ - The eventmachine_httpserver and em-http-request gems are needed to use the web node
142
+ - The em-websocket and em-websocket-client gems are needed to use the web socket node
133
143
  rubyforge_project:
134
144
  rubygems_version: 1.8.24
135
145
  signing_key:
data/lib/rjr/amqp_node.rb DELETED
@@ -1,330 +0,0 @@
1
- # RJR AMQP Endpoint
2
- #
3
- # Implements the RJR::Node interface to satisty JSON-RPC requests over the AMQP protocol
4
- #
5
- # Copyright (C) 2012 Mohammed Morsi <mo@morsi.org>
6
- # Licensed under the Apache License, Version 2.0
7
-
8
- skip_module = false
9
- begin
10
- require 'amqp'
11
- rescue LoadError
12
- skip_module = true
13
- end
14
-
15
- if skip_module
16
- # TODO output: "amqp gem could not be loaded, skipping amqp node definition"
17
- require 'rjr/missing_node'
18
- RJR::AMQPNode = RJR::MissingNode
19
-
20
- else
21
- require 'rjr/node'
22
- require 'rjr/message'
23
- require 'rjr/dispatcher'
24
- require 'rjr/errors'
25
- require 'rjr/thread_pool2'
26
-
27
- module RJR
28
-
29
- # AMQP node callback interface, used to invoke json-rpc methods on a
30
- # remote node which previously invoked a method on the local one.
31
- #
32
- # After a node sends a json-rpc request to another, the either node may send
33
- # additional requests to each other via amqp through this callback interface
34
- # until the queues are closed
35
- class AMQPNodeCallback
36
-
37
- # AMQPNodeCallback initializer
38
- # @param [Hash] args the options to create the amqp node callback with
39
- # @option args [AMQPNode] :node amqp node used to send/receive messages
40
- # @option args [String] :destination name of the queue to invoke callbacks on
41
- def initialize(args = {})
42
- @node = args[:node]
43
- @destination = args[:destination]
44
- end
45
-
46
- # Implementation of {RJR::NodeCallback#invoke}
47
- def invoke(callback_method, *data)
48
- msg = NotificationMessage.new :method => callback_method, :args => data, :headers => @message_headers
49
- @node.publish(msg.to_s, :routing_key => @destination, :mandatory => true) { }
50
- end
51
- end
52
-
53
- # AMQP node definition, implements the {RJR::Node} interface to
54
- # listen for and invoke json-rpc requests over
55
- # {http://en.wikipedia.org/wiki/Advanced_Message_Queuing_Protocol AMQP}.
56
- #
57
- # Clients should specify the amqp broker to connect to when initializing
58
- # a node and specify the remote queue when invoking requests.
59
- #
60
- # @example Listening for json-rpc requests over amqp
61
- # # register rjr dispatchers (see RJR::Dispatcher)
62
- # RJR::Dispatcher.add_handler('hello') { |name|
63
- # "Hello #{name}!"
64
- # }
65
- #
66
- # # initialize node, listen, and block
67
- # server = RJR::AMQPNode.new :node_id => 'server', :broker => 'localhost'
68
- # server.listen
69
- # server.join
70
- #
71
- # @example Invoking json-rpc requests over amqp
72
- # client = RJR::AMQPNode.new :node_id => 'client', :broker => 'localhost'
73
- # puts client.invoke_request('server-queue', 'hello', 'mo') # the queue name is set to "#{node_id}-queue"
74
- class AMQPNode < RJR::Node
75
- RJR_NODE_TYPE = :amqp
76
-
77
- private
78
-
79
- # Internal helper, handle message pulled off queue
80
- def handle_message(metadata, msg)
81
- if RequestMessage.is_request_message?(msg)
82
- reply_to = metadata.reply_to
83
- ThreadPool2Manager << ThreadPool2Job.new { handle_request(reply_to, msg, false) }
84
-
85
- elsif NotificationMessage.is_notification_message?(msg)
86
- reply_to = metadata.reply_to
87
- ThreadPool2Manager << ThreadPool2Job.new { handle_request(reply_to, msg, true) }
88
-
89
- elsif ResponseMessage.is_response_message?(msg)
90
- handle_response(msg)
91
-
92
- end
93
- end
94
-
95
- # Internal helper, handle request message pulled off queue
96
- def handle_request(reply_to, message, notification=false)
97
- msg = notification ? NotificationMessage.new(:message => message, :headers => @message_headers) :
98
- RequestMessage.new(:message => message, :headers => @message_headers)
99
- headers = @message_headers.merge(msg.headers) # append request message headers
100
- result = Dispatcher.dispatch_request(msg.jr_method,
101
- :method_args => msg.jr_args,
102
- :headers => headers,
103
- :client_ip => nil, # since client doesn't directly connect to server, we can't leverage
104
- :client_port => nil, # client ip / port for requests received via the amqp node type
105
- :rjr_node => self,
106
- :rjr_node_id => @node_id,
107
- :rjr_node_type => RJR_NODE_TYPE,
108
- :rjr_callback =>
109
- AMQPNodeCallback.new(:node => self,
110
- :exchange => @exchange,
111
- :destination => reply_to,
112
- :headers => headers))
113
- unless notification
114
- response = ResponseMessage.new(:id => msg.msg_id, :result => result, :headers => headers)
115
- publish(response.to_s, :routing_key => reply_to) { }
116
- end
117
- end
118
-
119
- # Internal helper, handle response message pulled off queue
120
- def handle_response(message)
121
- msg = ResponseMessage.new(:message => message, :headers => @message_headers)
122
- res = err = nil
123
- begin
124
- res = Dispatcher.handle_response(msg.result)
125
- rescue Exception => e
126
- err = e.to_s
127
- end
128
-
129
- @response_lock.synchronize{
130
- result = [msg.msg_id, res]
131
- result << err if !err.nil?
132
- @responses << result
133
- @response_cv.signal
134
- }
135
- end
136
-
137
- # Initialize the amqp subsystem
138
- def init_node(&on_init)
139
- if !@conn.nil? && @conn.connected?
140
- on_init.call
141
- return
142
- end
143
-
144
- super
145
- @conn = AMQP.connect(:host => @broker) do |*args|
146
- on_init.call
147
- end
148
- @conn.on_tcp_connection_failure { puts "OTCF #{@node_id}" }
149
-
150
- # TODO move the rest into connect callback ?
151
-
152
- ### connect to qpid broker
153
- @channel = AMQP::Channel.new(@conn)
154
-
155
- # qpid constructs that will be created for node
156
- @queue_name = "#{@node_id.to_s}-queue"
157
- @queue = @channel.queue(@queue_name, :auto_delete => true)
158
- @exchange = @channel.default_exchange
159
-
160
- @listening = false
161
- #@disconnected = false
162
-
163
- @exchange.on_return do |basic_return, metadata, payload|
164
- puts "#{payload} was returned! reply_code = #{basic_return.reply_code}, reply_text = #{basic_return.reply_text}"
165
- #@disconnected = true # FIXME member will be set on wrong class
166
- connection_event(:error)
167
- connection_event(:closed)
168
- end
169
- end
170
-
171
- # Internal helper, block until response matching message id is received
172
- def wait_for_result(message)
173
- res = nil
174
- while res.nil?
175
- @response_lock.synchronize{
176
- # FIXME throw err if more than 1 match found
177
- res = @responses.select { |response| message.msg_id == response.first }.first
178
- if !res.nil?
179
- @responses.delete(res)
180
-
181
- else
182
- @response_cv.signal
183
- @response_cv.wait @response_lock
184
- end
185
- }
186
- end
187
- return res
188
- end
189
-
190
- # Internal helper, run connection event handlers for specified event
191
- # TODO these are only run when we fail to send message to queue, need to detect when that queue is shutdown & other events
192
- def connection_event(event)
193
- if @connection_event_handlers.keys.include?(event)
194
- @connection_event_handlers[event].each { |h|
195
- h.call self
196
- }
197
- end
198
- end
199
-
200
- # Internal helper, subscribe to messages using the amqp queue
201
- def subscribe(*args, &bl)
202
- return if @listening
203
- @amqp_lock.synchronize {
204
- @listening = true
205
- @queue.subscribe do |metadata, msg|
206
- bl.call metadata, msg
207
- end
208
- }
209
- nil
210
- end
211
-
212
-
213
- public
214
-
215
- # AMQPNode initializer
216
- # @param [Hash] args the options to create the amqp node with
217
- # @option args [String] :broker the amqp message broker which to connect to
218
- def initialize(args = {})
219
- super(args)
220
- @broker = args[:broker]
221
- @connection_event_handlers = {:closed => [], :error => []}
222
- @response_lock = Mutex.new
223
- @response_cv = ConditionVariable.new
224
- @responses = []
225
- @amqp_lock = Mutex.new
226
- end
227
-
228
- # Publish a message using the amqp exchange (*do* *not* *use*).
229
- #
230
- # XXX hack should be private, declared publically so as to be able to be used by {RJR::AMQPNodeCallback}
231
- def publish(*args, &on_publish)
232
- @amqp_lock.synchronize {
233
- #raise RJR::Errors::ConnectionError.new("client unreachable") if @disconnected
234
- @exchange.publish *args do |*cargs|
235
- on_publish.call
236
- end
237
- }
238
- nil
239
- end
240
-
241
- # Register connection event handler
242
- # @param [:error, :close] event the event to register the handler for
243
- # @param [Callable] handler block param to be added to array of handlers that are called when event occurs
244
- # @yield [AMQPNode] self is passed to each registered handler when event occurs
245
- def on(event, &handler)
246
- if @connection_event_handlers.keys.include?(event)
247
- @connection_event_handlers[event] << handler
248
- end
249
- end
250
-
251
- # Instruct Node to start listening for and dispatching rpc requests.
252
- #
253
- # Implementation of {RJR::Node#listen}
254
- def listen
255
- em_run do
256
- init_node {
257
- # start receiving messages
258
- subscribe { |metadata, msg|
259
- handle_message(metadata, msg)
260
- }
261
- }
262
- end
263
- end
264
-
265
- # Instructs node to send rpc request, and wait for and return response.
266
- #
267
- # Do not invoke directly from em event loop or callback as will block the message
268
- # subscription used to receive responses
269
- #
270
- # @param [String] routing_key destination queue to send request to
271
- # @param [String] rpc_method json-rpc method to invoke on destination
272
- # @param [Array] args array of arguments to convert to json and invoke remote method wtih
273
- # @return [Object] the json result retrieved from destination converted to a ruby object
274
- # @raise [Exception] if the destination raises an exception, it will be converted to json and re-raised here
275
- def invoke_request(routing_key, rpc_method, *args)
276
- message = RequestMessage.new :method => rpc_method,
277
- :args => args,
278
- :headers => @message_headers
279
- em_run do
280
- init_node {
281
- # begin listening for result
282
- subscribe { |metadata, msg|
283
- handle_message(metadata, msg)
284
- }
285
-
286
- publish(message.to_s, :routing_key => routing_key, :reply_to => @queue_name) { }
287
- }
288
- end
289
-
290
- # TODO optional timeout for response
291
- result = wait_for_result(message)
292
-
293
- if result.size > 2
294
- raise Exception, result[2]
295
- end
296
- return result[1]
297
- end
298
-
299
- # FIXME add method to instruct node to send rpc request, and immediately
300
- # return / ignoring response & also add method to collect response
301
- # at a later time
302
-
303
- # Instructs node to send rpc notification (immadiately returns / no response is generated)
304
- #
305
- # @param [String] routing_key destination queue to send request to
306
- # @param [String] rpc_method json-rpc method to invoke on destination
307
- # @param [Array] args array of arguments to convert to json and invoke remote method wtih
308
- def send_notification(routing_key, rpc_method, *args)
309
- # will block until message is published
310
- published_l = Mutex.new
311
- published_c = ConditionVariable.new
312
-
313
- invoked = false
314
- message = NotificationMessage.new :method => rpc_method,
315
- :args => args,
316
- :headers => @message_headers
317
- em_run do
318
- init_node {
319
- publish(message.to_s, :routing_key => routing_key, :reply_to => @queue_name){
320
- published_l.synchronize { invoked = true ; published_c.signal }
321
- }
322
- }
323
- end
324
- published_l.synchronize { published_c.wait published_l unless invoked }
325
- nil
326
- end
327
-
328
- end
329
- end
330
- end
data/lib/rjr/inspect.rb DELETED
@@ -1,65 +0,0 @@
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
- :thread_status => EMAdapter.reactor_thread.status,
56
- :connections => EventMachine.connection_count },
57
-
58
- # thread pool
59
- :thread_pool => { :running => ThreadPool2Manager.thread_pool.running?,
60
- :inspect => ThreadPool2Manager.thread_pool.inspect },
61
- }
62
- }
63
-
64
- #:log =>
65
- # lambda {},
@@ -1,150 +0,0 @@
1
- # RJR Local Endpoint
2
- #
3
- # Implements the RJR::Node interface to satisty JSON-RPC requests via local method calls
4
- #
5
- # Copyright (C) 2012 Mohammed Morsi <mo@morsi.org>
6
- # Licensed under the Apache License, Version 2.0
7
-
8
- require 'rjr/node'
9
- require 'rjr/message'
10
- require 'rjr/dispatcher'
11
- require 'rjr/errors'
12
-
13
- module RJR
14
-
15
- # Local node callback interface, used to invoke local json-rpc method handlers
16
- class LocalNodeCallback
17
- # LocalNodeCallback initializer
18
- # @param [Hash] args the options to create the local node callback with
19
- # @option args [LocalNode] :node local node used to send/receive messages
20
- def initialize(args = {})
21
- @node = args[:node]
22
- end
23
-
24
- # Implementation of {RJR::NodeCallback#invoke}
25
- def invoke(callback_method, *data)
26
- # TODO any exceptions from handler will propagate here, surround w/ begin/rescue block
27
- # TODO support local_node 'disconnections' via RJR::ConnectionError & triggering mechanism
28
- ThreadPool2Manager <<
29
- ThreadPool2Job.new(callback_method, data) { |m|
30
- @node.local_dispatch(callback_method, *data)
31
- }
32
- end
33
- end
34
-
35
- # Local node definition, implements the {RJR::Node} interface to
36
- # listen for and invoke json-rpc requests via local handlers
37
- #
38
- # This is useful for situations in which you would like to invoke registered
39
- # json-rpc handlers locally, enforcing the same constraints as
40
- # you would on a json-rpc request coming in remotely.
41
- #
42
- # @example Listening for and dispatching json-rpc requests locally
43
- # RJR::Dispatcher.add_handler('hello') { |name|
44
- # @rjr_node_type == :local ? "Hello superuser #{name}" : "Hello #{name}!"
45
- # }
46
- #
47
- # # initialize node and invoke request
48
- # node = RJR::LocalNode.new :node_id => 'node'
49
- # node.invoke_request('hello', 'mo')
50
- #
51
- class LocalNode < RJR::Node
52
- RJR_NODE_TYPE = :local
53
-
54
- # allows clients to override the node type for the local node
55
- attr_accessor :node_type
56
-
57
- # Helper method to locally dispatch method/args
58
- #
59
- # TODO would like to make private but needed in LocalNodeCallback
60
- def local_dispatch(rpc_method, *args)
61
- # create request from args
62
- 0.upto(args.size).each { |i| args[i] = args[i].to_s if args[i].is_a?(Symbol) }
63
- message = RequestMessage.new :method => rpc_method,
64
- :args => args,
65
- :headers => @message_headers
66
-
67
- # here we serialize / unserialze messages to/from json to
68
- # ensure local node complies to same json-rpc restrictions as other nodes
69
- message = RequestMessage.new :message => message.to_s,
70
- :headers => @message_headers
71
-
72
- result = Dispatcher.dispatch_request(message.jr_method,
73
- :method_args => message.jr_args,
74
- :headers => @message_headers,
75
- :rjr_node => self,
76
- :rjr_node_id => @node_id,
77
- :rjr_node_type => @node_type,
78
- :rjr_callback =>
79
- LocalNodeCallback.new(:node => self,
80
- :headers => @message_headers))
81
-
82
- # create response message from result
83
- response = ResponseMessage.new(:id => message.msg_id,
84
- :result => result,
85
- :headers => @message_headers)
86
-
87
- # same comment on serialization/unserialization as above
88
- response = ResponseMessage.new(:message => response.to_s,
89
- :headers => @message_headers)
90
-
91
- response
92
- end
93
-
94
- # LocalNode initializer
95
- # @param [Hash] args the options to create the local node with
96
- def initialize(args = {})
97
- super(args)
98
- @node_type = RJR_NODE_TYPE
99
- end
100
-
101
- # register connection event handler,
102
- # *note* Until we support manual disconnections of the local node, we don't have to do anything here
103
- #
104
- # @param [:error, :close] event the event to register the handler for
105
- # @param [Callable] handler block param to be added to array of handlers that are called when event occurs
106
- # @yield [LocalNode] self is passed to each registered handler when event occurs
107
- def on(event, &handler)
108
- # TODO raise error (for the time being)?
109
- end
110
-
111
- # Instruct Node to start listening for and dispatching rpc requests
112
- #
113
- # Currently does nothing as method handlers can be invoked directly upon invoke_request
114
- def listen
115
- end
116
-
117
- # Instructs node to send rpc request, and wait for and return response
118
- #
119
- # If strictly confirming to other nodes, use event machine to launch
120
- # a thread pool job to dispatch request and block on result.
121
- # Optimized for performance reasons but recognize the semantics of using
122
- # the local node will be somewhat different.
123
- #
124
- # @param [String] rpc_method json-rpc method to invoke on destination
125
- # @param [Array] args array of arguments to convert to json and invoke remote method wtih
126
- # @return [Object] the json result retrieved from destination converted to a ruby object
127
- # @raise [Exception] if the destination raises an exception, it will be converted to json and re-raised here
128
- def invoke_request(rpc_method, *args)
129
- res = local_dispatch(rpc_method, *args)
130
- return Dispatcher.handle_response(res.result)
131
- end
132
-
133
- # Instructs node to send rpc notification (immediately returns / no response is generated)
134
- #
135
- # Same performance comment as invoke_request above
136
- #
137
- # @param [String] rpc_method json-rpc method to invoke on destination
138
- # @param [Array] args array of arguments to convert to json and invoke remote method wtih
139
- def send_notification(rpc_method, *args)
140
- # TODO run in thread & immediately return?
141
- begin
142
- local_dispatch(rpc_method, *args)
143
- rescue
144
- end
145
- nil
146
- end
147
-
148
-
149
- end
150
- end