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