rjr 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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