rjr 0.18.2 → 0.19.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.
- checksums.yaml +4 -4
- data/Rakefile +2 -0
- data/bin/rjr-client +16 -9
- data/bin/rjr-server +2 -1
- data/examples/client.rb +21 -19
- data/examples/server.rb +1 -1
- data/examples/structured_server.rb +1 -0
- data/examples/tcp.rb +1 -0
- data/lib/rjr/common.rb +1 -226
- data/lib/rjr/core_ext.rb +63 -0
- data/lib/rjr/dispatcher.rb +75 -219
- data/lib/rjr/messages.rb +8 -0
- data/lib/rjr/messages/compressed.rb +264 -0
- data/lib/rjr/messages/notification.rb +95 -0
- data/lib/rjr/messages/request.rb +99 -0
- data/lib/rjr/messages/response.rb +128 -0
- data/lib/rjr/node.rb +100 -97
- data/lib/rjr/node_callback.rb +43 -0
- data/lib/rjr/nodes/amqp.rb +12 -11
- data/lib/rjr/nodes/easy.rb +4 -4
- data/lib/rjr/nodes/local.rb +13 -12
- data/lib/rjr/nodes/multi.rb +1 -1
- data/lib/rjr/nodes/tcp.rb +15 -13
- data/lib/rjr/nodes/template.rb +4 -4
- data/lib/rjr/nodes/unix.rb +15 -13
- data/lib/rjr/nodes/web.rb +15 -14
- data/lib/rjr/nodes/ws.rb +12 -11
- data/lib/rjr/request.rb +128 -0
- data/lib/rjr/result.rb +75 -0
- data/lib/rjr/util/args.rb +145 -0
- data/lib/rjr/{em_adapter.rb → util/em_adapter.rb} +0 -0
- data/lib/rjr/util/handles_methods.rb +115 -0
- data/lib/rjr/util/has_messages.rb +50 -0
- data/lib/rjr/{inspect.rb → util/inspect.rb} +1 -1
- data/lib/rjr/util/json_parser.rb +101 -0
- data/lib/rjr/util/logger.rb +128 -0
- data/lib/rjr/{thread_pool.rb → util/thread_pool.rb} +2 -0
- data/lib/rjr/version.rb +1 -1
- data/site/jrw.js +1 -1
- data/specs/args_spec.rb +144 -0
- data/specs/dispatcher_spec.rb +399 -211
- data/specs/em_adapter_spec.rb +31 -18
- data/specs/handles_methods_spec.rb +154 -0
- data/specs/has_messages_spec.rb +54 -0
- data/specs/inspect_spec.rb +1 -1
- data/specs/json_parser_spec.rb +169 -0
- data/specs/messages/notification_spec.rb +59 -0
- data/specs/messages/request_spec.rb +66 -0
- data/specs/messages/response_spec.rb +94 -0
- data/specs/node_callbacks_spec.rb +47 -0
- data/specs/node_spec.rb +465 -56
- data/specs/request_spec.rb +147 -0
- data/specs/result_spec.rb +144 -0
- data/specs/thread_pool_spec.rb +1 -1
- metadata +41 -11
- data/lib/rjr/errors.rb +0 -23
- data/lib/rjr/message.rb +0 -351
- data/lib/rjr/semaphore.rb +0 -58
- data/specs/message_spec.rb +0 -229
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'rjr/messages/notification'
|
2
|
+
|
3
|
+
module RJR
|
4
|
+
module Messages
|
5
|
+
describe Notification do
|
6
|
+
it "should accept notification parameters" do
|
7
|
+
msg = Notification.new :method => 'test',
|
8
|
+
:args => ['a', 1],
|
9
|
+
:headers => {:h => 2}
|
10
|
+
msg.jr_method.should == "test"
|
11
|
+
msg.jr_args.should =~ ['a', 1]
|
12
|
+
msg.headers.should have_key(:h)
|
13
|
+
msg.headers[:h].should == 2
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should return bool indicating if string is a notification msg"
|
17
|
+
|
18
|
+
it "should produce valid json" do
|
19
|
+
msg = Notification.new :method => 'test',
|
20
|
+
:args => ['a', 1],
|
21
|
+
:headers => {:h => 2}
|
22
|
+
|
23
|
+
msg_string = msg.to_s
|
24
|
+
msg_string.should include('"h":2')
|
25
|
+
msg_string.should include('"method":"test"')
|
26
|
+
msg_string.should include('"params":["a",1]')
|
27
|
+
msg_string.should include('"jsonrpc":"2.0"')
|
28
|
+
msg_string.should_not include('"id":"')
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should parse notification message string" do
|
32
|
+
msg_string = '{"jsonrpc": "2.0", ' +
|
33
|
+
'"method": "test", "params": ["a", 1]}'
|
34
|
+
msg = Notification.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
|
+
end
|
39
|
+
|
40
|
+
it "should extract optional headers from message string" do
|
41
|
+
msg_string = '{"jsonrpc": "2.0", ' +
|
42
|
+
'"method": "test", "params": ["a", 1], ' +
|
43
|
+
'"h": 2}'
|
44
|
+
msg = Notification.new :message => msg_string, :headers => {'f' => 'g'}
|
45
|
+
msg.json_message.should == msg_string
|
46
|
+
msg.headers.should have_key 'h'
|
47
|
+
msg.headers.should have_key 'f'
|
48
|
+
msg.headers['h'].should == 2
|
49
|
+
msg.headers['f'].should == 'g'
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should fail if parsing invalid message string" do
|
53
|
+
lambda {
|
54
|
+
msg = Notification.new :message => 'foobar'
|
55
|
+
}.should raise_error JSON::ParserError
|
56
|
+
end
|
57
|
+
end # describe Notification
|
58
|
+
end # module Messages
|
59
|
+
end # module RJR
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'rjr/common'
|
2
|
+
require 'rjr/messages/request'
|
3
|
+
|
4
|
+
module RJR
|
5
|
+
module Messages
|
6
|
+
describe Request do
|
7
|
+
it "should accept request parameters" do
|
8
|
+
msg = Request.new :method => 'test',
|
9
|
+
:args => ['a', 1],
|
10
|
+
:headers => {:h => 2}
|
11
|
+
msg.jr_method.should == "test"
|
12
|
+
msg.jr_args.should =~ ['a', 1]
|
13
|
+
msg.headers.should have_key(:h)
|
14
|
+
msg.headers[:h].should == 2
|
15
|
+
msg.msg_id.should =~ /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should return bool indicating if string is a request msg"
|
19
|
+
|
20
|
+
it "should produce valid json" do
|
21
|
+
msg = Request.new :method => 'test',
|
22
|
+
:args => ['a', 1],
|
23
|
+
:headers => {:h => 2}
|
24
|
+
|
25
|
+
msg_string = msg.to_s
|
26
|
+
msg_string.should include('"h":2')
|
27
|
+
msg_string.should include('"method":"test"')
|
28
|
+
msg_string.should include('"params":["a",1]')
|
29
|
+
msg_string.should include('"jsonrpc":"2.0"')
|
30
|
+
msg_string.should include('"id":"'+msg.msg_id+'"')
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should parse request message string" do
|
34
|
+
msg_uuid = gen_uuid
|
35
|
+
msg_string = '{"jsonrpc": "2.0", ' +
|
36
|
+
'"id": "' + msg_uuid + '", ' +
|
37
|
+
'"method": "test", "params": ["a", 1]}'
|
38
|
+
msg = Request.new :message => msg_string
|
39
|
+
msg.json_message.should == msg_string
|
40
|
+
msg.jr_method.should == 'test'
|
41
|
+
msg.jr_args.should =~ ['a', 1]
|
42
|
+
msg.msg_id.should == msg_uuid
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should extract optional headers from message string" do
|
46
|
+
msg_uuid = gen_uuid
|
47
|
+
msg_string = '{"jsonrpc": "2.0", ' +
|
48
|
+
'"id": "' + msg_uuid + '", ' +
|
49
|
+
'"method": "test", "params": ["a", 1], ' +
|
50
|
+
'"h": 2}'
|
51
|
+
msg = Request.new :message => msg_string, :headers => {'f' => 'g'}
|
52
|
+
msg.json_message.should == msg_string
|
53
|
+
msg.headers.should have_key 'h'
|
54
|
+
msg.headers.should have_key 'f'
|
55
|
+
msg.headers['h'].should == 2
|
56
|
+
msg.headers['f'].should == 'g'
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should fail if parsing invalid message string" do
|
60
|
+
lambda {
|
61
|
+
msg = Request.new :message => 'foobar'
|
62
|
+
}.should raise_error JSON::ParserError
|
63
|
+
end
|
64
|
+
end # describe Request
|
65
|
+
end # module Messages
|
66
|
+
end # module RJR
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'rjr/common'
|
2
|
+
require 'rjr/result'
|
3
|
+
require 'rjr/messages/response'
|
4
|
+
|
5
|
+
module RJR
|
6
|
+
module Messages
|
7
|
+
describe Response do
|
8
|
+
it "should accept response parameters" do
|
9
|
+
msg_id = gen_uuid
|
10
|
+
msg = Response.new :id => msg_id,
|
11
|
+
:result => Result.new(:result => 'success'),
|
12
|
+
:headers => {:h => 2}
|
13
|
+
msg.msg_id.should == msg_id
|
14
|
+
msg.result.result == 'success'
|
15
|
+
msg.headers.should have_key(:h)
|
16
|
+
msg.headers[:h].should == 2
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should return bool indicating if string is a response msg"
|
20
|
+
|
21
|
+
it "should produce valid result response json" do
|
22
|
+
msg_id = gen_uuid
|
23
|
+
msg = Response.new :id => msg_id,
|
24
|
+
:result => RJR::Result.new(:result => 'success'),
|
25
|
+
:headers => {:h => 2}
|
26
|
+
msg_string = msg.to_s
|
27
|
+
msg_string.should include('"id":"'+msg_id+'"')
|
28
|
+
msg_string.should include('"result":"success"')
|
29
|
+
msg_string.should include('"h":2')
|
30
|
+
msg_string.should include('"jsonrpc":"2.0"')
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should produce valid error response json" do
|
34
|
+
msg_id = gen_uuid
|
35
|
+
msg = Response.new :id => msg_id,
|
36
|
+
:result =>
|
37
|
+
RJR::Result.new(:error_code => 404,
|
38
|
+
:error_msg => 'not found',
|
39
|
+
:error_class => ArgumentError),
|
40
|
+
:headers => {:h => 2}
|
41
|
+
msg_string = msg.to_s
|
42
|
+
msg_string.should include('"id":"'+msg_id+'"')
|
43
|
+
msg_string.should include('"h":2')
|
44
|
+
msg_string.should include('"jsonrpc":"2.0"')
|
45
|
+
msg_string.should include('"error":{')
|
46
|
+
msg_string.should include('"code":404')
|
47
|
+
msg_string.should include('"message":"not found"')
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
it "should parse result response message string" do
|
52
|
+
msg_id = gen_uuid
|
53
|
+
msg_string = '{"id":"' + msg_id + '", ' +
|
54
|
+
'"result":"success","jsonrpc":"2.0"}'
|
55
|
+
msg = Response.new :message => msg_string
|
56
|
+
msg.json_message.should == msg_string
|
57
|
+
msg.msg_id.should == msg_id
|
58
|
+
msg.result.success.should == true
|
59
|
+
msg.result.failed.should == false
|
60
|
+
msg.result.result.should == "success"
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should parse error response message string" do
|
64
|
+
msg_id = gen_uuid
|
65
|
+
msg_string = '{"id":"' + msg_id + '", ' +
|
66
|
+
'"error":{"code":404,"message":"not found","class":"ArgumentError"}, "jsonrpc":"2.0"}'
|
67
|
+
msg = Response.new :message => msg_string
|
68
|
+
msg.json_message.should == msg_string
|
69
|
+
msg.msg_id.should == msg_id
|
70
|
+
msg.result.success.should == false
|
71
|
+
msg.result.failed.should == true
|
72
|
+
msg.result.error_code.should == 404
|
73
|
+
msg.result.error_msg.should == "not found"
|
74
|
+
msg.result.error_class.should == 'ArgumentError'
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should extract optional headers from message string" do
|
78
|
+
msg_id = gen_uuid
|
79
|
+
msg_string = '{"id":"' + msg_id + '", ' +
|
80
|
+
'"result":"success","h":2,"jsonrpc":"2.0"}'
|
81
|
+
msg = Response.new :message => msg_string
|
82
|
+
msg.json_message.should == msg_string
|
83
|
+
msg.headers.should have_key 'h'
|
84
|
+
msg.headers['h'].should == 2
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should fail if parsing invalid message string" do
|
88
|
+
lambda {
|
89
|
+
msg = Response.new :message => 'foobar'
|
90
|
+
}.should raise_error JSON::ParserError
|
91
|
+
end
|
92
|
+
end # describe Response
|
93
|
+
end # module Messages
|
94
|
+
end # module RJR
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'rjr/dispatcher'
|
2
|
+
|
3
|
+
require 'rjr/node'
|
4
|
+
require 'rjr/node_callback'
|
5
|
+
|
6
|
+
module RJR
|
7
|
+
describe NodeCallback do
|
8
|
+
describe "#initialize" do
|
9
|
+
it "sets node" do
|
10
|
+
node = Object.new
|
11
|
+
callback = NodeCallback.new :node => node
|
12
|
+
callback.node.should == node
|
13
|
+
end
|
14
|
+
|
15
|
+
it "sets connection" do
|
16
|
+
connection = Object.new
|
17
|
+
callback = NodeCallback.new :connection => connection
|
18
|
+
callback.connection.should == connection
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#notify" do
|
23
|
+
before(:each) do
|
24
|
+
@connection = Object.new
|
25
|
+
@node = Node.new :headers => {'msg' => 'headers'}
|
26
|
+
@callback = NodeCallback.new :node => @node,
|
27
|
+
:connection => @connection
|
28
|
+
end
|
29
|
+
|
30
|
+
context "node is not persistent" do
|
31
|
+
it "just returns / does not send msg" do
|
32
|
+
@node.should_receive(:persistent?).and_return(false)
|
33
|
+
@node.should_not_receive(:send_msg)
|
34
|
+
@callback.notify('')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
it "sends notification via node" do
|
39
|
+
expected = Messages::Notification.new :method => 'method1',
|
40
|
+
:args => ['method', 'args'],
|
41
|
+
:headers => {'msg' => 'headers'}
|
42
|
+
@node.should_not_receive(:send_msg).with(expected.to_s, @connection)
|
43
|
+
@callback.notify('msg')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/specs/node_spec.rb
CHANGED
@@ -3,13 +3,6 @@ require 'rjr/node'
|
|
3
3
|
|
4
4
|
module RJR
|
5
5
|
describe Node do
|
6
|
-
before(:all) do
|
7
|
-
# was made public in ruby 1.9
|
8
|
-
if RUBY_VERSION < "1.9"
|
9
|
-
RJR::Node.public_class_method(:class_variable_get)
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
6
|
describe "::persistent?" do
|
14
7
|
context "PERSISTENT_NODE is defined and true" do
|
15
8
|
it "returns true" do
|
@@ -19,11 +12,19 @@ module RJR
|
|
19
12
|
end
|
20
13
|
end
|
21
14
|
|
22
|
-
context "PERSISTENT_NODE is not defined
|
15
|
+
context "PERSISTENT_NODE is not defined" do
|
23
16
|
it "returns false" do
|
24
17
|
Node.should_not be_persistent
|
25
18
|
end
|
26
19
|
end
|
20
|
+
|
21
|
+
context "PERSISTENT_NODE returns false" do
|
22
|
+
it "returns false" do
|
23
|
+
new_node = Class.new(Node)
|
24
|
+
new_node.const_set(:PERSISTENT_NODE, false)
|
25
|
+
new_node.should_not be_persistent
|
26
|
+
end
|
27
|
+
end
|
27
28
|
end
|
28
29
|
|
29
30
|
describe "#persistent?" do
|
@@ -42,69 +43,477 @@ module RJR
|
|
42
43
|
end
|
43
44
|
end
|
44
45
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
46
|
+
describe "::indirect?" do
|
47
|
+
context "INDIRECT_NODE defined and true" do
|
48
|
+
it "returns true" do
|
49
|
+
new_node = Class.new(Node)
|
50
|
+
new_node.const_set(:INDIRECT_NODE, true)
|
51
|
+
new_node.should be_indirect
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "INDIRECT_NODE not defined" do
|
56
|
+
it "returns false" do
|
57
|
+
Node.should_not be_indirect
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "INDIRECT_NODE false" do
|
62
|
+
it "returns false" do
|
63
|
+
new_node = Class.new(Node)
|
64
|
+
new_node.const_set(:INDIRECT_NODE, false)
|
65
|
+
new_node.should_not be_indirect
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "#indirect?" do
|
71
|
+
context "node is indirect" do
|
72
|
+
it "returns true" do
|
73
|
+
new_node = Class.new(Node)
|
74
|
+
new_node.const_set(:INDIRECT_NODE, true)
|
75
|
+
new_node.new.should be_indirect
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context "node is direct" do
|
80
|
+
it "returns false" do
|
81
|
+
Node.new.should_not be_indirect
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "#node_type" do
|
87
|
+
it "returns NODE_TYPE" do
|
88
|
+
new_node = Class.new(Node)
|
89
|
+
new_node.const_set(:RJR_NODE_TYPE, 'nt')
|
90
|
+
new_node.new.node_type.should == 'nt'
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "#initialize" do
|
95
|
+
it "sets node id" do
|
96
|
+
node = Node.new :node_id => 'node1'
|
97
|
+
node.node_id.should == 'node1'
|
98
|
+
end
|
99
|
+
|
100
|
+
it "sets dispatcher" do
|
101
|
+
dispatcher = Dispatcher.new
|
102
|
+
node = Node.new :dispatcher => dispatcher
|
103
|
+
node.dispatcher.should == dispatcher
|
104
|
+
end
|
105
|
+
|
106
|
+
context "dispatcher not specified" do
|
107
|
+
it "initializes dispatcher" do
|
108
|
+
Node.new.dispatcher.should be_an_instance_of(Dispatcher)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
it "sets message headers" do
|
113
|
+
headers = {:header1 => :val}
|
114
|
+
node = Node.new :headers => headers
|
115
|
+
node.message_headers.should == headers
|
116
|
+
end
|
117
|
+
|
118
|
+
context "headers not specified" do
|
119
|
+
it "initializes blank headers" do
|
120
|
+
Node.new.message_headers.should == {}
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
it "initializes shared thread pool" do
|
125
|
+
Node.new
|
126
|
+
Node.tp.should_not be_nil
|
127
|
+
orig = Node.tp
|
128
|
+
|
129
|
+
Node.new.tp.should == orig
|
130
|
+
Node.tp.should == orig
|
131
|
+
end
|
132
|
+
|
133
|
+
it "initializes shared em adapter" do
|
134
|
+
Node.new
|
135
|
+
Node.em.should_not be_nil
|
136
|
+
orig = Node.em
|
137
|
+
|
138
|
+
Node.new.em.should == orig
|
139
|
+
Node.em.should == orig
|
140
|
+
end
|
141
|
+
|
142
|
+
it "starts thread pool" do
|
143
|
+
tp = ThreadPool.new
|
144
|
+
Node.should_receive(:tp).and_return(tp)
|
145
|
+
tp.should_receive(:start)
|
146
|
+
Node.new
|
147
|
+
end
|
148
|
+
|
149
|
+
it "starts em adapter" do
|
150
|
+
em = EMAdapter.new
|
151
|
+
Node.should_receive(:em).and_return(em)
|
152
|
+
em.should_receive(:start)
|
153
|
+
Node.new
|
154
|
+
end
|
53
155
|
end
|
54
156
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
157
|
+
describe "#join" do
|
158
|
+
it "joins thread pool & em adapter and returns self" do
|
159
|
+
Node.tp.should_receive(:join)
|
160
|
+
Node.em.should_receive(:join)
|
161
|
+
n = Node.new
|
162
|
+
n.join.should == n
|
163
|
+
end
|
59
164
|
end
|
60
165
|
|
61
|
-
|
62
|
-
|
63
|
-
|
166
|
+
describe "#halt" do
|
167
|
+
it "stops thread pool & em adatper and returns self" do
|
168
|
+
Node.tp.should_receive(:stop)
|
169
|
+
Node.em.should_receive(:stop_event_loop)
|
170
|
+
n = Node.new
|
171
|
+
n.halt.should == n
|
172
|
+
end
|
64
173
|
end
|
65
174
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
175
|
+
describe "#clear_event_handlers" do
|
176
|
+
it "resets connection event handlers" do
|
177
|
+
n = Node.new
|
178
|
+
n.on(:error) {}
|
179
|
+
n.clear_event_handlers
|
180
|
+
n.connection_event_handlers.should == {:closed => [], :error => []}
|
181
|
+
end
|
70
182
|
end
|
71
183
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
184
|
+
describe "#on" do
|
185
|
+
it "registers new connection event handler" do
|
186
|
+
bl = proc{}
|
187
|
+
n = Node.new
|
188
|
+
n.on(:error, &bl)
|
189
|
+
n.connection_event_handlers.should == {:closed => [], :error => [bl]}
|
190
|
+
end
|
76
191
|
end
|
77
192
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
193
|
+
describe "#connection_event" do
|
194
|
+
it "invokes event handlers" do
|
195
|
+
bl = proc{}
|
196
|
+
n = Node.new
|
197
|
+
n.on(:error, &bl)
|
198
|
+
bl.should_receive(:call).with(n)
|
199
|
+
n.send :connection_event, :error
|
200
|
+
end
|
82
201
|
end
|
83
202
|
|
84
|
-
|
85
|
-
node
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
203
|
+
describe "#client_for" do
|
204
|
+
context "node is indirect" do
|
205
|
+
it "returns nil" do
|
206
|
+
n = Node.new
|
207
|
+
n.should_receive(:indirect?).and_return(true)
|
208
|
+
n.send(:client_for, 'whatever').should == [nil, nil]
|
209
|
+
end
|
90
210
|
end
|
91
|
-
|
92
|
-
|
211
|
+
|
212
|
+
context "node is local" do
|
213
|
+
it "returns nil" do
|
214
|
+
n = Node.new
|
215
|
+
n.should_receive(:node_type).and_return(:local)
|
216
|
+
n.send(:client_for, 'whatever').should == [nil, nil]
|
217
|
+
end
|
93
218
|
end
|
94
|
-
node.send(:connection_event, :error)
|
95
|
-
error.should be_true
|
96
219
|
|
97
|
-
|
98
|
-
|
220
|
+
it "extracts & returns client ip and port from socket connection" do
|
221
|
+
n = Node.new
|
222
|
+
n.should_receive(:node_type).and_return(:tcp)
|
223
|
+
|
224
|
+
c = Object.new
|
225
|
+
c.should_receive(:get_peername).and_return('peername')
|
226
|
+
expected = ['127.0.0.1', 5000]
|
227
|
+
Socket.should_receive(:unpack_sockaddr_in).
|
228
|
+
with('peername').and_return(expected)
|
229
|
+
n.send(:client_for, c).should == expected
|
230
|
+
end
|
231
|
+
|
232
|
+
context "error during ip/port extraction" do
|
233
|
+
it "returns nil" do
|
234
|
+
n = Node.new
|
235
|
+
n.should_receive(:node_type).and_return(:tcp)
|
236
|
+
|
237
|
+
c = Object.new
|
238
|
+
c.should_receive(:get_peername).and_raise(Exception)
|
239
|
+
n.send(:client_for, c).should == [nil, nil]
|
240
|
+
end
|
241
|
+
end
|
99
242
|
end
|
100
243
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
244
|
+
describe "#handle_message" do
|
245
|
+
context "request message" do
|
246
|
+
it "handles request via thread pool" do
|
247
|
+
connection = Object.new
|
248
|
+
request = Messages::Request.new :id => 1, :method => 'foo'
|
249
|
+
request_str = request.to_s
|
250
|
+
|
251
|
+
node = Node.new
|
252
|
+
node.tp.should_receive(:<<) { |job|
|
253
|
+
job.should be_an_instance_of(ThreadPoolJob)
|
254
|
+
job.exec Mutex.new
|
255
|
+
}
|
256
|
+
|
257
|
+
node.should_receive(:handle_request).with(request_str, false, connection)
|
258
|
+
node.send :handle_message, request_str, connection
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
context "notification message" do
|
263
|
+
it "handles notification via thread pool" do
|
264
|
+
connection = Object.new
|
265
|
+
notification = Messages::Notification.new :method => 'foo'
|
266
|
+
notification_str = notification.to_s
|
106
267
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
268
|
+
node = Node.new
|
269
|
+
node.tp.should_receive(:<<) { |job|
|
270
|
+
job.should be_an_instance_of(ThreadPoolJob)
|
271
|
+
job.exec Mutex.new
|
272
|
+
}
|
273
|
+
|
274
|
+
node.should_receive(:handle_request).
|
275
|
+
with(notification_str, true, connection)
|
276
|
+
node.send :handle_message, notification_str, connection
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
context "response message" do
|
281
|
+
it "handles response" do
|
282
|
+
response = Messages::Response.new :result => Result.new
|
283
|
+
response_str = response.to_s
|
284
|
+
|
285
|
+
node = Node.new
|
286
|
+
node.should_receive(:handle_response).with(response_str)
|
287
|
+
node.send :handle_message, response_str
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
context "anything else" do
|
292
|
+
it "does nothing" do
|
293
|
+
node = Node.new
|
294
|
+
node.tp.should_not_receive(:<<)
|
295
|
+
node.should_not_receive(:handle_request)
|
296
|
+
node.should_not_receive(:handle_response)
|
297
|
+
node.send :handle_message, "{}"
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
describe "#handle_request" do
|
303
|
+
before(:each) do
|
304
|
+
notification =
|
305
|
+
Messages::Notification.new :method => 'rjr_method1',
|
306
|
+
:args => ['method', 'args'],
|
307
|
+
:headers => {'msg' => 'headers'}
|
308
|
+
@notification = notification.to_s
|
309
|
+
@node = Node.new
|
310
|
+
end
|
311
|
+
|
312
|
+
it "invokes dispatcher.dispatch" do
|
313
|
+
@node.dispatcher.should_receive(:dispatch)
|
314
|
+
@node.send :handle_request, @notification, true
|
315
|
+
end
|
316
|
+
|
317
|
+
describe "dispatcher.dispatch args" do
|
318
|
+
it "includes :rjr_method => msg.jr_method" do
|
319
|
+
@node.dispatcher.should_receive(:dispatch) { |args|
|
320
|
+
args[:rjr_method].should == 'rjr_method1'
|
321
|
+
}
|
322
|
+
@node.send :handle_request, @notification, true
|
323
|
+
end
|
324
|
+
|
325
|
+
it "includes :rjr_method_args => msg.jr_args" do
|
326
|
+
@node.dispatcher.should_receive(:dispatch) { |args|
|
327
|
+
args[:rjr_method_args].should == ['method', 'args']
|
328
|
+
}
|
329
|
+
@node.send :handle_request, @notification, true
|
330
|
+
end
|
331
|
+
|
332
|
+
it "includes :rjr_headers => msg.headers" do
|
333
|
+
@node.dispatcher.should_receive(:dispatch) { |args|
|
334
|
+
args[:rjr_headers].should == {'msg' => 'headers'}
|
335
|
+
}
|
336
|
+
@node.send :handle_request, @notification, true
|
337
|
+
end
|
338
|
+
|
339
|
+
it "includes :rjr_client_ip => extracted client ip" do
|
340
|
+
connection = Object.new
|
341
|
+
@node.should_receive(:client_for).with(connection).
|
342
|
+
and_return([9999, '127.0.0.1'])
|
343
|
+
@node.dispatcher.should_receive(:dispatch) { |args|
|
344
|
+
args[:rjr_client_ip].should == '127.0.0.1'
|
345
|
+
}
|
346
|
+
@node.send :handle_request, @notification, true, connection
|
347
|
+
end
|
348
|
+
|
349
|
+
it "includes :rjr_client_port => extracted client port" do
|
350
|
+
connection = Object.new
|
351
|
+
@node.should_receive(:client_for).with(connection).
|
352
|
+
and_return([9999, '127.0.0.1'])
|
353
|
+
@node.dispatcher.should_receive(:dispatch) { |args|
|
354
|
+
args[:rjr_client_port].should == 9999
|
355
|
+
}
|
356
|
+
@node.send :handle_request, @notification, true, connection
|
357
|
+
end
|
358
|
+
|
359
|
+
it "includes :rjr_node => self" do
|
360
|
+
@node.dispatcher.should_receive(:dispatch) { |args|
|
361
|
+
args[:rjr_node].should == @node
|
362
|
+
}
|
363
|
+
@node.send :handle_request, @notification, true
|
364
|
+
end
|
365
|
+
|
366
|
+
it "includes :rjr_node_id => self.node_id" do
|
367
|
+
@node.should_receive(:node_id).and_return('node_id')
|
368
|
+
@node.dispatcher.should_receive(:dispatch) { |args|
|
369
|
+
args[:rjr_node_id].should == 'node_id'
|
370
|
+
}
|
371
|
+
@node.send :handle_request, @notification, true
|
372
|
+
end
|
373
|
+
|
374
|
+
it "includes :rjr_node_type => self.node_type" do
|
375
|
+
@node.should_receive(:node_type).at_least(:once).and_return(:nt)
|
376
|
+
@node.dispatcher.should_receive(:dispatch) { |args|
|
377
|
+
args[:rjr_node_type].should == :nt
|
378
|
+
}
|
379
|
+
@node.send :handle_request, @notification, true
|
380
|
+
end
|
381
|
+
|
382
|
+
it "includes :rjr_callback => constructed node callback" do
|
383
|
+
@node.dispatcher.should_receive(:dispatch) { |args|
|
384
|
+
args[:rjr_callback].should be_an_instance_of(NodeCallback)
|
385
|
+
}
|
386
|
+
@node.send :handle_request, @notification, true
|
387
|
+
end
|
388
|
+
|
389
|
+
describe "specified node callback" do
|
390
|
+
it "has a handle to the node" do
|
391
|
+
@node.dispatcher.should_receive(:dispatch) { |args|
|
392
|
+
args[:rjr_callback].node.should == @node
|
393
|
+
}
|
394
|
+
@node.send :handle_request, @notification, true
|
395
|
+
end
|
396
|
+
|
397
|
+
it "has a handle to the connection" do
|
398
|
+
connection = Object.new
|
399
|
+
@node.dispatcher.should_receive(:dispatch) { |args|
|
400
|
+
args[:rjr_callback].connection.should == connection
|
401
|
+
}
|
402
|
+
@node.send :handle_request, @notification, true, connection
|
403
|
+
end
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
context "handling request / not a notification" do
|
408
|
+
before(:each) do
|
409
|
+
request = Messages::Request.new :method => 'method1',
|
410
|
+
:args => ['method', 'args'],
|
411
|
+
:headers => {'msg' => 'headers'}
|
412
|
+
@request = request.to_s
|
413
|
+
|
414
|
+
result = Result.new
|
415
|
+
@expected = Messages::Response.new :result => result,
|
416
|
+
:headers => {'msg' => 'headers'},
|
417
|
+
:id => request.msg_id
|
418
|
+
|
419
|
+
# stub out dispatch
|
420
|
+
@node.dispatcher.should_receive(:dispatch).and_return(result)
|
421
|
+
end
|
422
|
+
|
423
|
+
it "sends response msg string via connection" do
|
424
|
+
connection = Object.new
|
425
|
+
@node.should_receive(:send_msg) { |response,econnection|
|
426
|
+
response.should == @expected.to_s
|
427
|
+
econnection.should == connection
|
428
|
+
}
|
429
|
+
@node.send :handle_request, @request, false, connection
|
430
|
+
end
|
431
|
+
|
432
|
+
it "returns response" do
|
433
|
+
@node.should_receive(:send_msg) # stub out
|
434
|
+
response = @node.send(:handle_request, @request, false)
|
435
|
+
response.should be_an_instance_of(Messages::Response)
|
436
|
+
response.to_s.should == @expected.to_s
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
it "returns nil" do
|
441
|
+
@node.send(:handle_request, @notification, true).should be_nil
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
describe "#handle_response" do
|
446
|
+
before(:each) do
|
447
|
+
@node = Node.new
|
448
|
+
|
449
|
+
@result = Result.new :result => 42
|
450
|
+
response = Messages::Response.new :id => 'msg1', :result => @result
|
451
|
+
@response = response.to_s
|
452
|
+
end
|
453
|
+
|
454
|
+
it "invokes dispatcher.handle_response with response result" do
|
455
|
+
@node.dispatcher.should_receive(:handle_response) { |r|
|
456
|
+
r.should be_an_instance_of(Result)
|
457
|
+
r.result.should == 42
|
458
|
+
}
|
459
|
+
@node.send :handle_response, @response
|
460
|
+
end
|
461
|
+
|
462
|
+
it "adds response msg_id and result to response queue" do
|
463
|
+
@node.send :handle_response, @response
|
464
|
+
responses = @node.instance_variable_get(:@responses)
|
465
|
+
responses.size.should == 1
|
466
|
+
responses.first[0].should == 'msg1'
|
467
|
+
responses.first[1].should == 42
|
468
|
+
end
|
469
|
+
|
470
|
+
context "response contains error" do
|
471
|
+
it "adds response error to response queue" do
|
472
|
+
@node.dispatcher.should_receive(:handle_response).and_raise(Exception)
|
473
|
+
@node.send :handle_response, @response
|
474
|
+
responses = @node.instance_variable_get(:@responses)
|
475
|
+
responses.first[2].should be_an_instance_of(Exception)
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
it "signals response cv" do
|
480
|
+
@node.instance_variable_get(:@response_cv).should_receive(:broadcast)
|
481
|
+
@node.send :handle_response, @response
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
describe "#wait_for_result" do
|
486
|
+
before(:each) do
|
487
|
+
@node = Node.new
|
488
|
+
@msg = Messages::Request.new :method => 'method1', :args => []
|
489
|
+
@response = [@msg.msg_id, 42]
|
490
|
+
end
|
491
|
+
|
492
|
+
context "response in response queue" do
|
493
|
+
before(:each) do
|
494
|
+
@node.instance_variable_set(:@responses, [@response])
|
495
|
+
end
|
496
|
+
|
497
|
+
it "deletes response from queue" do
|
498
|
+
@node.send :wait_for_result, @msg
|
499
|
+
@node.instance_variable_get(:@responses).should be_empty
|
500
|
+
end
|
501
|
+
|
502
|
+
it "returns response" do
|
503
|
+
@node.send(:wait_for_result, @msg).should == @response
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
507
|
+
context "response not in response queue" do
|
508
|
+
it "waits on response cv" do
|
509
|
+
node = Node.new
|
510
|
+
node.instance_variable_get(:@response_cv).stub(:wait) {
|
511
|
+
node.instance_variable_set(:@responses, [@response])
|
512
|
+
}
|
513
|
+
node.instance_variable_get(:@response_cv).should_receive(:wait).once
|
514
|
+
node.send(:wait_for_result, @msg)
|
515
|
+
end
|
516
|
+
end
|
517
|
+
end
|
518
|
+
end # describe Node
|
519
|
+
end # module RJR
|