ione-rpc 1.0.0.pre0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,153 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+
6
+ module Ione
7
+ module Rpc
8
+ describe Codec do
9
+ let :codec do
10
+ CodecSpec::JsonCodec.new
11
+ end
12
+
13
+ describe '#encode' do
14
+ let :object do
15
+ {'foo' => 'bar', 'baz' => 42}
16
+ end
17
+
18
+ let :encoded_message do
19
+ codec.encode(object, 42)
20
+ end
21
+
22
+ it 'encodes the version in the first byte' do
23
+ encoded_message[0, 1].unpack('c').should == [1]
24
+ end
25
+
26
+ it 'encodes the channel in the second byte' do
27
+ encoded_message[1, 1].unpack('c').should == [42]
28
+ end
29
+
30
+ it 'encodes the length of the frame in the following four bytes' do
31
+ encoded_message[2, 4].unpack('N').should == [22]
32
+ end
33
+
34
+ it 'encodes the object as JSON' do
35
+ encoded_message[6..-1].should == '{"foo":"bar","baz":42}'
36
+ end
37
+ end
38
+
39
+ describe '#decode' do
40
+ let :object do
41
+ {'foo' => 'bar', 'baz' => 42}
42
+ end
43
+
44
+ let :encoded_message do
45
+ %(\x01\x2a\x00\x00\x00\x16{"foo":"bar","baz":42})
46
+ end
47
+
48
+ it 'returns a partial message when there are less than five bytes' do
49
+ _, _, complete = codec.decode(Ione::ByteBuffer.new(encoded_message[0, 4]), nil)
50
+ complete.should be_false
51
+ end
52
+
53
+ it 'returns a partial message when the combined size of a previous partial message an new data is still less than the full frame size' do
54
+ buffer = Ione::ByteBuffer.new
55
+ buffer << encoded_message[0, 10]
56
+ message, _, _ = codec.decode(buffer, nil)
57
+ buffer << encoded_message[10, 4]
58
+ _, _, complete = codec.decode(buffer, message)
59
+ complete.should be_false
60
+ end
61
+
62
+ it 'returns a message and channel when it gets a full frame in one chunk' do
63
+ message, channel, complete = codec.decode(Ione::ByteBuffer.new(encoded_message), nil)
64
+ complete.should be_true
65
+ message.should == object
66
+ channel.should == 42
67
+ end
68
+
69
+ it 'returns a message and channel when it gets a full frame in chunks' do
70
+ buffer = Ione::ByteBuffer.new
71
+ buffer << encoded_message[0, 4]
72
+ message, _, _ = codec.decode(buffer, nil)
73
+ buffer << encoded_message[4, 10]
74
+ message, _, _ = codec.decode(buffer, message)
75
+ buffer << encoded_message[14..-1]
76
+ message, channel, _ = codec.decode(buffer, message)
77
+ message.should == object
78
+ channel.should == 42
79
+ end
80
+
81
+ it 'returns a message when it gets more bytes than needed' do
82
+ buffer = Ione::ByteBuffer.new
83
+ buffer << encoded_message[0, 10]
84
+ message, _ = codec.decode(buffer, message)
85
+ buffer << encoded_message[10..-1]
86
+ buffer << 'fooooo'
87
+ message, channel = codec.decode(buffer, message)
88
+ message.should == object
89
+ channel.should == 42
90
+ end
91
+ end
92
+
93
+ context 'when decoding and encoding' do
94
+ let :message do
95
+ {'foo' => 'bar', 'baz' => 42}
96
+ end
97
+
98
+ let :channel do
99
+ 42
100
+ end
101
+
102
+ let :frame do
103
+ %(\x01\x2a\x00\x00\x00\x16{"foo":"bar","baz":42})
104
+ end
105
+
106
+ it 'decoding a encoded frame returns the original message' do
107
+ frm = codec.encode(message, channel)
108
+ msg, ch, _ = codec.decode(Ione::ByteBuffer.new(frm), nil)
109
+ msg.should == message
110
+ ch.should == channel
111
+ end
112
+
113
+ it 'encoding a message gives the original frame' do
114
+ msg, ch, _ = codec.decode(Ione::ByteBuffer.new(frame), nil)
115
+ frm = codec.encode(msg, ch)
116
+ frm.should == frame
117
+ end
118
+ end
119
+ end
120
+
121
+ describe StandardCodec do
122
+ let :codec do
123
+ described_class.new(JSON)
124
+ end
125
+
126
+ describe '#encode' do
127
+ it 'calls #dump on the delegate' do
128
+ codec.encode({'foo' => 'bar'}, 0)[6..-1].should == '{"foo":"bar"}'
129
+ end
130
+ end
131
+
132
+ describe '#decode' do
133
+ it 'calls #load on the delegate' do
134
+ buffer = Ione::ByteBuffer.new(%(\x01\x00\x00\x00\x00\x0d{"foo":"bar"}))
135
+ message, _, _ = codec.decode(buffer, nil)
136
+ message.should eql('foo' => 'bar')
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
142
+
143
+ module CodecSpec
144
+ class JsonCodec < Ione::Rpc::Codec
145
+ def encode_message(message)
146
+ JSON.dump(message)
147
+ end
148
+
149
+ def decode_message(str)
150
+ JSON.load(str)
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,137 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+
6
+ shared_examples 'peers' do
7
+ context 'when setting up' do
8
+ it 'registers itself to receive notifications when there is new data' do
9
+ connection.data_listener.should_not be_nil
10
+ end
11
+
12
+ it 'registers itself to receive notifications when the connection closes' do
13
+ connection.closed_listener.should_not be_nil
14
+ end
15
+ end
16
+
17
+ context 'when receiving data' do
18
+ let :empty_frame do
19
+ double(:empty_frame, :partial? => true)
20
+ end
21
+
22
+ let :partial_frame do
23
+ double(:partial_frame, :partial? => true)
24
+ end
25
+
26
+ let :complete_frame do
27
+ double(:complete_frame, :partial? => false)
28
+ end
29
+
30
+ before do
31
+ channel = 9
32
+ codec.stub(:decode) do |buffer, previous_frame|
33
+ if buffer.empty?
34
+ [empty_frame, channel, false]
35
+ elsif buffer.index('FAKEPARTIALFRAME') == 0
36
+ buffer.read(16)
37
+ [partial_frame, channel, false]
38
+ elsif buffer.index('FAKEENDOFFRAME') == 0 && previous_frame == partial_frame
39
+ buffer.read(14)
40
+ [complete_frame, channel, true]
41
+ elsif buffer.index('FOO') == 0 || buffer.index('BAR') == 0 || buffer.index('BAZ') == 0
42
+ buffer.read(3)
43
+ [complete_frame, channel, true]
44
+ end
45
+ end
46
+ end
47
+
48
+ it 'does nothing when it\'s a partial frame' do
49
+ connection.data_listener.call('FAKEPARTIALFRAME')
50
+ end
51
+
52
+ it 'stiches together frames from fragments' do
53
+ connection.data_listener.call('FAKEPARTIALFRAME')
54
+ connection.data_listener.call('FAKEENDOFFRAME')
55
+ connection.written_bytes.should_not be_nil
56
+ end
57
+
58
+ it 'delivers the decoded frames to #handle_message' do
59
+ connection.data_listener.call('FAKEPARTIALFRAME')
60
+ connection.data_listener.call('FAKEENDOFFRAME')
61
+ peer.messages.should == [[complete_frame, 9]]
62
+ end
63
+
64
+ it 'delivers multiple frames to #handle_message' do
65
+ connection.data_listener.call('FOOBARBAZ')
66
+ peer.messages.should have(3).items
67
+ end
68
+ end
69
+
70
+ context 'when sending data' do
71
+ before do
72
+ codec.stub(:encode) do |message, channel|
73
+ "#{message}@#{channel}"
74
+ end
75
+ end
76
+
77
+ it 'writes the encoded frame to the connection' do
78
+ peer.send_message('FUZZBAZZ')
79
+ codec.should have_received(:encode).with('FUZZBAZZ', 0)
80
+ connection.written_bytes.should == 'FUZZBAZZ@0'
81
+ end
82
+ end
83
+
84
+ context 'when the connection closes' do
85
+ it 'calls the closed listeners' do
86
+ called1 = false
87
+ called2 = false
88
+ peer.on_closed { called1 = true }
89
+ peer.on_closed { called2 = true }
90
+ connection.closed_listener.call
91
+ called1.should be_true
92
+ called2.should be_true
93
+ end
94
+
95
+ it 'calls the closed listener with the close cause' do
96
+ cause = nil
97
+ peer.on_closed { |e| cause = e }
98
+ connection.closed_listener.call(StandardError.new('foo'))
99
+ cause.should == StandardError.new('foo')
100
+ end
101
+ end
102
+
103
+ context 'when closed' do
104
+ before do
105
+ connection.stub(:close)
106
+ end
107
+
108
+ it 'closes the connection' do
109
+ peer.close
110
+ connection.should have_received(:close)
111
+ end
112
+ end
113
+ end
114
+
115
+ module RpcSpec
116
+ class FakeConnection
117
+ attr_reader :written_bytes, :data_listener, :closed_listener, :host, :port
118
+
119
+ def initialize
120
+ @host = 'example.com'
121
+ @port = 9999
122
+ @written_bytes = ''
123
+ end
124
+
125
+ def write(bytes)
126
+ @written_bytes << bytes
127
+ end
128
+
129
+ def on_data(&listener)
130
+ @data_listener = listener
131
+ end
132
+
133
+ def on_closed(&listener)
134
+ @closed_listener = listener
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,214 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+
6
+ module Ione
7
+ module Rpc
8
+ describe Server do
9
+ let :server do
10
+ ServerSpec::TestServer.new(4321, codec, io_reactor: io_reactor, logger: logger)
11
+ end
12
+
13
+ let :codec do
14
+ double(:codec)
15
+ end
16
+
17
+ let :logger do
18
+ double(:logger)
19
+ end
20
+
21
+ let :acceptor do
22
+ double(:acceptor)
23
+ end
24
+
25
+ let :io_reactor do
26
+ double(:io_reactor)
27
+ end
28
+
29
+ before do
30
+ io_reactor.stub(:start).and_return(Ione::Future.resolved(io_reactor))
31
+ io_reactor.stub(:stop).and_return(Ione::Future.resolved(io_reactor))
32
+ io_reactor.stub(:bind) do |*args, &callback|
33
+ callback.call(acceptor)
34
+ Ione::Future.resolved
35
+ end
36
+ end
37
+
38
+ before do
39
+ acceptor.stub(:on_accept)
40
+ end
41
+
42
+ before do
43
+ codec.stub(:encode) { |msg, _| msg }
44
+ end
45
+
46
+ before do
47
+ logger.stub(:info)
48
+ end
49
+
50
+ describe '#port' do
51
+ it 'returns the port the server is listening on' do
52
+ server.port.should == 4321
53
+ end
54
+ end
55
+
56
+ describe '#start' do
57
+ it 'starts the reactor' do
58
+ server.start.value
59
+ io_reactor.should have_received(:start)
60
+ end
61
+
62
+ it 'starts a server on the specified port' do
63
+ server.start.value
64
+ io_reactor.should have_received(:bind).with('0.0.0.0', 4321, 5)
65
+ end
66
+
67
+ it 'starts a server that binds to the specified address' do
68
+ server = described_class.new(4321, codec, io_reactor: io_reactor, bind_address: '1.1.1.1')
69
+ server.start.value
70
+ io_reactor.should have_received(:bind).with('1.1.1.1', 4321, anything)
71
+ end
72
+
73
+ it 'uses the specified queue size' do
74
+ server = described_class.new(4321, codec, io_reactor: io_reactor, queue_size: 11)
75
+ server.start.value
76
+ io_reactor.should have_received(:bind).with(anything, anything, 11)
77
+ end
78
+
79
+ it 'returns a future that resolves to the server' do
80
+ server.start.value.should equal(server)
81
+ end
82
+
83
+ it 'logs the address and port when listening for connections' do
84
+ server.start.value
85
+ logger.should have_received(:info).with('Server listening for connections on 0.0.0.0:4321')
86
+ end
87
+ end
88
+
89
+ describe '#stop' do
90
+ it 'stops the reactor' do
91
+ server.stop.value
92
+ io_reactor.should have_received(:stop)
93
+ end
94
+
95
+ it 'returns a future that resolves to the server' do
96
+ server.stop.value.should equal(server)
97
+ end
98
+ end
99
+
100
+ shared_context 'client_connections' do
101
+ let :accept_listeners do
102
+ []
103
+ end
104
+
105
+ let :raw_connection do
106
+ double(:connection)
107
+ end
108
+
109
+ before do
110
+ raw_connection.stub(:on_data)
111
+ raw_connection.stub(:on_closed) { |&listener| raw_connection.stub(:closed_listener).and_return(listener) }
112
+ raw_connection.stub(:host).and_return('client.example.com')
113
+ raw_connection.stub(:port).and_return(34534)
114
+ raw_connection.stub(:write)
115
+ end
116
+
117
+ before do
118
+ acceptor.stub(:on_accept) do |&listener|
119
+ accept_listeners << listener
120
+ end
121
+ end
122
+
123
+ before do
124
+ server.start.value
125
+ end
126
+ end
127
+
128
+ context 'when a client connects' do
129
+ include_context 'client_connections'
130
+
131
+ before do
132
+ accept_listeners.first.call(raw_connection)
133
+ end
134
+
135
+ it 'calls #handle_connection with the client connection' do
136
+ server.connections.should have(1).item
137
+ server.connections.first.host.should == raw_connection.host
138
+ end
139
+
140
+ it 'logs that a client connected' do
141
+ logger.should have_received(:info).with('Connection from client.example.com:34534 accepted')
142
+ end
143
+
144
+ it 'logs when the client is disconnected' do
145
+ raw_connection.closed_listener.call
146
+ logger.should have_received(:info).with('Connection from client.example.com:34534 closed')
147
+ end
148
+ end
149
+
150
+ context 'when a client sends a request' do
151
+ include_context 'client_connections'
152
+
153
+ before do
154
+ accept_listeners.first.call(raw_connection)
155
+ end
156
+
157
+ it 'calls #handle_request with the request' do
158
+ server.connections.first.handle_message('FOOBAZ', 42)
159
+ server.received_messages.first.should == ['FOOBAZ', server.connections.first]
160
+ end
161
+
162
+ it 'responds to the same peer and channel when the future returned by #handle_request is resolved' do
163
+ promise = Promise.new
164
+ server.override_handle_request { promise.future }
165
+ peer = server.connections.first
166
+ peer.stub(:write_message)
167
+ peer.handle_message('FOOBAZ', 42)
168
+ peer.should_not have_received(:write_message)
169
+ promise.fulfill('BAZFOO')
170
+ peer.should have_received(:write_message).with('BAZFOO', 42)
171
+ end
172
+
173
+ it 'uses the codec to encode the response' do
174
+ codec.stub(:encode).with('BAZFOO', 42).and_return('42BAZFOO')
175
+ server.override_handle_request { Future.resolved('BAZFOO') }
176
+ peer = server.connections.first
177
+ peer.handle_message('FOOBAZ', 42)
178
+ raw_connection.should have_received(:write).with('42BAZFOO')
179
+ end
180
+
181
+ it 'handles that the server fails to process the request'
182
+ end
183
+ end
184
+ end
185
+ end
186
+
187
+ module ServerSpec
188
+ class TestServer < Ione::Rpc::Server
189
+ attr_reader :connections, :received_messages
190
+
191
+ def initialize(*)
192
+ super
193
+ @connections = []
194
+ @received_messages = []
195
+ end
196
+
197
+ def handle_connection(peer)
198
+ @connections << peer
199
+ end
200
+
201
+ def override_handle_request(&handler)
202
+ @request_handler = handler
203
+ end
204
+
205
+ def handle_request(request, peer)
206
+ @received_messages << [request, peer]
207
+ if @request_handler
208
+ @request_handler.call(request, peer)
209
+ else
210
+ super
211
+ end
212
+ end
213
+ end
214
+ end
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+
3
+ ENV['SERVER_HOST'] ||= '127.0.0.1'.freeze
4
+
5
+ require 'bundler/setup'
6
+
7
+ unless ENV['COVERAGE'] == 'no' || RUBY_ENGINE == 'rbx'
8
+ require 'coveralls'
9
+ require 'simplecov'
10
+
11
+ if ENV.include?('TRAVIS')
12
+ Coveralls.wear!
13
+ SimpleCov.formatter = Coveralls::SimpleCov::Formatter
14
+ end
15
+
16
+ SimpleCov.start do
17
+ add_group 'Source', 'lib'
18
+ add_group 'Unit tests', 'spec/ione'
19
+ add_group 'Integration tests', 'spec/integration'
20
+ end
21
+ end
22
+
23
+ require 'ione/rpc'
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ione-rpc
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0.pre0
5
+ platform: ruby
6
+ authors:
7
+ - Theo Hultberg
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-05-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ione
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1'
27
+ description: A toolkit for building RPC layers using Ione
28
+ email:
29
+ - theo@iconara.net
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - .yardopts
35
+ - README.md
36
+ - lib/ione/rpc.rb
37
+ - lib/ione/rpc/client.rb
38
+ - lib/ione/rpc/client_peer.rb
39
+ - lib/ione/rpc/codec.rb
40
+ - lib/ione/rpc/peer.rb
41
+ - lib/ione/rpc/server.rb
42
+ - lib/ione/rpc/version.rb
43
+ - spec/ione/rpc/client_peer_spec.rb
44
+ - spec/ione/rpc/client_spec.rb
45
+ - spec/ione/rpc/codec_spec.rb
46
+ - spec/ione/rpc/peer_common.rb
47
+ - spec/ione/rpc/server_spec.rb
48
+ - spec/spec_helper.rb
49
+ homepage: http://github.com/iconara/ione-rpc
50
+ licenses:
51
+ - Apache License 2.0
52
+ metadata: {}
53
+ post_install_message:
54
+ rdoc_options: []
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: 1.9.3
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - '>'
65
+ - !ruby/object:Gem::Version
66
+ version: 1.3.1
67
+ requirements: []
68
+ rubyforge_project:
69
+ rubygems_version: 2.2.1
70
+ signing_key:
71
+ specification_version: 4
72
+ summary: RPC toolkit for Ruby
73
+ test_files:
74
+ - spec/ione/rpc/client_peer_spec.rb
75
+ - spec/ione/rpc/client_spec.rb
76
+ - spec/ione/rpc/codec_spec.rb
77
+ - spec/ione/rpc/peer_common.rb
78
+ - spec/ione/rpc/server_spec.rb
79
+ - spec/spec_helper.rb
80
+ has_rdoc: