ione 1.2.0.pre1 → 1.2.0.pre2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|