rjr 0.9.0 → 0.11.7
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/bin/rjr-client +150 -0
- data/bin/rjr-client-launcher +38 -0
- data/bin/rjr-server +54 -32
- data/lib/rjr/amqp_node.rb +95 -37
- data/lib/rjr/common.rb +60 -10
- data/lib/rjr/dispatcher.rb +98 -16
- data/lib/rjr/em_adapter.rb +110 -0
- data/lib/rjr/inspect.rb +66 -0
- data/lib/rjr/local_node.rb +1 -0
- data/lib/rjr/message.rb +123 -3
- data/lib/rjr/missing_node.rb +17 -0
- data/lib/rjr/multi_node.rb +3 -0
- data/lib/rjr/node.rb +79 -67
- data/lib/rjr/tcp_node.rb +146 -53
- data/lib/rjr/tcp_node2.rb +4 -0
- data/lib/rjr/thread_pool2.rb +271 -0
- data/lib/rjr/util.rb +104 -0
- data/lib/rjr/web_node.rb +115 -23
- data/lib/rjr/ws_node.rb +162 -34
- data/lib/rjr.rb +5 -10
- data/specs/dispatcher_spec.rb +81 -0
- data/specs/em_adapter_spec.rb +85 -0
- data/specs/inspect_spec.rb +60 -0
- data/specs/message_spec.rb +58 -0
- data/specs/multi_node_spec.rb +5 -4
- data/specs/node_spec.rb +140 -4
- data/specs/tcp_node_spec.rb +1 -0
- data/specs/thread_pool_spec.rb +41 -0
- data/specs/util_spec.rb +46 -0
- data/specs/web_node_spec.rb +1 -0
- data/specs/ws_node_spec.rb +1 -1
- metadata +24 -8
- data/lib/rjr/web_socket.rb +0 -589
data/lib/rjr/ws_node.rb
CHANGED
@@ -5,11 +5,25 @@
|
|
5
5
|
# Copyright (C) 2012 Mohammed Morsi <mo@morsi.org>
|
6
6
|
# Licensed under the Apache License, Version 2.0
|
7
7
|
|
8
|
+
skip_module = false
|
9
|
+
begin
|
8
10
|
require 'em-websocket'
|
9
|
-
require '
|
11
|
+
require 'em-websocket-client'
|
12
|
+
rescue LoadError
|
13
|
+
skip_module = true
|
14
|
+
end
|
15
|
+
|
16
|
+
if skip_module
|
17
|
+
# TODO output: "ws dependencies could not be loaded, skipping ws node definition"
|
18
|
+
require 'rjr/missing_node'
|
19
|
+
RJR::WSNode = RJR::MissingNode
|
10
20
|
|
21
|
+
else
|
22
|
+
require 'socket'
|
11
23
|
require 'rjr/node'
|
12
24
|
require 'rjr/message'
|
25
|
+
require 'rjr/dispatcher'
|
26
|
+
require 'rjr/thread_pool2'
|
13
27
|
|
14
28
|
module RJR
|
15
29
|
|
@@ -31,8 +45,7 @@ class WSNodeCallback
|
|
31
45
|
|
32
46
|
# Implementation of {RJR::NodeCallback#invoke}
|
33
47
|
def invoke(callback_method, *data)
|
34
|
-
|
35
|
-
msg = RequestMessage.new :method => callback_method, :args => data, :headers => @message_headers
|
48
|
+
msg = NotificationMessage.new :method => callback_method, :args => data, :headers => @message_headers
|
36
49
|
raise RJR::Errors::ConnectionError.new("websocket closed") if @socket.state == :closed
|
37
50
|
# TODO surround w/ begin/rescue block incase of other socket errors?
|
38
51
|
@socket.send(msg.to_s)
|
@@ -62,17 +75,57 @@ end
|
|
62
75
|
# puts client.invoke_request('ws://localhost:7777', 'hello', 'mo')
|
63
76
|
#
|
64
77
|
class WSNode < RJR::Node
|
65
|
-
RJR_NODE_TYPE = :
|
78
|
+
RJR_NODE_TYPE = :ws
|
66
79
|
|
67
80
|
private
|
68
|
-
|
69
|
-
|
81
|
+
|
82
|
+
# Internal helper initialize new connection
|
83
|
+
def init_node(uri, &on_init)
|
84
|
+
connection = nil
|
85
|
+
@connections_lock.synchronize {
|
86
|
+
connection = @connections.find { |c|
|
87
|
+
c.url == uri
|
88
|
+
}
|
89
|
+
if connection.nil?
|
90
|
+
connection = EventMachine::WebSocketClient.connect(uri)
|
91
|
+
connection.callback do
|
92
|
+
on_init.call(connection)
|
93
|
+
end
|
94
|
+
@connections << connection
|
95
|
+
# TODO sleep until connected?
|
96
|
+
else
|
97
|
+
on_init.call(connection)
|
98
|
+
end
|
99
|
+
}
|
100
|
+
connection
|
101
|
+
end
|
102
|
+
|
103
|
+
# Internal helper handle messages
|
104
|
+
def handle_msg(endpoint, msg)
|
105
|
+
# TODO use messageutil incase of large messages?
|
106
|
+
if RequestMessage.is_request_message?(msg)
|
107
|
+
ThreadPool2Manager << ThreadPool2Job.new { handle_request(endpoint, msg, false) }
|
108
|
+
|
109
|
+
elsif NotificationMessage.is_notification_message?(msg)
|
110
|
+
ThreadPool2Manager << ThreadPool2Job.new { handle_request(endpoint, msg, true) }
|
111
|
+
|
112
|
+
elsif ResponseMessage.is_response_message?(msg)
|
113
|
+
handle_response(msg)
|
114
|
+
|
115
|
+
end
|
70
116
|
end
|
71
117
|
|
72
118
|
# Internal helper, handle request message received
|
73
|
-
def handle_request(
|
74
|
-
|
75
|
-
|
119
|
+
def handle_request(endpoint, message, notification=false)
|
120
|
+
# XXX hack to handle client disconnection (should grap port/ip immediately on connection and use that)
|
121
|
+
client_port,client_ip = nil,nil
|
122
|
+
begin
|
123
|
+
client_port, client_ip = Socket.unpack_sockaddr_in(endpoint.get_peername)
|
124
|
+
rescue Exception=>e
|
125
|
+
end
|
126
|
+
|
127
|
+
msg = notification ? NotificationMessage.new(:message => message, :headers => @message_headers) :
|
128
|
+
RequestMessage.new(:message => message, :headers => @message_headers)
|
76
129
|
headers = @message_headers.merge(msg.headers)
|
77
130
|
result = Dispatcher.dispatch_request(msg.jr_method,
|
78
131
|
:method_args => msg.jr_args,
|
@@ -83,12 +136,53 @@ class WSNode < RJR::Node
|
|
83
136
|
:rjr_node_id => @node_id,
|
84
137
|
:rjr_node_type => RJR_NODE_TYPE,
|
85
138
|
:rjr_callback =>
|
86
|
-
WSNodeCallback.new(:socket =>
|
139
|
+
WSNodeCallback.new(:socket => endpoint,
|
87
140
|
:headers => headers))
|
88
|
-
|
89
|
-
|
141
|
+
unless notification
|
142
|
+
response = ResponseMessage.new(:id => msg.msg_id, :result => result, :headers => headers)
|
143
|
+
endpoint.send(response.to_s)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Internal helper, handle response message received
|
148
|
+
def handle_response(data)
|
149
|
+
msg = ResponseMessage.new(:message => data, :headers => @message_headers)
|
150
|
+
res = err = nil
|
151
|
+
begin
|
152
|
+
res = Dispatcher.handle_response(msg.result)
|
153
|
+
rescue Exception => e
|
154
|
+
err = e
|
155
|
+
end
|
156
|
+
|
157
|
+
@response_lock.synchronize {
|
158
|
+
result = [msg.msg_id, res]
|
159
|
+
result << err if !err.nil?
|
160
|
+
@responses << result
|
161
|
+
@response_cv.signal
|
162
|
+
}
|
163
|
+
end
|
164
|
+
|
165
|
+
# Internal helper, block until response matching message id is received
|
166
|
+
def wait_for_result(message)
|
167
|
+
res = nil
|
168
|
+
while res.nil?
|
169
|
+
@response_lock.synchronize{
|
170
|
+
# FIXME throw err if more than 1 match found
|
171
|
+
res = @responses.select { |response| message.msg_id == response.first }.first
|
172
|
+
if !res.nil?
|
173
|
+
@responses.delete(res)
|
174
|
+
|
175
|
+
else
|
176
|
+
@response_cv.signal
|
177
|
+
@response_cv.wait @response_lock
|
178
|
+
|
179
|
+
end
|
180
|
+
}
|
181
|
+
end
|
182
|
+
return res
|
90
183
|
end
|
91
184
|
|
185
|
+
|
92
186
|
public
|
93
187
|
# WSNode initializer
|
94
188
|
# @param [Hash] args the options to create the web socket node with
|
@@ -99,6 +193,13 @@ class WSNode < RJR::Node
|
|
99
193
|
@host = args[:host]
|
100
194
|
@port = args[:port]
|
101
195
|
|
196
|
+
@connections = []
|
197
|
+
@connections_lock = Mutex.new
|
198
|
+
|
199
|
+
@response_lock = Mutex.new
|
200
|
+
@response_cv = ConditionVariable.new
|
201
|
+
@responses = []
|
202
|
+
|
102
203
|
@connection_event_handlers = {:closed => [], :error => []}
|
103
204
|
end
|
104
205
|
|
@@ -117,22 +218,11 @@ class WSNode < RJR::Node
|
|
117
218
|
# Implementation of {RJR::Node#listen}
|
118
219
|
def listen
|
119
220
|
em_run do
|
120
|
-
init_node
|
121
221
|
EventMachine::WebSocket.start(:host => @host, :port => @port) do |ws|
|
122
|
-
ws.onopen {}
|
123
|
-
ws.onclose {
|
124
|
-
|
125
|
-
|
126
|
-
}
|
127
|
-
}
|
128
|
-
ws.onerror {|e|
|
129
|
-
@connection_event_handlers[:error].each { |h|
|
130
|
-
h.call self
|
131
|
-
}
|
132
|
-
}
|
133
|
-
ws.onmessage { |msg|
|
134
|
-
@thread_pool << ThreadPoolJob.new { handle_request(ws, msg) }
|
135
|
-
}
|
222
|
+
ws.onopen { }
|
223
|
+
ws.onclose { @connection_event_handlers[:closed].each { |h| h.call self } }
|
224
|
+
ws.onerror { |e| @connection_event_handlers[:error].each { |h| h.call self } }
|
225
|
+
ws.onmessage { |msg| handle_msg(ws, msg) }
|
136
226
|
end
|
137
227
|
end
|
138
228
|
end
|
@@ -143,17 +233,55 @@ class WSNode < RJR::Node
|
|
143
233
|
# @param [String] rpc_method json-rpc method to invoke on destination
|
144
234
|
# @param [Array] args array of arguments to convert to json and invoke remote method wtih
|
145
235
|
def invoke_request(uri, rpc_method, *args)
|
146
|
-
init_node
|
147
236
|
message = RequestMessage.new :method => rpc_method,
|
148
237
|
:args => args,
|
149
238
|
:headers => @message_headers
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
239
|
+
|
240
|
+
em_run{
|
241
|
+
init_node(uri) do |c|
|
242
|
+
c.stream { |msg| handle_msg(c, msg) }
|
243
|
+
|
244
|
+
c.send_msg message.to_s
|
245
|
+
end
|
246
|
+
}
|
247
|
+
|
248
|
+
# TODO optional timeout for response ?
|
249
|
+
result = wait_for_result(message)
|
250
|
+
|
251
|
+
if result.size > 2
|
252
|
+
raise Exception, result[2]
|
253
|
+
end
|
254
|
+
return result[1]
|
255
|
+
end
|
256
|
+
|
257
|
+
# Instructs node to send rpc notification (immadiately returns / no response is generated)
|
258
|
+
#
|
259
|
+
# @param [String] uri location of node to send notification to, should be
|
260
|
+
# in format of ws://hostname:port
|
261
|
+
# @param [String] rpc_method json-rpc method to invoke on destination
|
262
|
+
# @param [Array] args array of arguments to convert to json and invoke remote method wtih
|
263
|
+
def send_notification(uri, rpc_method, *args)
|
264
|
+
# will block until message is published
|
265
|
+
published_l = Mutex.new
|
266
|
+
published_c = ConditionVariable.new
|
267
|
+
|
268
|
+
message = NotificationMessage.new :method => rpc_method,
|
269
|
+
:args => args,
|
270
|
+
:headers => @message_headers
|
271
|
+
em_run{
|
272
|
+
init_node(uri) do |c|
|
273
|
+
c.send_msg message.to_s
|
274
|
+
|
275
|
+
# XXX same bug w/ tcp node, due to nature of event machine
|
276
|
+
# we aren't guaranteed that message is actually written to socket
|
277
|
+
# here, process must be kept alive until data is sent or will be lost
|
278
|
+
published_l.synchronize { published_c.signal }
|
279
|
+
end
|
280
|
+
}
|
281
|
+
published_l.synchronize { published_c.wait published_l }
|
282
|
+
nil
|
156
283
|
end
|
157
284
|
end
|
158
285
|
|
159
286
|
end # module RJR
|
287
|
+
end
|
data/lib/rjr.rb
CHANGED
@@ -6,16 +6,11 @@
|
|
6
6
|
# rjr - Ruby Json Rpc
|
7
7
|
module RJR ; end
|
8
8
|
|
9
|
-
require 'rjr/common'
|
10
|
-
require 'rjr/errors'
|
11
|
-
require 'rjr/thread_pool'
|
12
|
-
require 'rjr/semaphore'
|
13
|
-
require 'rjr/node'
|
14
|
-
require 'rjr/dispatcher'
|
15
|
-
require 'rjr/message'
|
16
|
-
require 'rjr/local_node'
|
17
9
|
require 'rjr/amqp_node'
|
18
|
-
require 'rjr/ws_node'
|
19
|
-
require 'rjr/web_node'
|
20
10
|
require 'rjr/tcp_node'
|
11
|
+
require 'rjr/web_node'
|
12
|
+
require 'rjr/ws_node'
|
13
|
+
require 'rjr/local_node'
|
21
14
|
require 'rjr/multi_node'
|
15
|
+
|
16
|
+
require 'rjr/util'
|
data/specs/dispatcher_spec.rb
CHANGED
@@ -49,6 +49,10 @@ end
|
|
49
49
|
|
50
50
|
|
51
51
|
describe RJR::Handler do
|
52
|
+
before(:each) do
|
53
|
+
RJR::DispatcherStat.reset
|
54
|
+
end
|
55
|
+
|
52
56
|
it "should return method not found result if method name is not specified" do
|
53
57
|
handler = RJR::Handler.new :method => nil
|
54
58
|
result = handler.handle
|
@@ -65,6 +69,15 @@ describe RJR::Handler do
|
|
65
69
|
invoked.should == true
|
66
70
|
end
|
67
71
|
|
72
|
+
it "should create dispatcher stat when invoking handler" do
|
73
|
+
handler = RJR::Handler.new :method => 'foobar',
|
74
|
+
:handler => lambda { 42 }
|
75
|
+
handler.handle({:method_args => [] })
|
76
|
+
RJR::DispatcherStat.stats.size.should == 1
|
77
|
+
RJR::DispatcherStat.stats.first.request.method.should == 'foobar'
|
78
|
+
RJR::DispatcherStat.stats.first.result.result.should == 42
|
79
|
+
end
|
80
|
+
|
68
81
|
it "should return handler's return value in successful result" do
|
69
82
|
retval = Object.new
|
70
83
|
handler = RJR::Handler.new :method => 'foobar',
|
@@ -90,6 +103,59 @@ describe RJR::Handler do
|
|
90
103
|
end
|
91
104
|
end
|
92
105
|
|
106
|
+
describe RJR::DispatcherStat do
|
107
|
+
before(:each) do
|
108
|
+
RJR::DispatcherStat.reset
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should store request and result" do
|
112
|
+
req = RJR::Request.new
|
113
|
+
res = RJR::Result.new
|
114
|
+
stat = RJR::DispatcherStat.new req, res
|
115
|
+
(stat.request == req).should be_true
|
116
|
+
stat.result.should == res
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should track global stats" do
|
120
|
+
req = RJR::Request.new
|
121
|
+
res = RJR::Result.new
|
122
|
+
stat = RJR::DispatcherStat.new req, res
|
123
|
+
|
124
|
+
RJR::DispatcherStat << stat
|
125
|
+
RJR::DispatcherStat.stats.should include(stat)
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should be convertable to json" do
|
129
|
+
req = RJR::Request.new :method => 'foobar', :method_args => [:a, :b],
|
130
|
+
:headers => { :foo => :bar }, :rjr_node_type => :local,
|
131
|
+
:rjr_node_id => :loc1
|
132
|
+
res = RJR::Result.new :result => 42
|
133
|
+
|
134
|
+
stat = RJR::DispatcherStat.new req, res
|
135
|
+
j = stat.to_json()
|
136
|
+
j.should include('"json_class":"RJR::DispatcherStat"')
|
137
|
+
j.should include('"method":"foobar"')
|
138
|
+
j.should include('"method_args":["a","b"]')
|
139
|
+
j.should include('"headers":{"foo":"bar"}')
|
140
|
+
j.should include('"rjr_node_type":"local"')
|
141
|
+
j.should include('"rjr_node_id":"loc1"')
|
142
|
+
j.should include('"result":42')
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should be convertable from json" do
|
146
|
+
j = '{"json_class":"RJR::DispatcherStat","data":{"request":{"method":"foobar","method_args":["a","b"],"headers":{"foo":"bar"},"rjr_node_type":"local","rjr_node_id":"loc1"},"result":{"result":42,"error_code":null,"error_msg":null,"error_class":null}}}'
|
147
|
+
s = JSON.parse(j)
|
148
|
+
|
149
|
+
s.class.should == RJR::DispatcherStat
|
150
|
+
s.request.method.should == 'foobar'
|
151
|
+
s.request.method_args.should == ['a', 'b']
|
152
|
+
s.request.headers.should == { 'foo' => 'bar' }
|
153
|
+
s.request.rjr_node_type.should == 'local'
|
154
|
+
s.request.rjr_node_id.should == 'loc1'
|
155
|
+
s.result.result.should == 42
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
93
159
|
describe RJR::Dispatcher do
|
94
160
|
it "should dispatch request to registered handler" do
|
95
161
|
invoked_foobar = false
|
@@ -111,6 +177,21 @@ describe RJR::Dispatcher do
|
|
111
177
|
invoked_barfoo.should == false
|
112
178
|
end
|
113
179
|
|
180
|
+
it "should allow user to determine registered handlers" do
|
181
|
+
foobar = lambda {}
|
182
|
+
barfoo = lambda {}
|
183
|
+
RJR::Dispatcher.add_handler('foobar', &foobar)
|
184
|
+
RJR::Dispatcher.add_handler('barfoo', &barfoo)
|
185
|
+
|
186
|
+
RJR::Dispatcher.has_handler_for?('foobar').should be_true
|
187
|
+
RJR::Dispatcher.has_handler_for?('barfoo').should be_true
|
188
|
+
RJR::Dispatcher.has_handler_for?('money').should be_false
|
189
|
+
|
190
|
+
RJR::Dispatcher.handler_for('foobar').handler_proc.should == foobar
|
191
|
+
RJR::Dispatcher.handler_for('barfoo').handler_proc.should == barfoo
|
192
|
+
RJR::Dispatcher.handler_for('money').should be_nil
|
193
|
+
end
|
194
|
+
|
114
195
|
it "should allow a single handler to be subscribed to multiple methods" do
|
115
196
|
invoked_handler = 0
|
116
197
|
RJR::Dispatcher.init_handlers
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'rjr/dispatcher'
|
2
|
+
require 'rjr/em_adapter'
|
3
|
+
|
4
|
+
describe EMManager do
|
5
|
+
it "should start and halt the reactor thread" do
|
6
|
+
manager = EMManager.new
|
7
|
+
manager.start
|
8
|
+
EventMachine.reactor_running?.should be_true
|
9
|
+
manager.running?.should be_true
|
10
|
+
manager.instance_variable_get(:@reactor_thread).should_not be_nil
|
11
|
+
rt = manager.instance_variable_get(:@reactor_thread)
|
12
|
+
['sleep', 'run'].should include(manager.instance_variable_get(:@reactor_thread).status)
|
13
|
+
|
14
|
+
manager.start
|
15
|
+
rt2 = manager.instance_variable_get(:@reactor_thread)
|
16
|
+
rt.should == rt2
|
17
|
+
|
18
|
+
manager.halt
|
19
|
+
manager.join
|
20
|
+
EventMachine.reactor_running?.should be_false
|
21
|
+
manager.running?.should be_false
|
22
|
+
manager.instance_variable_get(:@reactor_thread).should be_nil
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should allow the user to schedule jobs" do
|
26
|
+
manager = EMManager.new
|
27
|
+
manager.start
|
28
|
+
manager.running?.should be_true
|
29
|
+
block_ran = false
|
30
|
+
manager.schedule {
|
31
|
+
block_ran = true
|
32
|
+
}
|
33
|
+
sleep 0.5
|
34
|
+
block_ran.should == true
|
35
|
+
|
36
|
+
manager.halt
|
37
|
+
manager.join
|
38
|
+
manager.running?.should be_false
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should allow the user to keep the reactor alive until forcibly stopped" do
|
42
|
+
manager = EMManager.new
|
43
|
+
manager.start
|
44
|
+
manager.running?.should be_true
|
45
|
+
manager.schedule { "foo" }
|
46
|
+
|
47
|
+
manager.running?.should be_true
|
48
|
+
|
49
|
+
# forcibly stop the reactor
|
50
|
+
manager.halt
|
51
|
+
manager.join
|
52
|
+
manager.running?.should be_false
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should allow the user to schedule at job after a specified interval" do
|
56
|
+
manager = EMManager.new
|
57
|
+
manager.start
|
58
|
+
manager.running?.should be_true
|
59
|
+
block_called = false
|
60
|
+
manager.add_timer(1) { block_called = true }
|
61
|
+
sleep 0.5
|
62
|
+
block_called.should == false
|
63
|
+
sleep 1
|
64
|
+
block_called.should == true
|
65
|
+
manager.halt
|
66
|
+
manager.join
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should allow the user to schedule at job repeatidly with a specified interval" do
|
70
|
+
manager = EMManager.new
|
71
|
+
manager.start
|
72
|
+
manager.running?.should be_true
|
73
|
+
times_block_called = 0
|
74
|
+
manager.add_periodic_timer(1) { times_block_called += 1 }
|
75
|
+
sleep 0.6
|
76
|
+
times_block_called.should == 0
|
77
|
+
sleep 0.6
|
78
|
+
times_block_called.should == 1
|
79
|
+
sleep 1.2
|
80
|
+
times_block_called.should == 2
|
81
|
+
|
82
|
+
manager.halt
|
83
|
+
manager.join
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'rjr/inspect'
|
2
|
+
|
3
|
+
describe 'rjr/inspect.rb' do
|
4
|
+
describe "select_stats" do
|
5
|
+
before(:each) do
|
6
|
+
RJR::DispatcherStat.reset
|
7
|
+
|
8
|
+
req1 = RJR::Request.new :rjr_node_type => :local, :method => 'foobar'
|
9
|
+
req2 = RJR::Request.new :rjr_node_type => :tcp, :method => 'barfoo'
|
10
|
+
req3 = RJR::Request.new :rjr_node_type => :amqp, :method => 'foobar'
|
11
|
+
res1 = RJR::Result.new :result => true
|
12
|
+
res2 = RJR::Result.new :error_code => 123
|
13
|
+
res3 = RJR::Result.new :error_code => 123
|
14
|
+
|
15
|
+
@stat1 = RJR::DispatcherStat.new req1, res1
|
16
|
+
@stat2 = RJR::DispatcherStat.new req2, res2
|
17
|
+
@stat3 = RJR::DispatcherStat.new req3, res3
|
18
|
+
RJR::DispatcherStat << @stat1 << @stat2 << @stat3
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should get all dispatcherstats" do
|
22
|
+
stats = select_stats
|
23
|
+
stats.size.should == 3
|
24
|
+
stats.first.should == @stat1
|
25
|
+
stats[1].should == @stat2
|
26
|
+
stats.last.should == @stat3
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should get all dispatcherstats for a node" do
|
30
|
+
stats = select_stats 'on_node', 'local'
|
31
|
+
stats.size.should == 1
|
32
|
+
stats.first.should == @stat1
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should get all dispatcherstats for a method" do
|
36
|
+
stats = select_stats 'for_method', 'foobar'
|
37
|
+
stats.size.should == 2
|
38
|
+
stats.first.should == @stat1
|
39
|
+
stats.last.should == @stat3
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should get all successfull/failed dispatcherstats" do
|
43
|
+
stats = select_stats 'successful'
|
44
|
+
stats.size.should == 1
|
45
|
+
stats.first.should == @stat1
|
46
|
+
|
47
|
+
stats = select_stats 'failed'
|
48
|
+
stats.size.should == 2
|
49
|
+
stats.first.should == @stat2
|
50
|
+
stats.last.should == @stat3
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should get dispatcher stats meeting multiple criteria" do
|
54
|
+
stats = select_stats 'for_method', 'foobar', 'successful'
|
55
|
+
stats.size.should == 1
|
56
|
+
stats.first.should == @stat1
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
data/specs/message_spec.rb
CHANGED
@@ -143,3 +143,61 @@ describe RJR::ResponseMessage do
|
|
143
143
|
}.should raise_error JSON::ParserError
|
144
144
|
end
|
145
145
|
end
|
146
|
+
|
147
|
+
describe RJR::NotificationMessage do
|
148
|
+
it "should accept notification parameters" do
|
149
|
+
msg = RJR::NotificationMessage.new :method => 'test',
|
150
|
+
:args => ['a', 1],
|
151
|
+
:headers => {:h => 2}
|
152
|
+
msg.jr_method.should == "test"
|
153
|
+
msg.jr_args.should =~ ['a', 1]
|
154
|
+
msg.headers.should have_key(:h)
|
155
|
+
msg.headers[:h].should == 2
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should produce valid json" do
|
159
|
+
msg = RJR::NotificationMessage.new :method => 'test',
|
160
|
+
:args => ['a', 1],
|
161
|
+
:headers => {:h => 2}
|
162
|
+
|
163
|
+
msg_string = msg.to_s
|
164
|
+
msg_string.should include('"h":2')
|
165
|
+
msg_string.should include('"method":"test"')
|
166
|
+
msg_string.should include('"params":["a",1]')
|
167
|
+
msg_string.should include('"jsonrpc":"2.0"')
|
168
|
+
msg_string.should_not include('"id":"')
|
169
|
+
end
|
170
|
+
|
171
|
+
it "should parse notification message string" do
|
172
|
+
msg_string = '{"jsonrpc": "2.0", ' +
|
173
|
+
'"method": "test", "params": ["a", 1]}'
|
174
|
+
msg = RJR::NotificationMessage.new :message => msg_string
|
175
|
+
msg.json_message.should == msg_string
|
176
|
+
msg.jr_method.should == 'test'
|
177
|
+
msg.jr_args.should =~ ['a', 1]
|
178
|
+
end
|
179
|
+
|
180
|
+
it "should extract optional headers from message string" do
|
181
|
+
msg_string = '{"jsonrpc": "2.0", ' +
|
182
|
+
'"method": "test", "params": ["a", 1], ' +
|
183
|
+
'"h": 2}'
|
184
|
+
msg = RJR::NotificationMessage.new :message => msg_string, :headers => {'f' => 'g'}
|
185
|
+
msg.json_message.should == msg_string
|
186
|
+
msg.headers.should have_key 'h'
|
187
|
+
msg.headers.should have_key 'f'
|
188
|
+
msg.headers['h'].should == 2
|
189
|
+
msg.headers['f'].should == 'g'
|
190
|
+
end
|
191
|
+
|
192
|
+
it "should fail if parsing invalid message string" do
|
193
|
+
lambda {
|
194
|
+
msg = RJR::NotificationMessage.new :message => 'foobar'
|
195
|
+
}.should raise_error JSON::ParserError
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
describe RJR::MessageUtil do
|
200
|
+
it "should extract json messages out of a message stream" do
|
201
|
+
# TODO!
|
202
|
+
end
|
203
|
+
end
|
data/specs/multi_node_spec.rb
CHANGED
@@ -3,8 +3,8 @@ require 'rjr/amqp_node'
|
|
3
3
|
require 'rjr/web_node'
|
4
4
|
require 'rjr/dispatcher'
|
5
5
|
|
6
|
-
describe RJR::
|
7
|
-
it "should invoke and satisfy
|
6
|
+
describe RJR::MultiNode do
|
7
|
+
it "should invoke and satisfy requests over multiple protocols" do
|
8
8
|
foolbar_invoked = false
|
9
9
|
barfoo_invoked = false
|
10
10
|
RJR::Dispatcher.init_handlers
|
@@ -25,10 +25,11 @@ describe RJR::AMQPNode do
|
|
25
25
|
|
26
26
|
amqp = RJR::AMQPNode.new :node_id => 'amqp', :broker => 'localhost'
|
27
27
|
web = RJR::WebNode.new :node_id => 'web', :host => 'localhost', :port => 9876
|
28
|
-
multi = RJR::MultiNode.new :node_id => 'multi', :nodes => [amqp, web]
|
28
|
+
multi = RJR::MultiNode.new :node_id => 'multi', :nodes => [amqp, web], :keep_alive => true
|
29
29
|
|
30
30
|
multi.listen
|
31
|
-
|
31
|
+
|
32
|
+
amqp_client = RJR::AMQPNode.new :node_id => 'client', :broker => 'localhost', :keep_alive => true # see comment about keepalive in amqp_node_spec
|
32
33
|
res = amqp_client.invoke_request 'amqp-queue', 'foolbar', 'myparam1'
|
33
34
|
res.should == 'retval1'
|
34
35
|
|