rjr 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,25 +1,50 @@
1
1
  # RJR Request / Response Dispatcher
2
2
  #
3
+ # Representation of a json-rpc request, response and mechanisms which to
4
+ # register methods to handle requests and return responses
5
+ #
3
6
  # Copyright (C) 2012 Mohammed Morsi <mo@morsi.org>
4
7
  # Licensed under the Apache License, Version 2.0
5
8
 
6
- # establish client connection w/ specified args and invoke block w/
7
- # newly created client, returning it after block terminates
8
-
9
9
  require 'rjr/common'
10
10
 
11
11
  module RJR
12
12
 
13
+ # JSON-RPC request representation
13
14
  class Request
15
+ # name of the method which request is for
14
16
  attr_accessor :method
17
+
18
+ # array of arguments which to pass to the rpc method handler
15
19
  attr_accessor :method_args
20
+
21
+ # hash of keys/values corresponding to optional headers received as part of of the request
16
22
  attr_accessor :headers
23
+
24
+ # callback through which additional requests may be invoked
17
25
  attr_accessor :rjr_callback
26
+
27
+ # type of the rjr node which request was received on
18
28
  attr_accessor :rjr_node_type
29
+
30
+ # id of the rjr node which request was received on
19
31
  attr_accessor :rjr_node_id
20
32
 
33
+ # callable object registered to the specified method which to invoke request on with arguments
21
34
  attr_accessor :handler
22
35
 
36
+ # RJR request intializer
37
+ # @param [Hash] args options to set on request
38
+ # @option args [String] :method name of the method which request is for
39
+ # @option args [Array] :method_args array of arguments which to pass to the rpc method handler
40
+ # @option args [Hash] :headers hash of keys/values corresponding to optional headers received as part of of the request
41
+ # @option args [String] :client_ip ip address of client which invoked the request (if applicable)
42
+ # @option args [String] :client_port port of client which invoked the request (if applicable)
43
+ # @option args [RJR::Callback] :rjr_callback callback through which additional requests may be invoked
44
+ # @option args [RJR::Node] :rjr_node rjr node which request was received on
45
+ # @option args [String] :rjr_node_id id of the rjr node which request was received on
46
+ # @option args [Symbol] :rjr_node_type type of the rjr node which request was received on
47
+ # @option args [Callable] :handler callable object registered to the specified method which to invoke request on with arguments
23
48
  def initialize(args = {})
24
49
  @method = args[:method]
25
50
  @method_args = args[:method_args]
@@ -33,6 +58,8 @@ class Request
33
58
  @handler = args[:handler]
34
59
  end
35
60
 
61
+ # Actually invoke the request by calling the registered handler with the specified
62
+ # method parameters in the local scope
36
63
  def handle
37
64
  RJR::Logger.info "Dispatching '#{@method}' request with parameters (#{@method_args.join(',')}) on #{@rjr_node_type}-node(#{@rjr_node_id})"
38
65
  retval = instance_exec(*@method_args, &@handler)
@@ -41,14 +68,32 @@ class Request
41
68
  end
42
69
  end
43
70
 
71
+ # JSON-RPC result representation
44
72
  class Result
73
+ # boolean indicating if request was successfully invoked
45
74
  attr_accessor :success
75
+
76
+ # boolean indicating if request was not successfully invoked
46
77
  attr_accessor :failed
78
+
79
+ # return value of the json-rpc call if successful
47
80
  attr_accessor :result
81
+
82
+ # code corresponding to json-rpc error if problem occured during request invocation
48
83
  attr_accessor :error_code
84
+
85
+ # message corresponding to json-rpc error if problem occured during request invocation
49
86
  attr_accessor :error_msg
87
+
88
+ # class of error raised (if any) during request invocation (this is extra metadata beyond standard json-rpc)
50
89
  attr_accessor :error_class
51
90
 
91
+ # RJR result intializer
92
+ # @param [Hash] args options to set on result
93
+ # @option args [Object] :result result of json-rpc method handler if successfully returned
94
+ # @option args [Integer] :error_code code corresponding to json-rpc error if problem occured during request invocation
95
+ # @option args [String] :error_msg message corresponding to json-rpc error if problem occured during request invocation
96
+ # @option args [Class] :error_class class of error raised (if any) during request invocation (this is extra metadata beyond standard json-rpc)
52
97
  def initialize(args = {})
53
98
  @result = nil
54
99
  @error_code = nil
@@ -70,6 +115,8 @@ class Result
70
115
  end
71
116
  end
72
117
 
118
+ # Compare Result against other result, returning true if both correspond
119
+ # to equivalent json-rpc results else false
73
120
  def ==(other)
74
121
  @success == other.success &&
75
122
  @failed == other.failed &&
@@ -79,17 +126,20 @@ class Result
79
126
  @error_class == other.error_class
80
127
  end
81
128
 
129
+ # Convert Response to human consumable string
82
130
  def to_s
83
131
  "#{@success} #{@result} #{@error_code} #{@error_msg} #{@error_class}"
84
132
  end
85
133
 
86
134
  ######### Specific request types
87
135
 
136
+ # JSON-RPC -32600 / Invalid Request
88
137
  def self.invalid_request
89
138
  return Result.new(:error_code => -32600,
90
139
  :error_msg => ' Invalid Request')
91
140
  end
92
141
 
142
+ # JSON-RPC -32602 / Method not found
93
143
  def self.method_not_found(name)
94
144
  return Result.new(:error_code => -32602,
95
145
  :error_msg => "Method '#{name}' not found")
@@ -97,15 +147,33 @@ class Result
97
147
 
98
148
  end
99
149
 
150
+ # Association between json-rpc method name and registered handler to
151
+ # be invoked on new requests.
152
+ #
153
+ # When invoked, creates new {RJR::Request} object with specified request
154
+ # params and uses it to invoke handler in its context. Formats and returns
155
+ # return of operation
100
156
  class Handler
101
157
  attr_accessor :method_name
102
158
  attr_accessor :handler_proc
103
159
 
160
+ # RJR::Handler intializer
161
+ # @param [Hash] args options to set on handler
162
+ # @option args [String] :method name of json-rpc method to which handler is bound
163
+ # @option args [Callable] :handle callable object which to bind to method name
104
164
  def initialize(args = {})
105
165
  @method_name = args[:method]
106
166
  @handler_proc = args[:handler]
107
167
  end
108
168
 
169
+ # Handle new json-rpc request to registered method.
170
+ #
171
+ # Creates new {RJR::Request} with the local method name and handler and the
172
+ # arguments received as part of the request. Uses it to invoke handler and
173
+ # creates and returns new {RJR::Result} encapsulating the return value if
174
+ # successful or error code/message/class if not.
175
+ #
176
+ # If invalid method_name is specified returns a json-rpc 'Method not found'
109
177
  def handle(args = {})
110
178
  return Result.method_not_found(args[:missing_name]) if @method_name.nil?
111
179
 
@@ -126,13 +194,39 @@ class Handler
126
194
  end
127
195
  end
128
196
 
197
+ # Primary RJR JSON-RPC method dispatcher interface.
198
+ #
199
+ # Provides class methods which to register global handlers to json-rpc methods and
200
+ # to handle requests and responses.
129
201
  class Dispatcher
130
- # clear handlers
202
+ # Clear all registered json-rpc handlers
131
203
  def self.init_handlers
132
204
  @@handlers = {}
133
205
  end
134
206
 
135
- # register a handler to the specified method
207
+ # Register a handler for the specified method(s)
208
+ #
209
+ # *WARNING* Do not invoke 'return' in registered handlers as these are blocks and *not* lambdas
210
+ # (see {http://stackoverflow.com/questions/626/when-to-use-lambda-when-to-use-proc-new Ruby Lambdas vs Procs})
211
+ #
212
+ # If specifying a single method name pass in a string, else pass in an array of strings.
213
+ # The block argument will be used as the method handler and will be invoked when
214
+ # json-rpc requests are received corresponding to the method name(s)
215
+ # @param [String,Array<String>] method_names one or more string method names
216
+ # @param [Hash] args options to initialize handler with, current unused
217
+ # @param [Callable] handler block to invoke when json-rpc requests to method are received
218
+ #
219
+ # @example
220
+ # RJR::Dispatcher.add_handler("hello_world") {
221
+ # "hello world"
222
+ # }
223
+ #
224
+ # RJR::Dispatcher.add_handler(["echo", "ECHO"]) { |val|
225
+ # val
226
+ # }
227
+ #
228
+ # # TODO define yard macros to construct documentation for a rjr based api
229
+ # from calls to add_handler
136
230
  def self.add_handler(method_names, args = {}, &handler)
137
231
  method_names = Array(method_names) unless method_names.is_a?(Array)
138
232
  @@handlers ||= {}
@@ -142,7 +236,8 @@ class Dispatcher
142
236
  }
143
237
  end
144
238
 
145
- # Helper to handle request messages
239
+ # Helper used by RJR nodes to dispatch requests received via transports to
240
+ # registered handlers.
146
241
  def self.dispatch_request(method_name, args = {})
147
242
  @@handlers ||= {}
148
243
  handler = @@handlers[method_name]
@@ -155,7 +250,8 @@ class Dispatcher
155
250
  return handler.handle args
156
251
  end
157
252
 
158
- # Helper to handle response messages
253
+ # Helper used by RJR nodes to handle responses received from rjr requests
254
+ # (returns return-value of method handler or raises error)
159
255
  def self.handle_response(result)
160
256
  unless result.success
161
257
  #if result.error_class
data/lib/rjr/errors.rb CHANGED
@@ -3,12 +3,14 @@
3
3
  # Copyright (C) 2012 Mohammed Morsi <mo@morsi.org>
4
4
  # Licensed under the Apache License, Version 2.0
5
5
 
6
- # establish client connection w/ specified args and invoke block w/
7
- # newly created client, returning it after block terminates
8
-
9
6
  module RJR
7
+
8
+ # The RJR::Errors module provides a mechanism to dynamically create errors
9
+ # on demand as they are needed. At some point this will go away / be replaced
10
+ # with a more rigidly / fixed defined error heirarchy.
10
11
  module Errors
11
12
 
13
+ # Catches all Errors constants and define new RuntimeError subclass
12
14
  def self.const_missing(error_name) # :nodoc:
13
15
  if error_name.to_s =~ /Error\z/
14
16
  const_set(error_name, Class.new(RuntimeError))
@@ -1,23 +1,25 @@
1
1
  # RJR Local Endpoint
2
2
  #
3
+ # Implements the RJR::Node interface to satisty JSON-RPC requests via local method calls
4
+ #
3
5
  # Copyright (C) 2012 Mohammed Morsi <mo@morsi.org>
4
6
  # Licensed under the Apache License, Version 2.0
5
7
 
6
- # establish client connection w/ specified args and invoke block w/
7
- # newly created client, returning it after block terminates
8
-
9
8
  require 'rjr/node'
10
9
  require 'rjr/message'
11
10
 
12
11
  module RJR
13
12
 
14
- # Local client node callback interface,
15
- # send data back to client via local handlers
13
+ # Local node callback interface, used to invoke local json-rpc method handlers
16
14
  class LocalNodeCallback
15
+ # LocalNodeCallback initializer
16
+ # @param [Hash] args the options to create the local node callback with
17
+ # @option args [LocalNode] :node local node used to send/receive messages
17
18
  def initialize(args = {})
18
19
  @node = args[:node]
19
20
  end
20
21
 
22
+ # Implementation of {RJR::NodeCallback#invoke}
21
23
  def invoke(callback_method, *data)
22
24
  # TODO any exceptions from handler will propagate here, surround w/ begin/rescue block
23
25
  @node.invoke_request(callback_method, *data)
@@ -25,34 +27,58 @@ class LocalNodeCallback
25
27
  end
26
28
  end
27
29
 
28
- # Local node definition, listen for and invoke json-rpc
29
- # requests via local handlers
30
+ # Local node definition, implements the {RJR::Node} interface to
31
+ # listen for and invoke json-rpc requests via local handlers
32
+ #
33
+ # This is useful for situations in which you would like to invoke registered
34
+ # json-rpc handlers locally, enforcing the same constraints as
35
+ # you would on a json-rpc request coming in remotely.
36
+ #
37
+ # @example Listening for and dispatching json-rpc requests locally
38
+ # RJR::Dispatcher.add_handler('hello') { |name|
39
+ # @rjr_node_type == :local ? "Hello superuser #{name}" : "Hello #{name}!"
40
+ # }
41
+ #
42
+ # # initialize node and invoke request
43
+ # node = RJR::LocalNode.new :node_id => 'node'
44
+ # node.invoke_request('hello', 'mo')
45
+ #
30
46
  class LocalNode < RJR::Node
31
47
  RJR_NODE_TYPE = :local
32
48
 
33
- # allow clients to override the node type for the local node
49
+ # allows clients to override the node type for the local node
34
50
  attr_accessor :node_type
35
51
 
36
- # initialize the node w/ the specified params
52
+ # LocalNode initializer
53
+ # @param [Hash] args the options to create the local node with
37
54
  def initialize(args = {})
38
55
  super(args)
39
56
  @node_type = RJR_NODE_TYPE
40
57
  end
41
58
 
42
59
  # register connection event handler,
43
- # until we support manual disconnections of the local node, we don't
44
- # have to do anything here
60
+ # *note* Until we support manual disconnections of the local node, we don't have to do anything here
61
+ #
62
+ # @param [:error, :close] event the event to register the handler for
63
+ # @param [Callable] handler block param to be added to array of handlers that are called when event occurs
64
+ # @yield [LocalNode] self is passed to each registered handler when event occurs
45
65
  def on(event, &handler)
46
66
  # TODO raise error (for the time being)?
47
67
  end
48
68
 
49
69
  # Instruct Node to start listening for and dispatching rpc requests
70
+ #
71
+ # Currently does nothing as method handlers can be invoked directly upon invoke_request
50
72
  def listen
51
73
  em_run do
52
74
  end
53
75
  end
54
76
 
55
- # Instructs node to send rpc request, and wait for / return response
77
+ # Instructs node to send rpc request, and wait for and return response
78
+ # @param [String] rpc_method json-rpc method to invoke on destination
79
+ # @param [Array] args array of arguments to convert to json and invoke remote method wtih
80
+ # @return [Object] the json result retrieved from destination converted to a ruby object
81
+ # @raise [Exception] if the destination raises an exception, it will be converted to json and re-raised here
56
82
  def invoke_request(rpc_method, *args)
57
83
  0.upto(args.size).each { |i| args[i] = args[i].to_s if args[i].is_a?(Symbol) }
58
84
  message = RequestMessage.new :method => rpc_method,
data/lib/rjr/message.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # RJR Message
2
2
  #
3
+ # Representations of json-rpc messages in accordance with the standard
4
+ #
3
5
  # Copyright (C) 2012 Mohammed Morsi <mo@morsi.org>
4
6
  # Licensed under the Apache License, Version 2.0
5
7
 
@@ -18,12 +20,34 @@ class RequestMessage
18
20
  Array.new(16) {|x| rand(0xff) }
19
21
  end
20
22
 
23
+ # Message string received from the source
21
24
  attr_accessor :json_message
25
+
26
+ # Method source is invoking on the destination
22
27
  attr_accessor :jr_method
28
+
29
+ # Arguments source is passing to destination method
23
30
  attr_accessor :jr_args
31
+
32
+ # ID of the message in accordance w/ json-rpc specification
24
33
  attr_accessor :msg_id
34
+
35
+ # Optional headers to add to json outside of standard json-rpc request
25
36
  attr_accessor :headers
26
37
 
38
+ # RJR Request Message initializer
39
+ #
40
+ # This should be invoked with one of two argument sets. If creating a new message
41
+ # to send to the server, specify :method, :args, and :headers to include in the message
42
+ # (message id will be autogenerated). If handling an new request message sent from the
43
+ # client, simply specify :message and optionally any additional headers (they will
44
+ # be merged with the headers contained in the message)
45
+ #
46
+ # @param [Hash] args options to set on request
47
+ # @option args [String] :message json string received from sender
48
+ # @option args [Hash] :headers optional headers to set in request and subsequent messages
49
+ # @option args [String] :method method to invoke on server
50
+ # @option args [Array<Object>] :args to pass to server method, all must be convertable to/from json
27
51
  def initialize(args = {})
28
52
  if args.has_key?(:message)
29
53
  begin
@@ -52,6 +76,10 @@ class RequestMessage
52
76
  end
53
77
  end
54
78
 
79
+ # Class helper to determine if the specified string is a valid json-rpc
80
+ # method request
81
+ # @param [String] message string message to check
82
+ # @return [true,false] indicating if message is request message
55
83
  def self.is_request_message?(message)
56
84
  begin
57
85
  # TODO log error
@@ -61,6 +89,7 @@ class RequestMessage
61
89
  end
62
90
  end
63
91
 
92
+ # Convert request message to string json format
64
93
  def to_s
65
94
  request = { 'jsonrpc' => '2.0',
66
95
  'method' => @jr_method,
@@ -72,13 +101,34 @@ class RequestMessage
72
101
 
73
102
  end
74
103
 
75
- # Message sent from server to client in response to request message
104
+ # Message sent from server to client in response to json-rpc request message
76
105
  class ResponseMessage
106
+ # Message string received from the source
77
107
  attr_accessor :json_message
108
+
109
+ # ID of the message in accordance w/ json-rpc specification
78
110
  attr_accessor :msg_id
111
+
112
+ # Result encapsulated in the response message
113
+ # @see RJR::Result
79
114
  attr_accessor :result
115
+
116
+ # Optional headers to add to json outside of standard json-rpc request
80
117
  attr_accessor :headers
81
118
 
119
+ # ResponseMessage initializer
120
+ #
121
+ # This should be invoked with one of two argument sets. If creating a new message
122
+ # to send to the client, specify :id, :result, and :headers to include in the message.
123
+ # If handling an new request message sent from the client, simply specify :message
124
+ # and optionally any additional headers (they will be merged with the headers contained
125
+ # in the message)
126
+ #
127
+ # @param [Hash] args options to set on request
128
+ # @option args [String] :message json string received from sender
129
+ # @option args [Hash] :headers optional headers to set in request and subsequent messages
130
+ # @option args [String] :id id to set in response message, should be same as that in received message
131
+ # @option args [RJR::Result] :result result of json-rpc method invocation
82
132
  def initialize(args = {})
83
133
  if args.has_key?(:message)
84
134
  response = JSON.parse(args[:message])
@@ -115,6 +165,10 @@ class ResponseMessage
115
165
 
116
166
  end
117
167
 
168
+ # Class helper to determine if the specified string is a valid json-rpc
169
+ # method response
170
+ # @param [String] message string message to check
171
+ # @return [true,false] indicating if message is response message
118
172
  def self.is_response_message?(message)
119
173
  begin
120
174
  json = JSON.parse(message)
@@ -125,6 +179,7 @@ class ResponseMessage
125
179
  end
126
180
  end
127
181
 
182
+ # Convert request message to string json format
128
183
  def to_s
129
184
  s = ''
130
185
  if result.success
@@ -1,30 +1,57 @@
1
1
  # RJR MultiNode Endpoint
2
2
  #
3
+ # Implements the RJR::Node interface to satisty JSON-RPC requests over multiple protocols
4
+ #
3
5
  # Copyright (C) 2012 Mohammed Morsi <mo@morsi.org>
4
6
  # Licensed under the Apache License, Version 2.0
5
7
 
6
- # establish client connection w/ specified args and invoke block w/
7
- # newly created client, returning it after block terminates
8
-
9
8
  require 'eventmachine'
10
9
  require 'rjr/node'
11
10
  require 'rjr/message'
12
11
 
13
12
  module RJR
14
13
 
14
+ # Multiple node definition, allows a developer to easily multiplex transport
15
+ # mechanisms to serve JSON-RPC requests over.
16
+ #
17
+ # @example Listening for json-rpc requests over amqp, tcp, http, and websockets
18
+ # # register rjr dispatchers (see RJR::Dispatcher)
19
+ # RJR::Dispatcher.add_handler('hello') { |name|
20
+ # # optionally use @rjr_node_type to handle different transport types
21
+ # "Hello #{name}!"
22
+ # }
23
+ #
24
+ # amqp_server = RJR::TCPNode.new :node_id => 'amqp_server', :broker => 'localhost'
25
+ # tcp_server = RJR::TCPNode.new :node_id => 'tcp_server', :host => 'localhost', :port => '7777'
26
+ # web_server = RJR::WebNode.new :node_id => 'tcp_server', :host => 'localhost', :port => '80'
27
+ # ws_server = RJR::WebNode.new :node_id => 'tcp_server', :host => 'localhost', :port => '8080'
28
+ #
29
+ # server = RJR::MultiNode.new :node_id => 'server',
30
+ # :nodes => [amqp_server, tcp_server, web_server, ws_server]
31
+ # server.listen
32
+ # server.join
33
+ #
34
+ # # invoke requests as you normally would via any protocol
35
+ #
15
36
  class MultiNode < RJR::Node
16
- # initialize the node w/ the specified params
37
+ # MultiNode initializer
38
+ # @param [Hash] args the options to create the tcp node with
39
+ # @option args [Array<RJR::Node>] :nodes array of nodes to use to listen to new requests on
17
40
  def initialize(args = {})
18
41
  super(args)
19
42
  @nodes = args[:nodes]
20
43
  end
21
44
 
45
+ # Add node to multinode
46
+ # @param [RJR::Node] node the node to add
22
47
  def <<(node)
23
48
  @nodes << node
24
49
  end
25
50
 
26
51
 
27
52
  # Instruct Node to start listening for and dispatching rpc requests
53
+ #
54
+ # Implementation of {RJR::Node#listen}
28
55
  def listen
29
56
  @nodes.each { |node|
30
57
  node.listen