rjr 0.12.2 → 0.15.1

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.
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