rjr 0.18.2 → 0.19.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +2 -0
  3. data/bin/rjr-client +16 -9
  4. data/bin/rjr-server +2 -1
  5. data/examples/client.rb +21 -19
  6. data/examples/server.rb +1 -1
  7. data/examples/structured_server.rb +1 -0
  8. data/examples/tcp.rb +1 -0
  9. data/lib/rjr/common.rb +1 -226
  10. data/lib/rjr/core_ext.rb +63 -0
  11. data/lib/rjr/dispatcher.rb +75 -219
  12. data/lib/rjr/messages.rb +8 -0
  13. data/lib/rjr/messages/compressed.rb +264 -0
  14. data/lib/rjr/messages/notification.rb +95 -0
  15. data/lib/rjr/messages/request.rb +99 -0
  16. data/lib/rjr/messages/response.rb +128 -0
  17. data/lib/rjr/node.rb +100 -97
  18. data/lib/rjr/node_callback.rb +43 -0
  19. data/lib/rjr/nodes/amqp.rb +12 -11
  20. data/lib/rjr/nodes/easy.rb +4 -4
  21. data/lib/rjr/nodes/local.rb +13 -12
  22. data/lib/rjr/nodes/multi.rb +1 -1
  23. data/lib/rjr/nodes/tcp.rb +15 -13
  24. data/lib/rjr/nodes/template.rb +4 -4
  25. data/lib/rjr/nodes/unix.rb +15 -13
  26. data/lib/rjr/nodes/web.rb +15 -14
  27. data/lib/rjr/nodes/ws.rb +12 -11
  28. data/lib/rjr/request.rb +128 -0
  29. data/lib/rjr/result.rb +75 -0
  30. data/lib/rjr/util/args.rb +145 -0
  31. data/lib/rjr/{em_adapter.rb → util/em_adapter.rb} +0 -0
  32. data/lib/rjr/util/handles_methods.rb +115 -0
  33. data/lib/rjr/util/has_messages.rb +50 -0
  34. data/lib/rjr/{inspect.rb → util/inspect.rb} +1 -1
  35. data/lib/rjr/util/json_parser.rb +101 -0
  36. data/lib/rjr/util/logger.rb +128 -0
  37. data/lib/rjr/{thread_pool.rb → util/thread_pool.rb} +2 -0
  38. data/lib/rjr/version.rb +1 -1
  39. data/site/jrw.js +1 -1
  40. data/specs/args_spec.rb +144 -0
  41. data/specs/dispatcher_spec.rb +399 -211
  42. data/specs/em_adapter_spec.rb +31 -18
  43. data/specs/handles_methods_spec.rb +154 -0
  44. data/specs/has_messages_spec.rb +54 -0
  45. data/specs/inspect_spec.rb +1 -1
  46. data/specs/json_parser_spec.rb +169 -0
  47. data/specs/messages/notification_spec.rb +59 -0
  48. data/specs/messages/request_spec.rb +66 -0
  49. data/specs/messages/response_spec.rb +94 -0
  50. data/specs/node_callbacks_spec.rb +47 -0
  51. data/specs/node_spec.rb +465 -56
  52. data/specs/request_spec.rb +147 -0
  53. data/specs/result_spec.rb +144 -0
  54. data/specs/thread_pool_spec.rb +1 -1
  55. metadata +41 -11
  56. data/lib/rjr/errors.rb +0 -23
  57. data/lib/rjr/message.rb +0 -351
  58. data/lib/rjr/semaphore.rb +0 -58
  59. data/specs/message_spec.rb +0 -229
@@ -30,7 +30,7 @@ require 'thread'
30
30
  require 'eventmachine'
31
31
 
32
32
  require 'rjr/node'
33
- require 'rjr/message'
33
+ require 'rjr/messages'
34
34
 
35
35
  module RJR
36
36
  module Nodes
@@ -57,7 +57,7 @@ class WebConnection < EventMachine::Connection
57
57
  # XXX we still have to send a response back to client to satisfy
58
58
  # the http standard, even if this is a notification. handle_message
59
59
  # does not do this.
60
- @rjr_node.send_msg "", self if NotificationMessage.is_notification_message?(msg)
60
+ @rjr_node.send_msg "", self if Messages::Notification.is_notification_message?(msg)
61
61
  end
62
62
  end
63
63
 
@@ -87,13 +87,14 @@ end
87
87
  # puts client.invoke('http://localhost:7777', 'hello', 'mo')
88
88
  #
89
89
  # @example Invoking json-rpc requests over http using curl
90
- # $ curl -X POST http://localhost:7777 -d '{"jsonrpc":"2.0","method":"hello","params":["mo"],"id":"123"}'
91
- # > {"jsonrpc":"2.0","id":"123","result":"Hello mo!"}
90
+ # sh> curl -X POST http://localhost:7777 -d '{"jsonrpc":"2.0","method":"hello","params":["mo"],"id":"123"}'
91
+ # > {"jsonrpc":"2.0","id":"123","result":"Hello mo!"}
92
92
  #
93
93
  class Web < RJR::Node
94
94
 
95
95
  RJR_NODE_TYPE = :web
96
96
  PERSISTENT_NODE = false
97
+ INDIRECT_NODE = false
97
98
 
98
99
  public
99
100
 
@@ -113,7 +114,7 @@ class Web < RJR::Node
113
114
 
114
115
  # Send data using specified http connection
115
116
  #
116
- # Implementation of {RJR::Node#send_msg}
117
+ # Implementation of RJR::Node#send_msg
117
118
  def send_msg(data, connection)
118
119
  # we are assuming that since http connections
119
120
  # are not persistant, we should be sending a
@@ -131,7 +132,7 @@ class Web < RJR::Node
131
132
 
132
133
  # Instruct Node to start listening for and dispatching rpc requests
133
134
  #
134
- # Implementation of {RJR::Node#listen}
135
+ # Implementation of RJR::Node#listen
135
136
  def listen
136
137
  @@em.schedule do
137
138
  EventMachine::start_server(@host, @port, WebConnection, :rjr_node => self)
@@ -141,7 +142,7 @@ class Web < RJR::Node
141
142
 
142
143
  # Instructs node to send rpc request, and wait for / return response
143
144
  #
144
- # Implementation of {RJR::Node#invoke}
145
+ # Implementation of RJR::Node#invoke
145
146
  #
146
147
  # Do not invoke directly from em event loop or callback as will block the message
147
148
  # subscription used to receive responses
@@ -151,9 +152,9 @@ class Web < RJR::Node
151
152
  # @param [String] rpc_method json-rpc method to invoke on destination
152
153
  # @param [Array] args array of arguments to convert to json and invoke remote method wtih
153
154
  def invoke(uri, rpc_method, *args)
154
- message = RequestMessage.new :method => rpc_method,
155
- :args => args,
156
- :headers => @message_headers
155
+ message = Messages::Request.new :method => rpc_method,
156
+ :args => args,
157
+ :headers => @message_headers
157
158
  cb = lambda { |http|
158
159
  # TODO handle errors
159
160
  handle_message(http.response, http)
@@ -177,7 +178,7 @@ class Web < RJR::Node
177
178
 
178
179
  # Instructs node to send rpc notification (immadiately returns / no response is generated)
179
180
  #
180
- # Implementation of {RJR::Node#notify}
181
+ # Implementation of RJR::Node#notify
181
182
  #
182
183
  # @param [String] uri location of node to send request to, should be
183
184
  # in format of http://hostname:port
@@ -189,9 +190,9 @@ class Web < RJR::Node
189
190
  published_c = ConditionVariable.new
190
191
 
191
192
  invoked = false
192
- message = NotificationMessage.new :method => rpc_method,
193
- :args => args,
194
- :headers => @message_headers
193
+ message = Messages::Notification.new :method => rpc_method,
194
+ :args => args,
195
+ :headers => @message_headers
195
196
  cb = lambda { |arg| published_l.synchronize { invoked = true ; published_c.signal }}
196
197
  @@em.schedule do
197
198
  http = EventMachine::HttpRequest.new(uri).post :body => message.to_s,
@@ -23,7 +23,7 @@ else
23
23
  require 'thread'
24
24
 
25
25
  require 'rjr/node'
26
- require 'rjr/message'
26
+ require 'rjr/messages'
27
27
 
28
28
  module RJR
29
29
  module Nodes
@@ -55,6 +55,7 @@ module Nodes
55
55
  class WS < RJR::Node
56
56
  RJR_NODE_TYPE = :ws
57
57
  PERSISTENT_NODE = true
58
+ INDIRECT_NODE = false
58
59
 
59
60
  private
60
61
 
@@ -100,14 +101,14 @@ class WS < RJR::Node
100
101
 
101
102
  # Send data using specified websocket safely
102
103
  #
103
- # Implementation of {RJR::Node#send_msg}
104
+ # Implementation of RJR::Node#send_msg
104
105
  def send_msg(data, ws)
105
106
  @@em.schedule { ws.send(data) }
106
107
  end
107
108
 
108
109
  # Instruct Node to start listening for and dispatching rpc requests
109
110
  #
110
- # Implementation of {RJR::Node#listen}
111
+ # Implementation of RJR::Node#listen
111
112
  def listen
112
113
  @@em.schedule do
113
114
  EventMachine::WebSocket.run(:host => @host, :port => @port) do |ws|
@@ -122,7 +123,7 @@ class WS < RJR::Node
122
123
 
123
124
  # Instructs node to send rpc request, and wait for / return response
124
125
  #
125
- # Implementation of {RJR::Node#invoke}
126
+ # Implementation of RJR::Node#invoke
126
127
  #
127
128
  # Do not invoke directly from em event loop or callback as will block the message
128
129
  # subscription used to receive responses
@@ -132,9 +133,9 @@ class WS < RJR::Node
132
133
  # @param [String] rpc_method json-rpc method to invoke on destination
133
134
  # @param [Array] args array of arguments to convert to json and invoke remote method wtih
134
135
  def invoke(uri, rpc_method, *args)
135
- message = RequestMessage.new :method => rpc_method,
136
- :args => args,
137
- :headers => @message_headers
136
+ message = Messages::Request.new :method => rpc_method,
137
+ :args => args,
138
+ :headers => @message_headers
138
139
 
139
140
  @@em.schedule {
140
141
  init_client(uri) do |c|
@@ -155,7 +156,7 @@ class WS < RJR::Node
155
156
 
156
157
  # Instructs node to send rpc notification (immadiately returns / no response is generated)
157
158
  #
158
- # Implementation of {RJR::Node#notify}
159
+ # Implementation of RJR::Node#notify
159
160
  #
160
161
  # @param [String] uri location of node to send notification to, should be
161
162
  # in format of ws://hostname:port
@@ -167,9 +168,9 @@ class WS < RJR::Node
167
168
  published_c = ConditionVariable.new
168
169
 
169
170
  invoked = false
170
- message = NotificationMessage.new :method => rpc_method,
171
- :args => args,
172
- :headers => @message_headers
171
+ message = Messages::Notification.new :method => rpc_method,
172
+ :args => args,
173
+ :headers => @message_headers
173
174
  @@em.schedule {
174
175
  init_client(uri) do |c|
175
176
  c.stream { |msg| handle_message(msg.data, c) }
@@ -0,0 +1,128 @@
1
+ # RJR Request Representation
2
+ #
3
+ # Copyright (C) 2012-2014 Mohammed Morsi <mo@morsi.org>
4
+ # Licensed under the Apache License, Version 2.0
5
+
6
+ require 'json'
7
+ require 'rjr/result'
8
+ require 'rjr/core_ext'
9
+ require 'rjr/util/args'
10
+ require 'rjr/util/logger'
11
+
12
+ module RJR
13
+
14
+ # JSON-RPC request representation.
15
+ #
16
+ # Registered request handlers will be invoked in the context of
17
+ # instances of this class, meaning all member variables will be available
18
+ # for use in the handler.
19
+ class Request
20
+ # Result of the request operation, set by dispatcher
21
+ attr_accessor :result
22
+
23
+ # Method which request is for
24
+ attr_accessor :rjr_method
25
+
26
+ # Arguments be passed to method
27
+ attr_accessor :rjr_method_args
28
+
29
+ # Argument object encapsulating arguments
30
+ attr_accessor :rjr_args
31
+
32
+ # Headers which came w/ request
33
+ attr_accessor :rjr_headers
34
+
35
+ # Client IP which request came in on (only for direct nodes)
36
+ attr_accessor :rjr_client_ip
37
+
38
+ # Port which request came in on (only for direct nodes)
39
+ attr_accessor :rjr_client_port
40
+
41
+ # RJR callback which may be used to push data to client
42
+ attr_accessor :rjr_callback
43
+
44
+ # Node which the request came in on
45
+ attr_accessor :rjr_node
46
+
47
+ # Type of node which request came in on
48
+ attr_accessor :rjr_node_type
49
+
50
+ # ID of node which request came in on
51
+ attr_accessor :rjr_node_id
52
+
53
+ # Actual proc registered to handle request
54
+ attr_accessor :rjr_handler
55
+
56
+ # RJR Request initializer
57
+ #
58
+ # @param [Hash] args options to set on request,
59
+ # see Request accessors for valid keys
60
+ def initialize(args = {})
61
+ @rjr_method = args[:rjr_method] || args['rjr_method']
62
+ @rjr_method_args = args[:rjr_method_args] || args['rjr_method_args'] || []
63
+ @rjr_headers = args[:rjr_headers] || args['rjr_headers']
64
+
65
+ @rjr_client_ip = args[:rjr_client_ip]
66
+ @rjr_client_port = args[:rjr_client_port]
67
+
68
+ @rjr_callback = args[:rjr_callback]
69
+ @rjr_node = args[:rjr_node]
70
+ @rjr_node_id = args[:rjr_node_id] || args['rjr_node_id']
71
+ @rjr_node_type = args[:rjr_node_type] || args['rjr_node_type']
72
+
73
+ @rjr_handler = args[:rjr_handler]
74
+
75
+ @rjr_args = Arguments.new :args => @rjr_method_args
76
+ @result = nil
77
+ end
78
+
79
+ # Invoke the request by calling the registered handler with the registered
80
+ # method parameters in the local scope
81
+ def handle
82
+ node_sig = "#{@rjr_node_id}(#{@rjr_node_type})"
83
+ method_sig = "#{@rjr_method}(#{@rjr_method_args.join(',')})"
84
+
85
+ RJR::Logger.info "#{node_sig}->#{method_sig}"
86
+
87
+ # TODO option to compare arity of handler to number
88
+ # of method_args passed in ?
89
+ retval = instance_exec(*@rjr_method_args, &@rjr_handler)
90
+
91
+ RJR::Logger.info \
92
+ "#{node_sig}<-#{method_sig}<-#{retval.nil? ? "nil" : retval}"
93
+
94
+ return retval
95
+ end
96
+
97
+ def request_json
98
+ {:request => { :rjr_method => @rjr_method,
99
+ :rjr_method_args => @rjr_method_args,
100
+ :rjr_headers => @rjr_headers,
101
+ :rjr_node_type => @rjr_node_type,
102
+ :rjr_node_id => @rjr_node_id }}
103
+ end
104
+
105
+ def result_json
106
+ return {} unless !!@result
107
+ {:result => { :result => @result.result,
108
+ :error_code => @result.error_code,
109
+ :error_msg => @result.error_msg,
110
+ :error_class => @result.error_class }}
111
+ end
112
+
113
+ # Convert request to json representation and return it
114
+ def to_json(*a)
115
+ {'json_class' => self.class.name,
116
+ 'data' => request_json.merge(result_json)}.to_json(*a)
117
+ end
118
+
119
+ # Create new request from json representation
120
+ def self.json_create(o)
121
+ result = Result.new(o['data']['result'])
122
+ request = Request.new(o['data']['request'])
123
+ request.result = result
124
+ return request
125
+ end
126
+
127
+ end # class Request
128
+ end # module RJR
@@ -0,0 +1,75 @@
1
+ # RJR Result Representation
2
+ #
3
+ # Copyright (C) 2012-2014 Mohammed Morsi <mo@morsi.org>
4
+ # Licensed under the Apache License, Version 2.0
5
+
6
+ module RJR
7
+
8
+ # JSON-RPC Result Representation
9
+ class Result
10
+ # Boolean indicating if request was successfully invoked
11
+ attr_accessor :success
12
+
13
+ # Boolean indicating if request failed in some manner
14
+ attr_accessor :failed
15
+
16
+ # Return value of the json-rpc call if successful
17
+ attr_accessor :result
18
+
19
+ # Code corresponding to json-rpc error if problem occured during request invocation
20
+ attr_accessor :error_code
21
+
22
+ # Message corresponding to json-rpc error if problem occured during request invocation
23
+ attr_accessor :error_msg
24
+
25
+ # Class of error raised (if any) during request invocation (this is extra metadata beyond standard json-rpc)
26
+ attr_accessor :error_class
27
+
28
+ # RJR result intializer
29
+ # @param [Hash] args options to set on result
30
+ # @option args [Object] :result result of json-rpc method handler if successfully returned
31
+ # @option args [Integer] :error_code code corresponding to json-rpc error if problem occured during request invocation
32
+ # @option args [String] :error_msg message corresponding to json-rpc error if problem occured during request invocation
33
+ # @option args [Class] :error_class class of error raised (if any) during request invocation (this is extra metadata beyond standard json-rpc)
34
+ def initialize(args = {})
35
+ @result = args[:result] || args['result']
36
+ @error_code = args[:error_code] || args['error_code']
37
+ @error_msg = args[:error_msg] || args['error_msg']
38
+ @error_class = args[:error_class] || args['error_class']
39
+
40
+ @success = @error_code.nil?
41
+ @failed = !@error_code.nil?
42
+ end
43
+
44
+ # Compare Result against other result, returning true if both correspond
45
+ # to equivalent json-rpc results else false
46
+ def ==(other)
47
+ @success == other.success &&
48
+ @failed == other.failed &&
49
+ @result == other.result &&
50
+ @error_code == other.error_code &&
51
+ @error_msg == other.error_msg &&
52
+ @error_class == other.error_class
53
+ end
54
+
55
+ # Convert Response to human consumable string
56
+ def to_s
57
+ "#{@success} #{@result} #{@error_code} #{@error_msg} #{@error_class}"
58
+ end
59
+
60
+ ######### Specific request types
61
+
62
+ # JSON-RPC -32600 / Invalid Request
63
+ def self.invalid_request
64
+ return Result.new(:error_code => -32600,
65
+ :error_msg => 'Invalid Request')
66
+ end
67
+
68
+ # JSON-RPC -32602 / Method not found
69
+ def self.method_not_found(name)
70
+ return Result.new(:error_code => -32602,
71
+ :error_msg => "Method '#{name}' not found")
72
+ end
73
+
74
+ end # class Result
75
+ end # module RJR
@@ -0,0 +1,145 @@
1
+ # RJR JSON-RPC Argument Representation
2
+ #
3
+ # Copyright (C) 2014 Mohammed Morsi <mo@morsi.org>
4
+ # Licensed under the Apache License, Version 2.0
5
+
6
+ require 'forwardable'
7
+
8
+ module RJR
9
+
10
+ # Encapsulates a list of JSON-RPC method arguments as sent
11
+ # from the client to the server, in addition to providing
12
+ # various helper / utility methods.
13
+ class Arguments
14
+ include Enumerable
15
+ extend Forwardable
16
+
17
+ attr_accessor :args
18
+
19
+ def_delegators :@args, :each, :<<, :length, :[], :index
20
+
21
+ def initialize(args={})
22
+ @args = args[:args] || []
23
+ end
24
+
25
+ # Validate arguments against acceptable values.
26
+ #
27
+ # Raises error if value is found which is not on
28
+ # list of acceptable values.
29
+ #
30
+ # If acceptable values are hash's, keys are compared
31
+ # and on matches the values are used as the # of following
32
+ # arguments to skip in the validator
33
+ #
34
+ # *Note* args / acceptable params are converted to strings
35
+ # before comparison
36
+ #
37
+ # @example
38
+ # args = Arguments.new :args => ['custom', 'another']
39
+ # args.validate! 'custom', 'another' #=> nil
40
+ # args.validate! 'something' #=> ArgumentError
41
+ #
42
+ # args = Arguments.new :args => ['with_id', 123, 'another']
43
+ # args.validate! :with_id => 1, :another => 0 #=> nil
44
+ # args.validate! :with_id => 1 #=> ArgumentError
45
+ def validate!(*acceptable)
46
+ i = 0
47
+ if acceptable.first.is_a?(Hash)
48
+ # clone acceptable hash, swap keys for string
49
+ acceptable = Hash[acceptable.first]
50
+ acceptable.keys.each { |k|
51
+ acceptable[k.to_s] = acceptable[k]
52
+ acceptable.delete(k) unless k.is_a?(String)
53
+ }
54
+
55
+ # compare acceptable against arguments, raising error if issue found
56
+ while(i < length) do
57
+ val = self[i]
58
+ passed = acceptable.has_key?(val)
59
+ raise ArgumentError, "#{val} not an acceptable arg" unless passed
60
+ skip = acceptable[val]
61
+ i += (skip + 1)
62
+ end
63
+
64
+ else
65
+ # clone acceptable array, swap values for string
66
+ acceptable = Array.new(acceptable).map { |a| a.to_s }
67
+
68
+ # compare acceptable against arguments, raising error if issue found
69
+ while(i < length) do
70
+ val = self[i].to_s
71
+ passed = acceptable.include?(val)
72
+ raise ArgumentError, "#{val} not an acceptable arg" unless passed
73
+ i += 1
74
+ end
75
+ end
76
+
77
+ nil
78
+ end
79
+
80
+ # Extract groups of values from argument list
81
+ #
82
+ # Groups are generated by comparing arguments to keys in the
83
+ # specified map and on matches extracting the # of following
84
+ # arguments specified by the map values
85
+ #
86
+ # Note arguments / keys are converted to strings before comparison
87
+ #
88
+ # @example
89
+ # args = Arguments.new :args => ['with_id', 123, 'custom', 'another']
90
+ # args.extract :with_id => 1, :custom => 0
91
+ # # => [['with_id', 123], ['custom']]
92
+ #
93
+ def extract(map)
94
+ # clone map hash, swap keys for string
95
+ map = Hash[map]
96
+ map.keys.each { |k|
97
+ map[k.to_s] = map[k]
98
+ map.delete(k) unless k.is_a?(String)
99
+ }
100
+
101
+ groups = []
102
+ i = 0
103
+ while(i < length) do
104
+ val = self[i]
105
+ i += 1
106
+ next unless !!map.has_key?(val)
107
+
108
+ num = map[val]
109
+ group = [val]
110
+ 0.upto(num-1) do |j|
111
+ group << self[i]
112
+ i += 1
113
+ end
114
+ groups << group
115
+ end
116
+
117
+ groups
118
+ end
119
+
120
+ # Return boolean if tag appears in argument list.
121
+ # Simple wrapper around includes setting up the scope
122
+ # of argument 'specifiers'
123
+ #
124
+ # @example
125
+ # args = Arguments.new :args => ['foobar']
126
+ # args.specifies?('foobar') #=> true
127
+ # args.specifies?('barfoo') #=> false
128
+ def specifies?(tag)
129
+ include?(tag)
130
+ end
131
+
132
+ # Return specifier corresponding given tag. The specifier
133
+ # is defined as the value appearing in the argument list
134
+ # immediately after the tag
135
+ #
136
+ # @example
137
+ # args = Argument.new :args => ['with_id', 123]
138
+ # args.specifier_for('with_id') #=> 123
139
+ # args.specifier_for('other') #=> nil
140
+ def specifier_for(tag)
141
+ return nil unless specifies?(tag)
142
+ self[index(tag) + 1]
143
+ end
144
+ end
145
+ end