rjr 0.5.4 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -5,11 +5,6 @@
5
5
 
6
6
  require 'rdoc/task'
7
7
  require "rspec/core/rake_task"
8
- require 'rubygems/package_task'
9
-
10
-
11
- GEM_NAME="rjr"
12
- PKG_VERSION='0.5.4'
13
8
 
14
9
  desc "Run all specs"
15
10
  RSpec::Core::RakeTask.new(:spec) do |spec|
@@ -37,28 +32,7 @@ Rake::RDocTask.new do |rd|
37
32
  rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
38
33
  end
39
34
 
40
- PKG_FILES = FileList['lib/**/*.rb',
41
- 'LICENSE', 'Rakefile', 'README.rdoc', 'spec/**/*.rb' ]
42
-
43
- SPEC = Gem::Specification.new do |s|
44
- s.name = GEM_NAME
45
- s.version = PKG_VERSION
46
- s.files = PKG_FILES
47
- s.executables << 'rjr-server'
48
-
49
- s.required_ruby_version = '>= 1.8.1'
50
- s.required_rubygems_version = Gem::Requirement.new(">= 1.3.3")
51
- s.add_development_dependency('rspec', '~> 1.3.0')
52
-
53
- s.author = "Mohammed Morsi"
54
- s.email = "mo@morsi.org"
55
- s.date = %q{2012-04-25}
56
- s.description = %q{Ruby Json Rpc library}
57
- s.summary = %q{JSON RPC server and client library over amqp, websockets}
58
- s.homepage = %q{http://github.com/movitto/rjr}
59
- end
60
-
61
- Gem::PackageTask.new(SPEC) do |pkg|
62
- pkg.need_tar = true
63
- pkg.need_zip = true
35
+ desc "build the rjr gem"
36
+ task :build do
37
+ system "gem build rjr.gemspec"
64
38
  end
data/lib/rjr/amqp_node.rb CHANGED
@@ -17,27 +17,13 @@ module RJR
17
17
  # send data back to client via AMQP.
18
18
  class AMQPNodeCallback
19
19
  def initialize(args = {})
20
- @exchange = args[:exchange]
21
- @exchange_lock = args[:exchange_lock]
20
+ @node = args[:node]
22
21
  @destination = args[:destination]
23
- @message_headers = args[:headers]
24
- @disconnected = false
25
-
26
- @exchange_lock.synchronize{
27
- # FIXME should disconnect all callbacks on_return
28
- @exchange.on_return do |basic_return, metadata, payload|
29
- puts "#{payload} was returned! reply_code = #{basic_return.reply_code}, reply_text = #{basic_return.reply_text}"
30
- @disconnected = true
31
- end
32
- }
33
22
  end
34
23
 
35
24
  def invoke(callback_method, *data)
36
25
  msg = RequestMessage.new :method => callback_method, :args => data, :headers => @message_headers
37
- raise RJR::Errors::ConnectionError.new("client unreachable") if @disconnected
38
- @exchange_lock.synchronize{
39
- @exchange.publish(msg.to_s, :routing_key => @destination, :mandatory => true)
40
- }
26
+ @node.publish msg.to_s, :routing_key => @destination, :mandatory => true
41
27
  end
42
28
  end
43
29
 
@@ -45,25 +31,14 @@ end
45
31
  class AMQPNode < RJR::Node
46
32
  RJR_NODE_TYPE = :amqp
47
33
 
48
-
49
34
  private
50
35
  def handle_message(metadata, msg)
51
36
  if RequestMessage.is_request_message?(msg)
52
37
  reply_to = metadata.reply_to
53
-
54
- # TODO should delete handler threads as they complete & should handle timeout
55
38
  @thread_pool << ThreadPoolJob.new { handle_request(reply_to, msg) }
56
39
 
57
40
  elsif ResponseMessage.is_response_message?(msg)
58
- # TODO test message, make sure it is a response message
59
- msg = ResponseMessage.new(:message => msg, :headers => @message_headers)
60
- lock = @message_locks[msg.msg_id]
61
- if lock
62
- headers = @message_headers.merge(msg.headers)
63
- res = Dispatcher.handle_response(msg.result)
64
- lock << res
65
- lock[0].synchronize { lock[1].signal }
66
- end
41
+ handle_response(msg)
67
42
 
68
43
  end
69
44
  end
@@ -74,16 +49,34 @@ class AMQPNode < RJR::Node
74
49
  result = Dispatcher.dispatch_request(msg.jr_method,
75
50
  :method_args => msg.jr_args,
76
51
  :headers => headers,
52
+ :client_ip => nil, # since client doesn't directly connect to server, we can't leverage
53
+ :client_port => nil, # client ip / port for requests received via the amqp node type
54
+ :rjr_node => self,
77
55
  :rjr_node_id => @node_id,
78
56
  :rjr_node_type => RJR_NODE_TYPE,
79
57
  :rjr_callback =>
80
- AMQPNodeCallback.new(:exchange => @exchange,
81
- :exchange_lock => @exchange_lock,
58
+ AMQPNodeCallback.new(:node => self,
59
+ :exchange => @exchange,
60
+ :amqp_lock => @amqp_lock,
82
61
  :destination => reply_to,
83
62
  :headers => headers))
84
63
  response = ResponseMessage.new(:id => msg.msg_id, :result => result, :headers => headers)
85
- @exchange_lock.synchronize{
86
- @exchange.publish(response.to_s, :routing_key => reply_to)
64
+ publish response.to_s, :routing_key => reply_to
65
+ end
66
+
67
+ def handle_response(message)
68
+ msg = ResponseMessage.new(:message => message, :headers => @message_headers)
69
+ res = err = nil
70
+ begin
71
+ res = Dispatcher.handle_response(msg.result)
72
+ rescue Exception => e
73
+ err = e
74
+ end
75
+
76
+ @response_lock.synchronize{
77
+ @result = [res]
78
+ @result << err if !err.nil?
79
+ @response_cv.signal
87
80
  }
88
81
  end
89
82
 
@@ -93,14 +86,14 @@ class AMQPNode < RJR::Node
93
86
  def initialize(args = {})
94
87
  super(args)
95
88
  @broker = args[:broker]
96
-
97
- # tuple of message ids to locks/condition variables for the responses
98
- # of those messages with optional result response
99
- @message_locks = {}
89
+ @connection_event_handlers = {:closed => [], :error => []}
90
+ @response_lock = Mutex.new
91
+ @response_cv = ConditionVariable.new
100
92
  end
101
93
 
102
94
  # Initialize the amqp subsystem
103
95
  def init_node
96
+ return unless @conn.nil? || !@conn.connected?
104
97
  @conn = AMQP.connect(:host => @broker)
105
98
  @conn.on_tcp_connection_failure { puts "OTCF #{@node_id}" }
106
99
 
@@ -111,7 +104,57 @@ class AMQPNode < RJR::Node
111
104
  @queue_name = "#{@node_id.to_s}-queue"
112
105
  @queue = @channel.queue(@queue_name, :auto_delete => true)
113
106
  @exchange = @channel.default_exchange
114
- @exchange_lock = Mutex.new
107
+
108
+ @listening = false
109
+ @disconnected = false
110
+
111
+ @exchange.on_return do |basic_return, metadata, payload|
112
+ puts "#{payload} was returned! reply_code = #{basic_return.reply_code}, reply_text = #{basic_return.reply_text}"
113
+ @disconnected = true # FIXME member will be set on wrong class
114
+ connection_event(:error)
115
+ connection_event(:closed)
116
+ end
117
+ end
118
+
119
+ # publish a message using the amqp exchange
120
+ def publish(*args)
121
+ raise RJR::Errors::ConnectionError.new("client unreachable") if @disconnected
122
+ @exchange.publish *args
123
+ end
124
+
125
+ # subscribe to messages using the amqp queue
126
+ def subscribe(*args, &bl)
127
+ return if @listening
128
+ @listening = true
129
+ @queue.subscribe do |metadata, msg|
130
+ bl.call metadata, msg
131
+ end
132
+ end
133
+
134
+ def wait_for_result(message)
135
+ res = nil
136
+ @response_lock.synchronize{
137
+ @response_cv.wait @response_lock
138
+ res = @result
139
+ }
140
+ return res
141
+ end
142
+
143
+ # register connection event handler
144
+ def on(event, &handler)
145
+ if @connection_event_handlers.keys.include?(event)
146
+ @connection_event_handlers[event] << handler
147
+ end
148
+ end
149
+
150
+ # run connection event handlers for specified event
151
+ # TODO these are only run when we fail to send message to queue, need to detect when that queue is shutdown & other events
152
+ def connection_event(event)
153
+ if @connection_event_handlers.keys.include?(event)
154
+ @connection_event_handlers[event].each { |h|
155
+ h.call self
156
+ }
157
+ end
115
158
  end
116
159
 
117
160
  # Instruct Node to start listening for and dispatching rpc requests
@@ -120,44 +163,36 @@ class AMQPNode < RJR::Node
120
163
  init_node
121
164
 
122
165
  # start receiving messages
123
- @queue.subscribe do |metadata, msg|
124
- handle_message(metadata, msg)
125
- end
166
+ subscribe { |metadata, msg|
167
+ handle_message(metadata, msg)
168
+ }
126
169
  end
127
170
  end
128
171
 
129
172
  # Instructs node to send rpc request, and wait for / return response
130
173
  def invoke_request(routing_key, rpc_method, *args)
131
- req_mutex = Mutex.new
132
- req_cv = ConditionVariable.new
133
-
134
174
  message = RequestMessage.new :method => rpc_method,
135
175
  :args => args,
136
176
  :headers => @message_headers
137
177
  em_run do
138
178
  init_node
139
179
 
140
- @message_locks[message.msg_id] = [req_mutex, req_cv]
141
-
142
180
  # begin listening for result
143
- @queue.subscribe do |metadata, msg|
181
+ subscribe { |metadata, msg|
144
182
  handle_message(metadata, msg)
145
- end
146
-
147
- @exchange_lock.synchronize{
148
- @exchange.publish(message.to_s, :routing_key => routing_key, :reply_to => @queue_name)
149
183
  }
184
+
185
+ publish message.to_s, :routing_key => routing_key, :reply_to => @queue_name
150
186
  end
151
187
 
152
- ## wait for result
153
- # TODO - make this optional, eg a non-blocking operation mode
154
- # (allowing event handler registration to be run on success / fail / etc)
155
- req_mutex.synchronize { req_cv.wait(req_mutex) }
156
- result = @message_locks[message.msg_id][2]
157
- @message_locks.delete(message.msg_id)
188
+ result = wait_for_result(message)
158
189
  self.stop
159
190
  self.join unless self.em_running?
160
- return result
191
+
192
+ if result.size > 1
193
+ raise result[1]
194
+ end
195
+ return result.first
161
196
  end
162
197
 
163
198
  end
data/lib/rjr/common.rb CHANGED
@@ -12,8 +12,10 @@ class Logger
12
12
  private
13
13
  def self._instantiate_logger
14
14
  unless defined? @@logger
15
+ #STDOUT.sync = true
15
16
  @@logger = ::Logger.new(STDOUT)
16
17
  @@logger.level = ::Logger::FATAL
18
+ @@logger_mutex = Mutex.new
17
19
  end
18
20
  end
19
21
 
@@ -21,7 +23,15 @@ class Logger
21
23
 
22
24
  def self.method_missing(method_id, *args)
23
25
  _instantiate_logger
24
- @@logger.send(method_id, args)
26
+ @@logger_mutex.synchronize {
27
+ if args.first.is_a?(Array)
28
+ args.first.each{ |a|
29
+ @@logger.send(method_id, a)
30
+ }
31
+ else
32
+ @@logger.send(method_id, args)
33
+ end
34
+ }
25
35
  end
26
36
 
27
37
  def self.logger
@@ -37,6 +47,7 @@ end
37
47
 
38
48
  end # module RJR
39
49
 
50
+ if RUBY_VERSION < "1.9"
40
51
  # http://blog.jayfields.com/2006/09/ruby-instanceexec-aka-instanceeval.html
41
52
  class Object
42
53
  module InstanceExecHelper; end
@@ -58,3 +69,4 @@ class Object
58
69
  ret
59
70
  end
60
71
  end
72
+ end
@@ -24,7 +24,10 @@ class Request
24
24
  @method = args[:method]
25
25
  @method_args = args[:method_args]
26
26
  @headers = args[:headers]
27
+ @client_ip = args[:client_ip]
28
+ @client_port = args[:client_port]
27
29
  @rjr_callback = args[:rjr_callback]
30
+ @rjr_node = args[:rjr_node]
28
31
  @rjr_node_id = args[:rjr_node_id]
29
32
  @rjr_node_type = args[:rjr_node_type]
30
33
  @handler = args[:handler]
@@ -113,9 +116,7 @@ class Handler
113
116
  return Result.new(:result => retval)
114
117
 
115
118
  rescue Exception => e
116
- RJR::Logger.warn "Exception Raised in #{method_name} handler #{e}"
117
- e.backtrace.each { |b| RJR::Logger.warn b }
118
- # TODO store exception class to be raised later
119
+ RJR::Logger.warn ["Exception Raised in #{method_name} handler #{e}"] + e.backtrace
119
120
 
120
121
  return Result.new(:error_code => -32000,
121
122
  :error_msg => e.to_s,
@@ -132,10 +133,13 @@ class Dispatcher
132
133
  end
133
134
 
134
135
  # register a handler to the specified method
135
- def self.add_handler(method_name, args = {}, &handler)
136
+ def self.add_handler(method_names, args = {}, &handler)
137
+ method_names = Array(method_names) unless method_names.is_a?(Array)
136
138
  @@handlers ||= {}
137
- @@handlers[method_name] = Handler.new args.merge(:method => method_name,
138
- :handler => handler)
139
+ method_names.each { |method_name|
140
+ @@handlers[method_name] = Handler.new args.merge(:method => method_name,
141
+ :handler => handler)
142
+ }
139
143
  end
140
144
 
141
145
  # Helper to handle request messages
@@ -38,6 +38,13 @@ class LocalNode < RJR::Node
38
38
  @node_type = RJR_NODE_TYPE
39
39
  end
40
40
 
41
+ # register connection event handler,
42
+ # until we support manual disconnections of the local node, we don't
43
+ # have to do anything here
44
+ def on(event, &handler)
45
+ # TODO raise error (for the time being)?
46
+ end
47
+
41
48
  # Instruct Node to start listening for and dispatching rpc requests
42
49
  def listen
43
50
  em_run do
@@ -50,15 +57,29 @@ class LocalNode < RJR::Node
50
57
  message = RequestMessage.new :method => rpc_method,
51
58
  :args => args,
52
59
  :headers => @message_headers
53
- result = Dispatcher.dispatch_request(rpc_method,
54
- :method_args => args,
60
+
61
+ # we serialize / unserialze messages to ensure local node complies
62
+ # to same json-rpc restrictions as other nodes
63
+ message = RequestMessage.new :message => message.to_s,
64
+ :headers => @message_headers
65
+
66
+ result = Dispatcher.dispatch_request(message.jr_method,
67
+ :method_args => message.jr_args,
55
68
  :headers => @message_headers,
69
+ :rjr_node => self,
56
70
  :rjr_node_id => @node_id,
57
71
  :rjr_node_type => @node_type,
58
72
  :rjr_callback =>
59
73
  LocalNodeCallback.new(:node => self,
60
74
  :headers => @message_headers))
61
- return Dispatcher.handle_response(result)
75
+ response = ResponseMessage.new(:id => message.msg_id,
76
+ :result => result,
77
+ :headers => @message_headers)
78
+
79
+ # same comment on serialization/unserialization as above
80
+ response = ResponseMessage.new(:message => response.to_s,
81
+ :headers => @message_headers)
82
+ return Dispatcher.handle_response(response.result)
62
83
  end
63
84
 
64
85
  end
data/lib/rjr/message.rb CHANGED
@@ -40,7 +40,6 @@ class RequestMessage
40
40
 
41
41
  rescue Exception => e
42
42
  #puts "Exception Parsing Request #{e}"
43
- # TODO
44
43
  raise e
45
44
  end
46
45
 
@@ -55,6 +54,7 @@ class RequestMessage
55
54
 
56
55
  def self.is_request_message?(message)
57
56
  begin
57
+ # TODO log error
58
58
  JSON.parse(message).has_key?('method')
59
59
  rescue Exception => e
60
60
  false
@@ -95,7 +95,7 @@ class ResponseMessage
95
95
  elsif response.has_key?('error')
96
96
  @result.error_code = response['error']['code']
97
97
  @result.error_msg = response['error']['message']
98
- @result.error_class = response['error']['class'] # TODO safely constantize this
98
+ @result.error_class = response['error']['class'] # TODO safely constantize this ?
99
99
 
100
100
  end
101
101
 
@@ -117,8 +117,10 @@ class ResponseMessage
117
117
 
118
118
  def self.is_response_message?(message)
119
119
  begin
120
- JSON.parse(message).has_key?('result')
120
+ json = JSON.parse(message)
121
+ json.has_key?('result') || json.has_key?('error')
121
122
  rescue Exception => e
123
+ # TODO log error
122
124
  false
123
125
  end
124
126
  end
data/lib/rjr/node.rb CHANGED
@@ -25,10 +25,6 @@ class Node
25
25
 
26
26
  @message_headers = {}
27
27
  @message_headers.merge!(args[:headers]) if args.has_key?(:headers)
28
-
29
- # threads pool to handle incoming requests
30
- # FIXME make the # of threads and timeout configurable)
31
- @thread_pool = ThreadPool.new(10, :timeout => 5)
32
28
  end
33
29
 
34
30
  # run job in event machine
@@ -38,6 +34,12 @@ class Node
38
34
 
39
35
  @@em_thread ||= nil
40
36
 
37
+ unless !@thread_pool.nil? && @thread_pool.running?
38
+ # threads pool to handle incoming requests
39
+ # FIXME make the # of threads and timeout configurable)
40
+ @thread_pool = ThreadPool.new(10, :timeout => 5)
41
+ end
42
+
41
43
  if @@em_thread.nil?
42
44
  @@em_thread =
43
45
  Thread.new{
@@ -54,7 +56,7 @@ class Node
54
56
  end
55
57
 
56
58
  def em_running?
57
- EventMachine.reactor_running?
59
+ @@em_jobs > 0 && EventMachine.reactor_running?
58
60
  end
59
61
 
60
62
  def join
@@ -67,7 +69,7 @@ class Node
67
69
  def stop
68
70
  @@em_jobs -= 1
69
71
  if @@em_jobs == 0
70
- EventMachine.stop
72
+ EventMachine.stop_event_loop
71
73
  @thread_pool.stop
72
74
  end
73
75
  end
@@ -77,7 +77,7 @@ class ThreadPool
77
77
  res
78
78
  end
79
79
 
80
- # TODO should not invoke after stop is called
80
+ # should not invoke after stop is called
81
81
  def check_timeout(timeout)
82
82
  @timeout_lock.synchronize {
83
83
  if !@time_started.nil? && Time.now - @time_started > timeout
data/lib/rjr/web_node.rb CHANGED
@@ -40,11 +40,15 @@ class WebRequestHandler < EventMachine::Connection
40
40
  msg = nil
41
41
  result = nil
42
42
  begin
43
+ client_port, client_ip = Socket.unpack_sockaddr_in(get_peername)
43
44
  msg = RequestMessage.new(:message => message, :headers => @web_node.message_headers)
44
45
  headers = @web_node.message_headers.merge(msg.headers)
45
46
  result = Dispatcher.dispatch_request(msg.jr_method,
46
47
  :method_args => msg.jr_args,
47
48
  :headers => headers,
49
+ :client_ip => client_ip,
50
+ :client_port => client_port,
51
+ :rjr_node => @web_node,
48
52
  :rjr_node_id => @web_node.node_id,
49
53
  :rjr_node_type => RJR_NODE_TYPE,
50
54
  :rjr_callback => WebNodeCallback.new())
@@ -65,7 +69,6 @@ class WebRequestHandler < EventMachine::Connection
65
69
 
66
70
  def process_http_request
67
71
  # TODO support http protocols other than POST
68
- # TODO should delete handler threads as they complete & should handle timeout
69
72
  msg = @http_post_content.nil? ? '' : @http_post_content
70
73
  #@thread_pool << ThreadPoolJob.new { handle_request(msg) }
71
74
  handle_request(msg)
@@ -88,6 +91,12 @@ class WebNode < RJR::Node
88
91
  def init_node
89
92
  end
90
93
 
94
+ # register connection event handler,
95
+ # since web node connections aren't persistant, we don't do anything here
96
+ def on(event, &handler)
97
+ # TODO raise error?
98
+ end
99
+
91
100
  # Instruct Node to start listening for and dispatching rpc requests
92
101
  def listen
93
102
  em_run do
data/lib/rjr/ws_node.rb CHANGED
@@ -20,10 +20,6 @@ class WSNodeCallback
20
20
  def initialize(args = {})
21
21
  @socket = args[:socket]
22
22
  @message_headers = args[:headers]
23
-
24
- # FIXME onclose, invalidate this callback / terminate outstanding handlers
25
- #@socket.onclose {}
26
- #@socket.onerror { |error|}
27
23
  end
28
24
 
29
25
  def invoke(callback_method, *data)
@@ -40,11 +36,15 @@ class WSNode < RJR::Node
40
36
 
41
37
  private
42
38
  def handle_request(socket, message)
39
+ client_port, client_ip = Socket.unpack_sockaddr_in(socket.get_peername)
43
40
  msg = RequestMessage.new(:message => message, :headers => @message_headers)
44
41
  headers = @message_headers.merge(msg.headers)
45
42
  result = Dispatcher.dispatch_request(msg.jr_method,
46
43
  :method_args => msg.jr_args,
47
44
  :headers => headers,
45
+ :client_ip => client_ip,
46
+ :client_port => client_port,
47
+ :rjr_node => self,
48
48
  :rjr_node_id => @node_id,
49
49
  :rjr_node_type => RJR_NODE_TYPE,
50
50
  :rjr_callback =>
@@ -60,6 +60,15 @@ class WSNode < RJR::Node
60
60
  super(args)
61
61
  @host = args[:host]
62
62
  @port = args[:port]
63
+
64
+ @connection_event_handlers = {:closed => [], :error => []}
65
+ end
66
+
67
+ # register connection event handler
68
+ def on(event, &handler)
69
+ if @connection_event_handlers.keys.include?(event)
70
+ @connection_event_handlers[event] << handler
71
+ end
63
72
  end
64
73
 
65
74
  # Initialize the ws subsystem
@@ -71,11 +80,18 @@ class WSNode < RJR::Node
71
80
  em_run do
72
81
  init_node
73
82
  EventMachine::WebSocket.start(:host => @host, :port => @port) do |ws|
74
- ws.onopen { }
75
- ws.onclose { }
76
- ws.onerror {|e|}
83
+ ws.onopen {}
84
+ ws.onclose {
85
+ @connection_event_handlers[:closed].each { |h|
86
+ h.call self
87
+ }
88
+ }
89
+ ws.onerror {|e|
90
+ @connection_event_handlers[:error].each { |h|
91
+ h.call self
92
+ }
93
+ }
77
94
  ws.onmessage { |msg|
78
- # TODO should delete handler threads as they complete & should handle timeout
79
95
  @thread_pool << ThreadPoolJob.new { handle_request(ws, msg) }
80
96
  }
81
97
  end
data/lib/rjr.rb CHANGED
@@ -3,18 +3,15 @@
3
3
  # Copyright (C) 2010 Mohammed Morsi <movitto@yahoo.com>
4
4
  # Licensed under the AGPLv3+ http://www.gnu.org/licenses/agpl.txt
5
5
 
6
- lib = File.dirname(__FILE__)
7
- $: << lib + '/rjr/'
8
-
9
- require lib + '/rjr/common'
10
- require lib + '/rjr/errors'
11
- require lib + '/rjr/thread_pool'
12
- require lib + '/rjr/semaphore'
13
- require lib + '/rjr/node'
14
- require lib + '/rjr/dispatcher'
15
- require lib + '/rjr/message'
16
- require lib + '/rjr/local_node'
17
- require lib + '/rjr/amqp_node'
18
- require lib + '/rjr/ws_node'
19
- require lib + '/rjr/web_node'
20
- require lib + '/rjr/multi_node'
6
+ require 'rjr/common'
7
+ require 'rjr/errors'
8
+ require 'rjr/thread_pool'
9
+ require 'rjr/semaphore'
10
+ require 'rjr/node'
11
+ require 'rjr/dispatcher'
12
+ require 'rjr/message'
13
+ require 'rjr/local_node'
14
+ require 'rjr/amqp_node'
15
+ require 'rjr/ws_node'
16
+ require 'rjr/web_node'
17
+ require 'rjr/multi_node'
@@ -0,0 +1,31 @@
1
+ require 'rjr/amqp_node'
2
+ require 'rjr/dispatcher'
3
+
4
+ describe RJR::AMQPNode do
5
+ it "should invoke and satisfy amqp requests" do
6
+ server = RJR::AMQPNode.new :node_id => 'amqp', :broker => 'localhost'
7
+ client = RJR::AMQPNode.new :node_id => 'client', :broker => 'localhost'
8
+
9
+ foozbar_invoked = false
10
+ RJR::Dispatcher.init_handlers
11
+ RJR::Dispatcher.add_handler('foozbar') { |param|
12
+ @client_ip.should == nil
13
+ @client_port.should == nil
14
+ @rjr_node.should == server
15
+ @rjr_node_id.should == 'amqp'
16
+ @rjr_node_type.should == :amqp
17
+ param.should == 'myparam'
18
+ foozbar_invoked = true
19
+ 'retval'
20
+ }
21
+
22
+ server.listen
23
+ res = client.invoke_request 'amqp-queue', 'foozbar', 'myparam'
24
+ server.halt
25
+ server.join
26
+ res.should == 'retval'
27
+ foozbar_invoked.should == true
28
+ end
29
+
30
+ # TODO ensure closed / error event handlers are invoked
31
+ end
@@ -0,0 +1,144 @@
1
+ require 'rjr/dispatcher'
2
+
3
+ describe RJR::Request do
4
+ it "invokes registered handler in request context" do
5
+ invoked = false
6
+ rjr_callback = Object.new
7
+ request = RJR::Request.new :method => 'foobar',
8
+ :method_args => ['a', 123],
9
+ :headers => {'qqq' => 'www'},
10
+ :rjr_callback => rjr_callback,
11
+ :rjr_node_id => 'test',
12
+ :rjr_node_type => 'test_type',
13
+ :handler => lambda { |p1, p2|
14
+ invoked = true
15
+ @method.should == 'foobar'
16
+ p1.should == 'a'
17
+ p2.should == 123
18
+ @headers['qqq'].should == 'www'
19
+ @rjr_callback.should == rjr_callback
20
+ @rjr_node_id.should == 'test'
21
+ @rjr_node_type.should == 'test_type'
22
+ }
23
+ request.handle
24
+ invoked.should == true
25
+ end
26
+ end
27
+
28
+ describe RJR::Result do
29
+ it "should handle successful results" do
30
+ result = RJR::Result.new :result => 'foobar'
31
+ result.success.should == true
32
+ result.failed.should == false
33
+ result.result.should == 'foobar'
34
+ result.error_code.should == nil
35
+ result.error_msg.should == nil
36
+ result.error_class.should == nil
37
+ end
38
+
39
+ it "should handle errors" do
40
+ result = RJR::Result.new :error_code => 123, :error_msg => 'abc', :error_class => ArgumentError
41
+ result.success.should == false
42
+ result.failed.should == true
43
+ result.result.should == nil
44
+ result.error_code.should == 123
45
+ result.error_msg.should == 'abc'
46
+ result.error_class.should == ArgumentError
47
+ end
48
+ end
49
+
50
+
51
+ describe RJR::Handler do
52
+ it "should return method not found result if method name is not specified" do
53
+ handler = RJR::Handler.new :method => nil
54
+ result = handler.handle
55
+ result.should == RJR::Result.method_not_found(nil)
56
+ end
57
+
58
+ it "should invoke registered handler for request" do
59
+ invoked = false
60
+ handler = RJR::Handler.new :method => 'foobar',
61
+ :handler => lambda {
62
+ invoked = true
63
+ }
64
+ handler.handle({:method_args => [] })
65
+ invoked.should == true
66
+ end
67
+
68
+ it "should return handler's return value in successful result" do
69
+ retval = Object.new
70
+ handler = RJR::Handler.new :method => 'foobar',
71
+ :handler => lambda {
72
+ retval
73
+ }
74
+ res = handler.handle({:method_args => [] })
75
+ res.success.should == true
76
+ res.result.should == retval
77
+ end
78
+
79
+ it "should catch handler errors and return error result" do
80
+ handler = RJR::Handler.new :method => 'foobar',
81
+ :method_args => [],
82
+ :handler => lambda {
83
+ raise ArgumentError, "uh oh!"
84
+ }
85
+ res = handler.handle({:method_args => [] })
86
+ res.failed.should == true
87
+ res.error_code.should == -32000
88
+ res.error_msg.should == "uh oh!"
89
+ res.error_class.should == ArgumentError
90
+ end
91
+ end
92
+
93
+ describe RJR::Dispatcher do
94
+ it "should dispatch request to registered handler" do
95
+ invoked_foobar = false
96
+ invoked_barfoo = false
97
+ RJR::Dispatcher.init_handlers
98
+ RJR::Dispatcher.add_handler('foobar') { |param1, param2|
99
+ invoked_foobar = true
100
+ param1.should == "param1"
101
+ param2.should == "param2"
102
+ "retval"
103
+ }
104
+ RJR::Dispatcher.add_handler('barfoo') { |param1, param2|
105
+ invoked_barfoo = true
106
+ }
107
+ res = RJR::Dispatcher.dispatch_request('foobar', :method_args => ['param1', 'param2'])
108
+ res.success.should == true
109
+ res.result.should == "retval"
110
+ invoked_foobar.should == true
111
+ invoked_barfoo.should == false
112
+ end
113
+
114
+ it "should allow a single handler to be subscribed to multiple methods" do
115
+ invoked_handler = 0
116
+ RJR::Dispatcher.init_handlers
117
+ RJR::Dispatcher.add_handler(['foobar', 'barfoo']) { |param1, param2|
118
+ invoked_handler += 1
119
+ }
120
+ RJR::Dispatcher.dispatch_request('foobar', :method_args => ['param1', 'param2'])
121
+ RJR::Dispatcher.dispatch_request('barfoo', :method_args => ['param1', 'param2'])
122
+ invoked_handler.should == 2
123
+ end
124
+
125
+ it "should return method not found result if handler for specified message is missing" do
126
+ RJR::Dispatcher.init_handlers
127
+ res = RJR::Dispatcher.dispatch_request('foobar')
128
+ res.should == RJR::Result.method_not_found('foobar')
129
+ end
130
+
131
+ it "should handle success response" do
132
+ res = RJR::Result.new :result => 'woot'
133
+ processed = RJR::Dispatcher.handle_response(res)
134
+ processed.should == "woot"
135
+ end
136
+
137
+ it "should handle error response" do
138
+ lambda{
139
+ res = RJR::Result.new :error_code => 123, :error_msg => "bah", :error_class => ArgumentError
140
+ RJR::Dispatcher.handle_response(res)
141
+ }.should raise_error(Exception, "bah")
142
+ #}.should raise_error(ArgumentError, "bah")
143
+ end
144
+ end
@@ -0,0 +1,43 @@
1
+ require 'rjr/local_node'
2
+ require 'rjr/dispatcher'
3
+
4
+ describe RJR::LocalNode do
5
+ it "should invoke requests against local handler" do
6
+ node = RJR::LocalNode.new :node_id => 'aaa'
7
+ foobar_invoked = false
8
+ RJR::Dispatcher.init_handlers
9
+ RJR::Dispatcher.add_handler('foobar') { |param|
10
+ @rjr_node.should == node
11
+ @rjr_node_id.should == 'aaa'
12
+ @rjr_node_type.should == :local
13
+ param.should == 'myparam'
14
+ foobar_invoked = true
15
+ 'retval'
16
+ }
17
+
18
+ res = node.invoke_request 'foobar', 'myparam'
19
+ foobar_invoked.should == true
20
+ res.should == 'retval'
21
+ end
22
+
23
+ it "should invoke callbacks against local handlers" do
24
+ foobar_invoked = false
25
+ callback_invoked = false
26
+ RJR::Dispatcher.init_handlers
27
+ RJR::Dispatcher.add_handler('foobar') {
28
+ foobar_invoked = true
29
+ @rjr_callback.invoke('callback', 'cp')
30
+ }
31
+ RJR::Dispatcher.add_handler('callback') { |*params|
32
+ params.length.should == 1
33
+ params[0].should == 'cp'
34
+ callback_invoked = true
35
+ }
36
+
37
+ node = RJR::LocalNode.new
38
+ node.invoke_request 'foobar', 'myparam'
39
+ end
40
+
41
+ # TODO make sure object attributes not serialized to json
42
+ # are not available on remote end of local node invocation/response
43
+ end
@@ -0,0 +1,145 @@
1
+ require 'rjr/dispatcher'
2
+ require 'rjr/message'
3
+
4
+ describe RJR::RequestMessage do
5
+ it "should accept request parameters" do
6
+ msg = RJR::RequestMessage.new :method => 'test',
7
+ :args => ['a', 1],
8
+ :headers => {:h => 2}
9
+ msg.jr_method.should == "test"
10
+ msg.jr_args.should =~ ['a', 1]
11
+ msg.headers.should have_key(:h)
12
+ msg.headers[:h].should == 2
13
+ msg.msg_id.should =~ /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/
14
+ end
15
+
16
+ it "should produce valid json" do
17
+ msg = RJR::RequestMessage.new :method => 'test',
18
+ :args => ['a', 1],
19
+ :headers => {:h => 2}
20
+
21
+ msg_string = msg.to_s
22
+ msg_string.should include('"h":2')
23
+ msg_string.should include('"method":"test"')
24
+ msg_string.should include('"params":["a",1]')
25
+ msg_string.should include('"jsonrpc":"2.0"')
26
+ msg_string.should include('"id":"'+msg.msg_id+'"')
27
+ end
28
+
29
+ it "should parse request message string" do
30
+ msg_uuid = RJR::RequestMessage.gen_uuid
31
+ msg_string = '{"jsonrpc": "2.0", ' +
32
+ '"id": "' + msg_uuid + '", ' +
33
+ '"method": "test", "params": ["a", 1]}'
34
+ msg = RJR::RequestMessage.new :message => msg_string
35
+ msg.json_message.should == msg_string
36
+ msg.jr_method.should == 'test'
37
+ msg.jr_args.should =~ ['a', 1]
38
+ msg.msg_id.should == msg_uuid
39
+ end
40
+
41
+ it "should extract optional headers from message string" do
42
+ msg_uuid = RJR::RequestMessage.gen_uuid
43
+ msg_string = '{"jsonrpc": "2.0", ' +
44
+ '"id": "' + msg_uuid + '", ' +
45
+ '"method": "test", "params": ["a", 1], ' +
46
+ '"h": 2}'
47
+ msg = RJR::RequestMessage.new :message => msg_string, :headers => {'f' => 'g'}
48
+ msg.json_message.should == msg_string
49
+ msg.headers.should have_key 'h'
50
+ msg.headers.should have_key 'f'
51
+ msg.headers['h'].should == 2
52
+ msg.headers['f'].should == 'g'
53
+ end
54
+
55
+ it "should fail if parsing invalid message string" do
56
+ lambda {
57
+ msg = RJR::RequestMessage.new :message => 'foobar'
58
+ }.should raise_error JSON::ParserError
59
+ end
60
+ end
61
+
62
+ describe RJR::ResponseMessage do
63
+ it "should accept response parameters" do
64
+ msg_id = RJR::RequestMessage.gen_uuid
65
+ msg = RJR::ResponseMessage.new :id => msg_id,
66
+ :result => RJR::Result.new(:result => 'success'),
67
+ :headers => {:h => 2}
68
+ msg.msg_id.should == msg_id
69
+ msg.result.result == 'success'
70
+ msg.headers.should have_key(:h)
71
+ msg.headers[:h].should == 2
72
+ end
73
+
74
+ it "should produce valid result response json" do
75
+ msg_id = RJR::RequestMessage.gen_uuid
76
+ msg = RJR::ResponseMessage.new :id => msg_id,
77
+ :result => RJR::Result.new(:result => 'success'),
78
+ :headers => {:h => 2}
79
+ msg_string = msg.to_s
80
+ msg_string.should include('"id":"'+msg_id+'"')
81
+ msg_string.should include('"result":"success"')
82
+ msg_string.should include('"h":2')
83
+ msg_string.should include('"jsonrpc":"2.0"')
84
+ end
85
+
86
+ it "should produce valid error response json" do
87
+ msg_id = RJR::RequestMessage.gen_uuid
88
+ msg = RJR::ResponseMessage.new :id => msg_id,
89
+ :result => RJR::Result.new(:error_code => 404,
90
+ :error_msg => 'not found',
91
+ :error_class => ArgumentError),
92
+ :headers => {:h => 2}
93
+ msg_string = msg.to_s
94
+ msg_string.should include('"id":"'+msg_id+'"')
95
+ msg_string.should include('"h":2')
96
+ msg_string.should include('"jsonrpc":"2.0"')
97
+ msg_string.should include('"error":{')
98
+ msg_string.should include('"code":404')
99
+ msg_string.should include('"message":"not found"')
100
+ end
101
+
102
+
103
+ it "should parse result response message string" do
104
+ msg_id = RJR::RequestMessage.gen_uuid
105
+ msg_string = '{"id":"' + msg_id + '", ' +
106
+ '"result":"success","jsonrpc":"2.0"}'
107
+ msg = RJR::ResponseMessage.new :message => msg_string
108
+ msg.json_message.should == msg_string
109
+ msg.msg_id.should == msg_id
110
+ msg.result.success.should == true
111
+ msg.result.failed.should == false
112
+ msg.result.result.should == "success"
113
+ end
114
+
115
+ it "should parse error response message string" do
116
+
117
+ msg_id = RJR::RequestMessage.gen_uuid
118
+ msg_string = '{"id":"' + msg_id + '", ' +
119
+ '"error":{"code":404,"message":"not found","class":"ArgumentError"}, "jsonrpc":"2.0"}'
120
+ msg = RJR::ResponseMessage.new :message => msg_string
121
+ msg.json_message.should == msg_string
122
+ msg.msg_id.should == msg_id
123
+ msg.result.success.should == false
124
+ msg.result.failed.should == true
125
+ msg.result.error_code.should == 404
126
+ msg.result.error_msg.should == "not found"
127
+ msg.result.error_class.should == 'ArgumentError'
128
+ end
129
+
130
+ it "should extract optional headers from message string" do
131
+ msg_id = RJR::RequestMessage.gen_uuid
132
+ msg_string = '{"id":"' + msg_id + '", ' +
133
+ '"result":"success","h":2,"jsonrpc":"2.0"}'
134
+ msg = RJR::ResponseMessage.new :message => msg_string
135
+ msg.json_message.should == msg_string
136
+ msg.headers.should have_key 'h'
137
+ msg.headers['h'].should == 2
138
+ end
139
+
140
+ it "should fail if parsing invalid message string" do
141
+ lambda {
142
+ msg = RJR::ResponseMessage.new :message => 'foobar'
143
+ }.should raise_error JSON::ParserError
144
+ end
145
+ end
@@ -0,0 +1,44 @@
1
+ require 'rjr/multi_node'
2
+ require 'rjr/amqp_node'
3
+ require 'rjr/web_node'
4
+ require 'rjr/dispatcher'
5
+
6
+ describe RJR::AMQPNode do
7
+ it "should invoke and satisfy amqp requests" do
8
+ foolbar_invoked = false
9
+ barfoo_invoked = false
10
+ RJR::Dispatcher.init_handlers
11
+ RJR::Dispatcher.add_handler('foolbar') { |param|
12
+ @rjr_node_id.should == 'amqp'
13
+ @rjr_node_type.should == :amqp
14
+ param.should == 'myparam1'
15
+ foolbar_invoked = true
16
+ 'retval1'
17
+ }
18
+ RJR::Dispatcher.add_handler('barfoo') { |param|
19
+ @rjr_node_id.should == 'web'
20
+ @rjr_node_type.should == :web
21
+ param.should == 'myparam2'
22
+ barfoo_invoked = true
23
+ 'retval2'
24
+ }
25
+
26
+ amqp = RJR::AMQPNode.new :node_id => 'amqp', :broker => 'localhost'
27
+ web = RJR::WebNode.new :node_id => 'web', :host => 'localhost', :port => 9876
28
+ multi = RJR::MultiNode.new :node_id => 'multi', :nodes => [amqp, web]
29
+
30
+ multi.listen
31
+ amqp_client = RJR::AMQPNode.new :node_id => 'client', :broker => 'localhost'
32
+ res = amqp_client.invoke_request 'amqp-queue', 'foolbar', 'myparam1'
33
+ res.should == 'retval1'
34
+
35
+ web_client = RJR::WebNode.new
36
+ res = web_client.invoke_request 'http://localhost:9876', 'barfoo', 'myparam2'
37
+ res.should == 'retval2'
38
+
39
+ multi.halt
40
+ multi.join
41
+ foolbar_invoked.should == true
42
+ barfoo_invoked.should == true
43
+ end
44
+ end
@@ -0,0 +1,33 @@
1
+ require 'rjr/node'
2
+
3
+ describe RJR::Node do
4
+ it "should initialize properly from params" do
5
+ node = RJR::Node.new :node_id => 'foobar',
6
+ :headers => {:h => 123}
7
+ node.node_id.should == 'foobar'
8
+ node.message_headers[:h].should == 123
9
+ end
10
+
11
+ it "should start eventmachine and allow multiple blocks to be invoked in its context" do
12
+ block1_called = false
13
+ block2_called = false
14
+
15
+ node = RJR::Node.new :node_id => 'foobar',
16
+ :headers => {:h => 123}
17
+ node.em_run {
18
+ node.instance_variable_get(:@thread_pool).running?.should be_true
19
+
20
+ EventMachine.reactor_running?.should be_true
21
+ block1_called = true
22
+ node.em_run {
23
+ EventMachine.reactor_running?.should be_true
24
+ block2_called = true
25
+ node.halt
26
+ }
27
+ }
28
+ node.join
29
+
30
+ block1_called.should be_true
31
+ block2_called.should be_true
32
+ end
33
+ end
@@ -0,0 +1,31 @@
1
+ require 'rjr/web_node'
2
+ require 'rjr/dispatcher'
3
+
4
+ describe RJR::WebNode do
5
+ it "should invoke and satisfy http requests" do
6
+ server = RJR::WebNode.new :node_id => 'www', :host => 'localhost', :port => 9678
7
+ client = RJR::WebNode.new
8
+
9
+ foobar_invoked = false
10
+ RJR::Dispatcher.init_handlers
11
+ RJR::Dispatcher.add_handler('foobar') { |param|
12
+ @client_ip.should == "127.0.0.1"
13
+ #@client_port.should == 9678
14
+ @rjr_node.should == server
15
+ @rjr_node_id.should == 'www'
16
+ @rjr_node_type.should == :web
17
+ param.should == 'myparam'
18
+ foobar_invoked = true
19
+ 'retval'
20
+ }
21
+
22
+ server.listen
23
+
24
+ res = client.invoke_request 'http://localhost:9678', 'foobar', 'myparam'
25
+ res.should == 'retval'
26
+ server.halt
27
+
28
+ server.join
29
+ foobar_invoked.should == true
30
+ end
31
+ end
@@ -0,0 +1,32 @@
1
+ require 'rjr/ws_node'
2
+ require 'rjr/dispatcher'
3
+
4
+ describe RJR::WSNode do
5
+ it "should invoke and satisfy websocket requests" do
6
+ server = RJR::WSNode.new :node_id => 'ws', :host => 'localhost', :port => 9876
7
+ client = RJR::WSNode.new
8
+
9
+ foobar_invoked = false
10
+ RJR::Dispatcher.init_handlers
11
+ RJR::Dispatcher.add_handler('foobar') { |param|
12
+ @client_ip.should == "127.0.0.1"
13
+ #@client_port.should == 9678
14
+ @rjr_node.should == server
15
+ @rjr_node_id.should == 'ws'
16
+ @rjr_node_type.should == :websockets
17
+ param.should == 'myparam'
18
+ foobar_invoked = true
19
+ 'retval'
20
+ }
21
+
22
+ server.listen
23
+ sleep 1
24
+ res = client.invoke_request 'ws://localhost:9876', 'foobar', 'myparam'
25
+ res.should == 'retval'
26
+ server.halt
27
+ server.join
28
+ foobar_invoked.should == true
29
+ end
30
+
31
+ # TODO ensure closed / error event handlers are invoked
32
+ end
metadata CHANGED
@@ -1,47 +1,39 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: rjr
3
- version: !ruby/object:Gem::Version
4
- hash: 3
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.6.1
5
5
  prerelease:
6
- segments:
7
- - 0
8
- - 5
9
- - 4
10
- version: 0.5.4
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Mohammed Morsi
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2012-04-25 00:00:00 Z
19
- dependencies:
20
- - !ruby/object:Gem::Dependency
12
+ date: 2012-04-25 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
21
15
  name: rspec
22
- prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
24
17
  none: false
25
- requirements:
18
+ requirements:
26
19
  - - ~>
27
- - !ruby/object:Gem::Version
28
- hash: 27
29
- segments:
30
- - 1
31
- - 3
32
- - 0
20
+ - !ruby/object:Gem::Version
33
21
  version: 1.3.0
34
22
  type: :development
35
- version_requirements: *id001
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.3.0
36
30
  description: Ruby Json Rpc library
37
31
  email: mo@morsi.org
38
- executables:
32
+ executables:
39
33
  - rjr-server
40
34
  extensions: []
41
-
42
35
  extra_rdoc_files: []
43
-
44
- files:
36
+ files:
45
37
  - lib/rjr/semaphore.rb
46
38
  - lib/rjr/udp_node.rb
47
39
  - lib/rjr/errors.rb
@@ -58,46 +50,40 @@ files:
58
50
  - lib/rjr/dispatcher.rb
59
51
  - lib/rjr/amqp_node.rb
60
52
  - lib/rjr.rb
53
+ - specs/dispatcher_spec.rb
54
+ - specs/node_spec.rb
55
+ - specs/amqp_node_spec.rb
56
+ - specs/multi_node_spec.rb
57
+ - specs/local_node_spec.rb
58
+ - specs/web_node_spec.rb
59
+ - specs/ws_node_spec.rb
60
+ - specs/message_spec.rb
61
61
  - LICENSE
62
62
  - Rakefile
63
63
  - README.rdoc
64
64
  - bin/rjr-server
65
65
  homepage: http://github.com/movitto/rjr
66
66
  licenses: []
67
-
68
67
  post_install_message:
69
68
  rdoc_options: []
70
-
71
- require_paths:
69
+ require_paths:
72
70
  - lib
73
- required_ruby_version: !ruby/object:Gem::Requirement
71
+ required_ruby_version: !ruby/object:Gem::Requirement
74
72
  none: false
75
- requirements:
76
- - - ">="
77
- - !ruby/object:Gem::Version
78
- hash: 53
79
- segments:
80
- - 1
81
- - 8
82
- - 1
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
83
76
  version: 1.8.1
84
- required_rubygems_version: !ruby/object:Gem::Requirement
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
78
  none: false
86
- requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- hash: 29
90
- segments:
91
- - 1
92
- - 3
93
- - 3
79
+ requirements:
80
+ - - ! '>='
81
+ - !ruby/object:Gem::Version
94
82
  version: 1.3.3
95
83
  requirements: []
96
-
97
84
  rubyforge_project:
98
- rubygems_version: 1.7.2
85
+ rubygems_version: 1.8.24
99
86
  signing_key:
100
87
  specification_version: 3
101
- summary: JSON RPC server and client library over amqp, websockets
88
+ summary: JSON RPC server and client library over amqp, websockets, http, etc
102
89
  test_files: []
103
-