ione 1.2.0.pre1 → 1.2.0.pre2
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/lib/ione/future.rb +44 -24
- data/lib/ione/io.rb +3 -0
- data/lib/ione/io/acceptor.rb +25 -11
- data/lib/ione/io/base_connection.rb +4 -1
- data/lib/ione/io/connection.rb +1 -4
- data/lib/ione/io/io_reactor.rb +46 -5
- data/lib/ione/io/server_connection.rb +1 -4
- data/lib/ione/io/ssl_acceptor.rb +21 -0
- data/lib/ione/io/ssl_connection.rb +79 -0
- data/lib/ione/io/ssl_server_connection.rb +43 -0
- data/lib/ione/version.rb +1 -1
- data/spec/integration/ssl_spec.rb +97 -0
- data/spec/ione/future_spec.rb +145 -30
- data/spec/ione/io/acceptor_spec.rb +7 -0
- data/spec/ione/io/connection_common.rb +28 -26
- data/spec/ione/io/io_reactor_spec.rb +50 -2
- data/spec/ione/io/ssl_acceptor_spec.rb +116 -0
- data/spec/ione/io/ssl_connection_spec.rb +165 -0
- data/spec/ione/io/ssl_server_connection_spec.rb +108 -0
- metadata +13 -2
@@ -63,6 +63,7 @@ module Ione
|
|
63
63
|
socket_impl.stub(:new)
|
64
64
|
.with('FAMILY2', 'TYPE2', 0)
|
65
65
|
.and_return(socket)
|
66
|
+
socket.stub(:close)
|
66
67
|
socket.stub(:bind)
|
67
68
|
socket.stub(:listen)
|
68
69
|
end
|
@@ -100,6 +101,12 @@ module Ione
|
|
100
101
|
expect { f.value }.to raise_error(Errno::EADDRNOTAVAIL)
|
101
102
|
end
|
102
103
|
|
104
|
+
it 'closes the socket when none of the addresses worked' do
|
105
|
+
socket.stub(:bind).and_raise(Errno::EADDRNOTAVAIL)
|
106
|
+
acceptor.bind.value rescue nil
|
107
|
+
socket.should have_received(:close)
|
108
|
+
end
|
109
|
+
|
103
110
|
it 'returns a future that resolves to itself when the socket has been bound' do
|
104
111
|
f = acceptor.bind
|
105
112
|
f.should be_resolved
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
shared_examples_for 'a connection' do
|
3
|
+
shared_examples_for 'a connection' do |options|
|
4
4
|
describe '#close' do
|
5
5
|
it 'closes the socket' do
|
6
6
|
socket.should_receive(:close)
|
@@ -219,36 +219,38 @@ shared_examples_for 'a connection' do
|
|
219
219
|
end
|
220
220
|
end
|
221
221
|
|
222
|
-
|
223
|
-
|
224
|
-
socket
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
it 'calls the data listener with the new data' do
|
229
|
-
socket.should_receive(:read_nonblock).with(instance_of(Fixnum)).and_return('foo bar')
|
230
|
-
data = nil
|
231
|
-
handler.on_data { |d| data = d }
|
232
|
-
handler.read
|
233
|
-
data.should == 'foo bar'
|
234
|
-
end
|
235
|
-
|
236
|
-
context 'when #read_nonblock raises an error' do
|
237
|
-
before do
|
238
|
-
socket.stub(:close)
|
239
|
-
socket.stub(:read_nonblock).and_raise('Bork!')
|
222
|
+
if options.nil? || options.fetch(:skip_read, false) == false
|
223
|
+
describe '#read/#on_data' do
|
224
|
+
it 'reads a chunk from the socket' do
|
225
|
+
socket.should_receive(:read_nonblock).with(instance_of(Fixnum)).and_return('foo bar')
|
226
|
+
handler.read
|
240
227
|
end
|
241
228
|
|
242
|
-
it '
|
243
|
-
socket.should_receive(:
|
229
|
+
it 'calls the data listener with the new data' do
|
230
|
+
socket.should_receive(:read_nonblock).with(instance_of(Fixnum)).and_return('foo bar')
|
231
|
+
data = nil
|
232
|
+
handler.on_data { |d| data = d }
|
244
233
|
handler.read
|
234
|
+
data.should == 'foo bar'
|
245
235
|
end
|
246
236
|
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
237
|
+
context 'when #read_nonblock raises an error' do
|
238
|
+
before do
|
239
|
+
socket.stub(:close)
|
240
|
+
socket.stub(:read_nonblock).and_raise('Bork!')
|
241
|
+
end
|
242
|
+
|
243
|
+
it 'closes the socket' do
|
244
|
+
socket.should_receive(:close)
|
245
|
+
handler.read
|
246
|
+
end
|
247
|
+
|
248
|
+
it 'passes the error to the close handler' do
|
249
|
+
error = nil
|
250
|
+
handler.on_closed { |e| error = e }
|
251
|
+
handler.read
|
252
|
+
error.should be_a(Exception)
|
253
|
+
end
|
252
254
|
end
|
253
255
|
end
|
254
256
|
end
|
@@ -29,7 +29,11 @@ module Ione
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def fake_connected(connection)
|
32
|
-
connection.
|
32
|
+
if connection.is_a?(SslConnection)
|
33
|
+
connection.instance_variable_get(:@io).stub(:connect_nonblock)
|
34
|
+
else
|
35
|
+
connection.to_io.stub(:connect_nonblock)
|
36
|
+
end
|
33
37
|
end
|
34
38
|
|
35
39
|
after do
|
@@ -159,6 +163,18 @@ module Ione
|
|
159
163
|
x.should == :foo
|
160
164
|
end
|
161
165
|
|
166
|
+
it 'defaults to 5 as the connection timeout' do
|
167
|
+
reactor.start.value
|
168
|
+
connection = reactor.connect('example.com', 9999).value
|
169
|
+
connection.connection_timeout.should == 5
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'takes the connection timeout from the :timeout option' do
|
173
|
+
reactor.start.value
|
174
|
+
connection = reactor.connect('example.com', 9999, timeout: 9).value
|
175
|
+
connection.connection_timeout.should == 9
|
176
|
+
end
|
177
|
+
|
162
178
|
it 'returns the connection when no block is given' do
|
163
179
|
reactor.start.value
|
164
180
|
reactor.connect('example.com', 9999, 5).value.should be_a(Connection)
|
@@ -170,6 +186,19 @@ module Ione
|
|
170
186
|
await { selector.last_arguments[0].length > 1 }
|
171
187
|
selector.last_arguments[0].should include(connection)
|
172
188
|
end
|
189
|
+
|
190
|
+
it 'upgrades the connection to SSL' do
|
191
|
+
reactor.start.value
|
192
|
+
connection = reactor.connect('example.com', 9999, ssl: true).value
|
193
|
+
connection.should be_a(SslConnection)
|
194
|
+
end
|
195
|
+
|
196
|
+
it 'passes an SSL context to the SSL connection' do
|
197
|
+
ssl_context = double(:ssl_context)
|
198
|
+
reactor.start.value
|
199
|
+
f = reactor.connect('example.com', 9999, ssl: ssl_context)
|
200
|
+
expect { f.value }.to raise_error
|
201
|
+
end
|
173
202
|
end
|
174
203
|
|
175
204
|
describe '#bind' do
|
@@ -192,6 +221,18 @@ module Ione
|
|
192
221
|
x.should == :foo
|
193
222
|
end
|
194
223
|
|
224
|
+
it 'defaults to a backlog of 5' do
|
225
|
+
reactor.start.value
|
226
|
+
acceptor = reactor.bind(ENV['SERVER_HOST'], port).value
|
227
|
+
acceptor.backlog.should == 5
|
228
|
+
end
|
229
|
+
|
230
|
+
it 'takes the backlog from the :backlog option' do
|
231
|
+
reactor.start.value
|
232
|
+
acceptor = reactor.bind(ENV['SERVER_HOST'], port, backlog: 9).value
|
233
|
+
acceptor.backlog.should == 9
|
234
|
+
end
|
235
|
+
|
195
236
|
it 'returns the acceptor when no block is given' do
|
196
237
|
reactor.start.value
|
197
238
|
acceptor = reactor.bind(ENV['SERVER_HOST'], port, 5).value
|
@@ -201,9 +242,16 @@ module Ione
|
|
201
242
|
it 'creates an acceptor and passes it to the selector as a readable' do
|
202
243
|
reactor.start.value
|
203
244
|
acceptor = reactor.bind(ENV['SERVER_HOST'], port, 5).value
|
204
|
-
await { selector.last_arguments[0].length > 1 }
|
245
|
+
await { selector.last_arguments && selector.last_arguments[0].length > 1 }
|
205
246
|
selector.last_arguments[0].should include(acceptor)
|
206
247
|
end
|
248
|
+
|
249
|
+
it 'creates an SSL acceptor' do
|
250
|
+
ssl_context = double(:ssl_context)
|
251
|
+
reactor.start.value
|
252
|
+
acceptor = reactor.bind(ENV['SERVER_HOST'], port, ssl: ssl_context).value
|
253
|
+
acceptor.should be_an(SslAcceptor)
|
254
|
+
end
|
207
255
|
end
|
208
256
|
|
209
257
|
describe '#schedule_timer' do
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
|
6
|
+
module Ione
|
7
|
+
module Io
|
8
|
+
describe SslAcceptor do
|
9
|
+
let :acceptor do
|
10
|
+
described_class.new('example.com', 4321, backlog = 3, unblocker, reactor, ssl_context, socket_impl, ssl_socket_impl)
|
11
|
+
end
|
12
|
+
|
13
|
+
let :unblocker do
|
14
|
+
double(:unblocker)
|
15
|
+
end
|
16
|
+
|
17
|
+
let :reactor do
|
18
|
+
double(:reactor)
|
19
|
+
end
|
20
|
+
|
21
|
+
let :ssl_context do
|
22
|
+
double(:ssl_context)
|
23
|
+
end
|
24
|
+
|
25
|
+
let :socket_impl do
|
26
|
+
double(:socket_impl)
|
27
|
+
end
|
28
|
+
|
29
|
+
let :ssl_socket_impl do
|
30
|
+
double(:ssl_socket_impl)
|
31
|
+
end
|
32
|
+
|
33
|
+
let :socket do
|
34
|
+
double(:socket)
|
35
|
+
end
|
36
|
+
|
37
|
+
let :ssl_socket do
|
38
|
+
double(:ssl_socket)
|
39
|
+
end
|
40
|
+
|
41
|
+
let :client_socket do
|
42
|
+
double(:client_socket)
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '#read' do
|
46
|
+
let :accepted_handlers do
|
47
|
+
[]
|
48
|
+
end
|
49
|
+
|
50
|
+
before do
|
51
|
+
socket_impl.stub(:getaddrinfo).and_return([[nil, 'PORT', nil, 'IP1', 'FAMILY1', 'TYPE1']])
|
52
|
+
socket_impl.stub(:sockaddr_in).with('PORT', 'IP1').and_return('SOCKADDRX')
|
53
|
+
socket_impl.stub(:unpack_sockaddr_in).with('SOCKADDRX').and_return([3333, 'example.com'])
|
54
|
+
socket_impl.stub(:new).and_return(socket)
|
55
|
+
socket.stub(:bind)
|
56
|
+
socket.stub(:listen)
|
57
|
+
socket.stub(:accept_nonblock).and_return([client_socket, 'SOCKADDRX'])
|
58
|
+
reactor.stub(:accept) { |h| accepted_handlers << h }
|
59
|
+
ssl_socket_impl.stub(:new).with(client_socket, ssl_context).and_return(ssl_socket)
|
60
|
+
ssl_socket.stub(:accept_nonblock)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'accepts a new connection' do
|
64
|
+
acceptor.bind
|
65
|
+
acceptor.read
|
66
|
+
socket.should have_received(:accept_nonblock)
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'notifies the accept handlers' do
|
70
|
+
connection = nil
|
71
|
+
acceptor.on_accept { |c| connection = c }
|
72
|
+
acceptor.bind
|
73
|
+
acceptor.read
|
74
|
+
accepted_handlers.first.read
|
75
|
+
connection.should equal(accepted_handlers.first)
|
76
|
+
end
|
77
|
+
|
78
|
+
context 'creates a new connection handler that' do
|
79
|
+
it 'is registered it with the reactor' do
|
80
|
+
acceptor.bind
|
81
|
+
acceptor.read
|
82
|
+
accepted_handlers.should have(1).item
|
83
|
+
accepted_handlers.first.host.should == 'example.com'
|
84
|
+
accepted_handlers.first.port.should == 3333
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'returns the raw socket from #to_io' do
|
88
|
+
ssl_socket.stub(:to_io).and_return(client_socket)
|
89
|
+
acceptor.bind
|
90
|
+
acceptor.read
|
91
|
+
accepted_handlers.first.to_io.should equal(client_socket)
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'has a reference to the unblocker' do
|
95
|
+
unblocker.stub(:unblock!)
|
96
|
+
acceptor.bind
|
97
|
+
acceptor.read
|
98
|
+
accepted_handlers.first.write('foo')
|
99
|
+
unblocker.should have_received(:unblock!)
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'writes to the SSL socket' do
|
103
|
+
unblocker.stub(:unblock!)
|
104
|
+
ssl_socket.stub(:write_nonblock) { |b| b.bytesize }
|
105
|
+
acceptor.bind
|
106
|
+
acceptor.read
|
107
|
+
accepted_handlers.first.read
|
108
|
+
accepted_handlers.first.write('foo')
|
109
|
+
accepted_handlers.first.flush
|
110
|
+
ssl_socket.should have_received(:write_nonblock).with('foo')
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'ione/io/connection_common'
|
5
|
+
|
6
|
+
|
7
|
+
module Ione
|
8
|
+
module Io
|
9
|
+
describe SslConnection do
|
10
|
+
let :handler do
|
11
|
+
described_class.new('example.com', 55555, raw_socket, unblocker, ssl_context, socket_impl)
|
12
|
+
end
|
13
|
+
|
14
|
+
let :socket_impl do
|
15
|
+
double(:socket_impl)
|
16
|
+
end
|
17
|
+
|
18
|
+
let :raw_socket do
|
19
|
+
double(:raw_socket)
|
20
|
+
end
|
21
|
+
|
22
|
+
let :ssl_socket do
|
23
|
+
double(:ssl_socket)
|
24
|
+
end
|
25
|
+
|
26
|
+
let :unblocker do
|
27
|
+
double(:unblocker, unblock!: nil)
|
28
|
+
end
|
29
|
+
|
30
|
+
let :ssl_context do
|
31
|
+
double(:ssl_context)
|
32
|
+
end
|
33
|
+
|
34
|
+
before do
|
35
|
+
socket_impl.stub(:new).with(raw_socket, ssl_context).and_return(ssl_socket)
|
36
|
+
end
|
37
|
+
|
38
|
+
before do
|
39
|
+
ssl_socket.stub(:connect_nonblock)
|
40
|
+
ssl_socket.stub(:close)
|
41
|
+
end
|
42
|
+
|
43
|
+
it_behaves_like 'a connection', skip_read: true do
|
44
|
+
let :socket do
|
45
|
+
ssl_socket
|
46
|
+
end
|
47
|
+
|
48
|
+
before do
|
49
|
+
handler.connect
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '#connect' do
|
54
|
+
it 'creates an SSL socket and passes in the specified SSL context' do
|
55
|
+
handler.connect
|
56
|
+
socket_impl.should have_received(:new).with(raw_socket, ssl_context)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'does not pass the context parameter when the SSL context is nil' do
|
60
|
+
socket_impl.stub(:new).and_return(ssl_socket)
|
61
|
+
h = described_class.new('example.com', 55555, raw_socket, unblocker, nil, socket_impl)
|
62
|
+
h.connect
|
63
|
+
socket_impl.should have_received(:new).with(raw_socket)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'calls #connect_nonblock on the SSL socket' do
|
67
|
+
handler.connect
|
68
|
+
ssl_socket.should have_received(:connect_nonblock)
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'does nothing when the socket raises a "would block" error' do
|
72
|
+
error = OpenSSL::SSL::SSLError.new('would block')
|
73
|
+
error.extend(IO::WaitReadable)
|
74
|
+
ssl_socket.stub(:connect_nonblock).and_raise(error)
|
75
|
+
expect { handler.connect }.to_not raise_error
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'returns a future that resolves when the socket is connected' do
|
79
|
+
error = OpenSSL::SSL::SSLError.new('would block')
|
80
|
+
error.extend(IO::WaitReadable)
|
81
|
+
ssl_socket.stub(:connect_nonblock).and_raise(error)
|
82
|
+
f = handler.connect
|
83
|
+
f.should_not be_resolved
|
84
|
+
ssl_socket.stub(:connect_nonblock).and_return(nil)
|
85
|
+
handler.connect
|
86
|
+
f.should be_resolved
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'is connected when #connect_nonblock does not raise' do
|
90
|
+
handler.connect
|
91
|
+
handler.should be_connected
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'fails when #connect_nonblock raises an error that does not include the words "would block"' do
|
95
|
+
ssl_socket.stub(:connect_nonblock).and_raise(OpenSSL::SSL::SSLError.new('general bork'))
|
96
|
+
expect { handler.connect.value }.to raise_error(Ione::Io::ConnectionError)
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'is closed when #connect_nonblock raises something that is not a "would block" error' do
|
100
|
+
ssl_socket.stub(:connect_nonblock).and_raise(OpenSSL::SSL::SSLError.new('general bork'))
|
101
|
+
handler.connect
|
102
|
+
handler.should be_closed
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe '#to_io' do
|
107
|
+
it 'returns the raw socket' do
|
108
|
+
handler.to_io.should equal(raw_socket)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe '#read' do
|
113
|
+
if RUBY_ENGINE == 'jruby'
|
114
|
+
it 'reads chunks until #read_nonblock raises OpenSSL::SSL::SSLErrorWaitReadable' do
|
115
|
+
read_sizes = []
|
116
|
+
counter = 3
|
117
|
+
ssl_socket.stub(:read_nonblock) do |read_size|
|
118
|
+
read_sizes << read_size
|
119
|
+
if counter == 0
|
120
|
+
raise OpenSSL::SSL::SSLErrorWaitReadable, 'read would block'
|
121
|
+
end
|
122
|
+
counter -= 1
|
123
|
+
'bar'
|
124
|
+
end
|
125
|
+
handler.connect
|
126
|
+
handler.read
|
127
|
+
read_sizes.drop(1).should == [read_sizes.first] * 3
|
128
|
+
end
|
129
|
+
else
|
130
|
+
it 'reads and initial chunk of data' do
|
131
|
+
data = []
|
132
|
+
handler.on_data { |d| data << d }
|
133
|
+
ssl_socket.stub(:pending).and_return(0)
|
134
|
+
ssl_socket.stub(:read_nonblock).and_return('fooo')
|
135
|
+
handler.connect
|
136
|
+
handler.read
|
137
|
+
data.should == ['fooo']
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'reads once, and then again with the value of #pending, until #pending returns zero' do
|
141
|
+
read_sizes = []
|
142
|
+
counter = 3
|
143
|
+
ssl_socket.stub(:pending).and_return(0)
|
144
|
+
ssl_socket.stub(:read_nonblock) do |read_size|
|
145
|
+
read_sizes << read_size
|
146
|
+
ssl_socket.stub(:pending).and_return(counter)
|
147
|
+
counter -= 1
|
148
|
+
'bar'
|
149
|
+
end
|
150
|
+
handler.connect
|
151
|
+
handler.read
|
152
|
+
read_sizes.drop(1).should == [3, 2, 1]
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'closes the connection when an error occurs' do
|
157
|
+
ssl_socket.stub(:read_nonblock).and_raise('boork')
|
158
|
+
handler.connect
|
159
|
+
handler.read
|
160
|
+
handler.should be_closed
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|