rjr 0.5.4 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Rakefile +3 -29
- data/lib/rjr/amqp_node.rb +92 -57
- data/lib/rjr/common.rb +13 -1
- data/lib/rjr/dispatcher.rb +10 -6
- data/lib/rjr/local_node.rb +24 -3
- data/lib/rjr/message.rb +5 -3
- data/lib/rjr/node.rb +8 -6
- data/lib/rjr/thread_pool.rb +1 -1
- data/lib/rjr/web_node.rb +10 -1
- data/lib/rjr/ws_node.rb +24 -8
- data/lib/rjr.rb +12 -15
- data/specs/amqp_node_spec.rb +31 -0
- data/specs/dispatcher_spec.rb +144 -0
- data/specs/local_node_spec.rb +43 -0
- data/specs/message_spec.rb +145 -0
- data/specs/multi_node_spec.rb +44 -0
- data/specs/node_spec.rb +33 -0
- data/specs/web_node_spec.rb +31 -0
- data/specs/ws_node_spec.rb +32 -0
- metadata +38 -52
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
|
-
|
41
|
-
|
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
|
-
@
|
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
|
-
|
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
|
-
|
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(:
|
81
|
-
:
|
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
|
-
|
86
|
-
|
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
|
-
|
98
|
-
|
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
|
-
|
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
|
-
|
124
|
-
|
125
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
@@
|
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
|
data/lib/rjr/dispatcher.rb
CHANGED
@@ -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(
|
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
|
-
|
138
|
-
|
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
|
data/lib/rjr/local_node.rb
CHANGED
@@ -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
|
-
|
54
|
-
|
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
|
-
|
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)
|
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.
|
72
|
+
EventMachine.stop_event_loop
|
71
73
|
@thread_pool.stop
|
72
74
|
end
|
73
75
|
end
|
data/lib/rjr/thread_pool.rb
CHANGED
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
|
-
|
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
|
-
|
7
|
-
|
8
|
-
|
9
|
-
require
|
10
|
-
require
|
11
|
-
require
|
12
|
-
require
|
13
|
-
require
|
14
|
-
require
|
15
|
-
require
|
16
|
-
require
|
17
|
-
require
|
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
|
data/specs/node_spec.rb
ADDED
@@ -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
|
-
|
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
|
-
|
19
|
-
|
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
|
-
|
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
|
-
|
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.
|
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
|
-
|