ione 1.0.0 → 1.1.0.pre0
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/io/acceptor.rb +96 -0
- data/lib/ione/io/base_connection.rb +162 -0
- data/lib/ione/io/connection.rb +9 -150
- data/lib/ione/io/io_reactor.rb +15 -0
- data/lib/ione/io/server_connection.rb +17 -0
- data/lib/ione/io.rb +6 -0
- data/lib/ione/version.rb +1 -1
- data/spec/integration/io_spec.rb +41 -4
- data/spec/ione/io/acceptor_spec.rb +223 -0
- data/spec/ione/io/connection_common.rb +255 -0
- data/spec/ione/io/connection_spec.rb +23 -178
- data/spec/ione/io/io_reactor_spec.rb +48 -3
- data/spec/ione/io/server_connection_spec.rb +37 -0
- metadata +13 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6454a46df7484d84b651eec23a73e5c0e04f3530
|
4
|
+
data.tar.gz: e3a48a9e32efc7a42f511b65f6ba248518df01e3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bad31d689dea8e64182be056b877d7a4b4b12c4bbdebfe5b54c1b6eef1daaac9c9ff7e3705cc61bd68cbc6c1b6eeee7c6e228164de7b08a6cfd6a6bb0862ba7c
|
7
|
+
data.tar.gz: 75a8718bbbef223e86776b70e85e3ed4b382702c89f320bdfec5cb594dec95904d9913048283108834d2b7a5640256ee63ade40dfe269e16d6ac9b74ee0a33c4
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
|
4
|
+
module Ione
|
5
|
+
module Io
|
6
|
+
class Acceptor
|
7
|
+
ServerSocket = RUBY_ENGINE == 'jruby' ? ::ServerSocket : Socket
|
8
|
+
|
9
|
+
def initialize(host, port, backlog, unblocker, reactor, socket_impl=nil)
|
10
|
+
@host = host
|
11
|
+
@port = port
|
12
|
+
@backlog = backlog
|
13
|
+
@unblocker = unblocker
|
14
|
+
@reactor = reactor
|
15
|
+
@socket_impl = socket_impl || ServerSocket
|
16
|
+
@accept_listeners = []
|
17
|
+
@lock = Mutex.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def on_accept(&listener)
|
21
|
+
@lock.synchronize do
|
22
|
+
@accept_listeners << listener
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def bind
|
27
|
+
addrinfos = @socket_impl.getaddrinfo(@host, @port, nil, Socket::SOCK_STREAM)
|
28
|
+
begin
|
29
|
+
_, port, _, ip, address_family, socket_type = addrinfos.shift
|
30
|
+
@socket = @socket_impl.new(address_family, socket_type, 0)
|
31
|
+
bind_socket(@socket, @socket_impl.sockaddr_in(port, ip), @backlog)
|
32
|
+
rescue Errno::EADDRNOTAVAIL => e
|
33
|
+
if addrinfos.empty?
|
34
|
+
raise
|
35
|
+
else
|
36
|
+
retry
|
37
|
+
end
|
38
|
+
end
|
39
|
+
Future.resolved(self)
|
40
|
+
rescue => e
|
41
|
+
Future.failed(e)
|
42
|
+
end
|
43
|
+
|
44
|
+
def close
|
45
|
+
return false unless @socket
|
46
|
+
begin
|
47
|
+
@socket.close
|
48
|
+
rescue SystemCallError, IOError
|
49
|
+
# nothing to do, the socket was most likely already closed
|
50
|
+
end
|
51
|
+
@socket = nil
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_io
|
56
|
+
@socket
|
57
|
+
end
|
58
|
+
|
59
|
+
def closed?
|
60
|
+
@socket.nil?
|
61
|
+
end
|
62
|
+
|
63
|
+
def connected?
|
64
|
+
!closed?
|
65
|
+
end
|
66
|
+
|
67
|
+
def connecting?
|
68
|
+
false
|
69
|
+
end
|
70
|
+
|
71
|
+
def writable?
|
72
|
+
false
|
73
|
+
end
|
74
|
+
|
75
|
+
def read
|
76
|
+
client_socket, client_sockaddr = @socket.accept_nonblock
|
77
|
+
port, host = @socket_impl.unpack_sockaddr_in(client_sockaddr)
|
78
|
+
connection = ServerConnection.new(client_socket, host, port, @unblocker)
|
79
|
+
@reactor.accept(connection)
|
80
|
+
listeners = @lock.synchronize { @accept_listeners }
|
81
|
+
listeners.each { |l| l.call(connection) rescue nil }
|
82
|
+
end
|
83
|
+
|
84
|
+
if RUBY_ENGINE == 'jruby'
|
85
|
+
def bind_socket(socket, addr, backlog)
|
86
|
+
socket.bind(addr, backlog)
|
87
|
+
end
|
88
|
+
else
|
89
|
+
def bind_socket(socket, addr, backlog)
|
90
|
+
socket.bind(addr)
|
91
|
+
socket.listen(backlog)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Ione
|
4
|
+
module Io
|
5
|
+
class BaseConnection
|
6
|
+
attr_reader :host, :port
|
7
|
+
|
8
|
+
def initialize(host, port)
|
9
|
+
@host = host
|
10
|
+
@port = port
|
11
|
+
@state = :connecting
|
12
|
+
@closed_promise = Promise.new
|
13
|
+
end
|
14
|
+
|
15
|
+
# Closes the connection
|
16
|
+
#
|
17
|
+
# @return [true, false] returns false if the connection was already closed
|
18
|
+
def close(cause=nil)
|
19
|
+
return false if @state == :closed
|
20
|
+
if @io
|
21
|
+
begin
|
22
|
+
@io.close
|
23
|
+
@io = nil
|
24
|
+
rescue SystemCallError, IOError
|
25
|
+
# nothing to do, the socket was most likely already closed
|
26
|
+
end
|
27
|
+
end
|
28
|
+
@state = :closed
|
29
|
+
if cause
|
30
|
+
@closed_promise.fail(cause)
|
31
|
+
else
|
32
|
+
@closed_promise.fulfill(self)
|
33
|
+
end
|
34
|
+
true
|
35
|
+
end
|
36
|
+
|
37
|
+
# Wait for the connection's buffers to empty and then close it.
|
38
|
+
#
|
39
|
+
# This method is almost always preferable to {#close}.
|
40
|
+
#
|
41
|
+
# @return [Ione::Future] a future that resolves to the connection when it
|
42
|
+
# has closed
|
43
|
+
def drain
|
44
|
+
@state = :draining
|
45
|
+
close if @write_buffer.empty?
|
46
|
+
@closed_promise.future
|
47
|
+
end
|
48
|
+
|
49
|
+
# @private
|
50
|
+
def connecting?
|
51
|
+
@state == :connecting
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns true if the connection is connected
|
55
|
+
def connected?
|
56
|
+
@state == :connected
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns true if the connection is closed
|
60
|
+
def closed?
|
61
|
+
@state == :closed
|
62
|
+
end
|
63
|
+
|
64
|
+
# @private
|
65
|
+
def writable?
|
66
|
+
empty_buffer = @lock.synchronize do
|
67
|
+
@write_buffer.empty?
|
68
|
+
end
|
69
|
+
!(closed? || empty_buffer)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Register to receive notifications when new data is read from the socket.
|
73
|
+
#
|
74
|
+
# You should only call this method in your protocol handler constructor.
|
75
|
+
#
|
76
|
+
# Only one callback can be registered, if you register multiple times only
|
77
|
+
# the last one will receive notifications. This is not meant as a general
|
78
|
+
# event system, it's just for protocol handlers to receive data from their
|
79
|
+
# connection. If you want multiple listeners you need to implement that
|
80
|
+
# yourself in your protocol handler.
|
81
|
+
#
|
82
|
+
# It is very important that you don't do any heavy lifting in the callback
|
83
|
+
# since it is called from the IO reactor thread, and as long as the
|
84
|
+
# callback is working the reactor can't handle any IO and no other
|
85
|
+
# callbacks can be called.
|
86
|
+
#
|
87
|
+
# Errors raised by the callback will be ignored.
|
88
|
+
#
|
89
|
+
# @yield [String] the new data
|
90
|
+
def on_data(&listener)
|
91
|
+
@data_listener = listener
|
92
|
+
end
|
93
|
+
|
94
|
+
# Register to receive a notification when the socket is closed, both for
|
95
|
+
# expected and unexpected reasons.
|
96
|
+
#
|
97
|
+
# Errors raised by the callback will be ignored.
|
98
|
+
#
|
99
|
+
# @yield [error, nil] the error that caused the socket to close, or nil if
|
100
|
+
# the socket closed with #close
|
101
|
+
def on_closed(&listener)
|
102
|
+
@closed_promise.future.on_value { listener.call(nil) }
|
103
|
+
@closed_promise.future.on_failure { |e| listener.call(e) }
|
104
|
+
end
|
105
|
+
|
106
|
+
# Write bytes to the socket.
|
107
|
+
#
|
108
|
+
# You can either pass in bytes (as a string or as a `ByteBuffer`), or you
|
109
|
+
# can use the block form of this method to get access to the connection's
|
110
|
+
# internal buffer.
|
111
|
+
#
|
112
|
+
# @yieldparam buffer [Ione::ByteBuffer] the connection's internal buffer
|
113
|
+
# @param bytes [String, Ione::ByteBuffer] the data to write to the socket
|
114
|
+
def write(bytes=nil)
|
115
|
+
if @state == :connected || @state == :connecting
|
116
|
+
@lock.synchronize do
|
117
|
+
if block_given?
|
118
|
+
yield @write_buffer
|
119
|
+
elsif bytes
|
120
|
+
@write_buffer.append(bytes)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
@unblocker.unblock!
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# @private
|
128
|
+
def flush
|
129
|
+
if @state == :connected || @state == :draining
|
130
|
+
@lock.synchronize do
|
131
|
+
unless @write_buffer.empty?
|
132
|
+
bytes_written = @io.write_nonblock(@write_buffer.cheap_peek)
|
133
|
+
@write_buffer.discard(bytes_written)
|
134
|
+
end
|
135
|
+
if @state == :draining && @write_buffer.empty?
|
136
|
+
close
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
rescue => e
|
141
|
+
close(e)
|
142
|
+
end
|
143
|
+
|
144
|
+
# @private
|
145
|
+
def read
|
146
|
+
new_data = @io.read_nonblock(2**16)
|
147
|
+
@data_listener.call(new_data) if @data_listener
|
148
|
+
rescue => e
|
149
|
+
close(e)
|
150
|
+
end
|
151
|
+
|
152
|
+
# @private
|
153
|
+
def to_io
|
154
|
+
@io
|
155
|
+
end
|
156
|
+
|
157
|
+
def to_s
|
158
|
+
%(#<#{self.class.name} #{@state} #{@host}:#{@port}>)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
data/lib/ione/io/connection.rb
CHANGED
@@ -1,27 +1,23 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
require 'socket'
|
4
|
-
|
5
|
-
|
6
3
|
module Ione
|
7
4
|
module Io
|
8
5
|
# A wrapper around a socket. Handles connecting to the remote host, reading
|
9
6
|
# from and writing to the socket.
|
10
|
-
class Connection
|
11
|
-
attr_reader :
|
7
|
+
class Connection < BaseConnection
|
8
|
+
attr_reader :connection_timeout
|
12
9
|
|
13
10
|
# @private
|
14
11
|
def initialize(host, port, connection_timeout, unblocker, clock, socket_impl=Socket)
|
15
|
-
|
16
|
-
@port = port
|
12
|
+
super(host, port)
|
17
13
|
@connection_timeout = connection_timeout
|
18
14
|
@unblocker = unblocker
|
19
15
|
@clock = clock
|
20
16
|
@socket_impl = socket_impl
|
21
17
|
@lock = Mutex.new
|
22
|
-
@connected = false
|
23
18
|
@write_buffer = ByteBuffer.new
|
24
19
|
@connected_promise = Promise.new
|
20
|
+
on_closed(&method(:cleanup_on_close))
|
25
21
|
end
|
26
22
|
|
27
23
|
# @private
|
@@ -38,11 +34,11 @@ module Ione
|
|
38
34
|
end
|
39
35
|
unless connected?
|
40
36
|
@io.connect_nonblock(@sockaddr)
|
41
|
-
@
|
37
|
+
@state = :connected
|
42
38
|
@connected_promise.fulfill(self)
|
43
39
|
end
|
44
40
|
rescue Errno::EISCONN
|
45
|
-
@
|
41
|
+
@state = :connected
|
46
42
|
@connected_promise.fulfill(self)
|
47
43
|
rescue Errno::EINPROGRESS, Errno::EALREADY
|
48
44
|
if @clock.now - @connection_started_at > @connection_timeout
|
@@ -58,157 +54,20 @@ module Ione
|
|
58
54
|
rescue SystemCallError => e
|
59
55
|
close(e)
|
60
56
|
rescue SocketError => e
|
61
|
-
close(e) ||
|
57
|
+
close(e) || cleanup_on_close(e)
|
62
58
|
end
|
63
59
|
@connected_promise.future
|
64
60
|
end
|
65
61
|
|
66
|
-
# Closes the connection
|
67
|
-
def close(cause=nil)
|
68
|
-
return false unless @io
|
69
|
-
begin
|
70
|
-
@io.close
|
71
|
-
rescue SystemCallError, IOError
|
72
|
-
# nothing to do, the socket was most likely already closed
|
73
|
-
end
|
74
|
-
closed!(cause)
|
75
|
-
true
|
76
|
-
end
|
77
|
-
|
78
|
-
# @private
|
79
|
-
def connecting?
|
80
|
-
!(closed? || connected?)
|
81
|
-
end
|
82
|
-
|
83
|
-
# Returns true if the connection is connected
|
84
|
-
def connected?
|
85
|
-
@connected
|
86
|
-
end
|
87
|
-
|
88
|
-
# Returns true if the connection is closed
|
89
|
-
def closed?
|
90
|
-
@io.nil?
|
91
|
-
end
|
92
|
-
|
93
|
-
# @private
|
94
|
-
def writable?
|
95
|
-
empty_buffer = @lock.synchronize do
|
96
|
-
@write_buffer.empty?
|
97
|
-
end
|
98
|
-
!(closed? || empty_buffer)
|
99
|
-
end
|
100
|
-
|
101
|
-
# Register to receive notifications when new data is read from the socket.
|
102
|
-
#
|
103
|
-
# You should only call this method in your protocol handler constructor.
|
104
|
-
#
|
105
|
-
# Only one callback can be registered, if you register multiple times only
|
106
|
-
# the last one will receive notifications. This is not meant as a general
|
107
|
-
# event system, it's just for protocol handlers to receive data from their
|
108
|
-
# connection. If you want multiple listeners you need to implement that
|
109
|
-
# yourself in your protocol handler.
|
110
|
-
#
|
111
|
-
# It is very important that you don't do any heavy lifting in the callback
|
112
|
-
# since it is called from the IO reactor thread, and as long as the
|
113
|
-
# callback is working the reactor can't handle any IO and no other
|
114
|
-
# callbacks can be called.
|
115
|
-
#
|
116
|
-
# Errors raised by the callback will be ignored.
|
117
|
-
#
|
118
|
-
# @yield [String] the new data
|
119
|
-
def on_data(&listener)
|
120
|
-
@data_listener = listener
|
121
|
-
end
|
122
|
-
|
123
|
-
# Register to receive a notification when the socket is closed, both for
|
124
|
-
# expected and unexpected reasons.
|
125
|
-
#
|
126
|
-
# You shoud only call this method in your protocol handler constructor.
|
127
|
-
#
|
128
|
-
# Only one callback can be registered, if you register multiple times only
|
129
|
-
# the last one will receive notifications. This is not meant as a general
|
130
|
-
# event system, it's just for protocol handlers to be notified of the
|
131
|
-
# connection closing. If you want multiple listeners you need to implement
|
132
|
-
# that yourself in your protocol handler.
|
133
|
-
#
|
134
|
-
# Errors raised by the callback will be ignored.
|
135
|
-
#
|
136
|
-
# @yield [error, nil] the error that caused the socket to close, or nil if
|
137
|
-
# the socket closed with #close
|
138
|
-
def on_closed(&listener)
|
139
|
-
@closed_listener = listener
|
140
|
-
end
|
141
|
-
|
142
|
-
# Write bytes to the socket.
|
143
|
-
#
|
144
|
-
# You can either pass in bytes (as a string or as a `ByteBuffer`), or you
|
145
|
-
# can use the block form of this method to get access to the connection's
|
146
|
-
# internal buffer.
|
147
|
-
#
|
148
|
-
# @yieldparam buffer [Ione::ByteBuffer] the connection's internal buffer
|
149
|
-
# @param bytes [String, Ione::ByteBuffer] the data to write to the socket
|
150
|
-
def write(bytes=nil)
|
151
|
-
@lock.synchronize do
|
152
|
-
if block_given?
|
153
|
-
yield @write_buffer
|
154
|
-
elsif bytes
|
155
|
-
@write_buffer.append(bytes)
|
156
|
-
end
|
157
|
-
end
|
158
|
-
@unblocker.unblock!
|
159
|
-
end
|
160
|
-
|
161
|
-
# @private
|
162
|
-
def flush
|
163
|
-
if writable?
|
164
|
-
@lock.synchronize do
|
165
|
-
bytes_written = @io.write_nonblock(@write_buffer.cheap_peek)
|
166
|
-
@write_buffer.discard(bytes_written)
|
167
|
-
end
|
168
|
-
end
|
169
|
-
rescue => e
|
170
|
-
close(e)
|
171
|
-
end
|
172
|
-
|
173
|
-
# @private
|
174
|
-
def read
|
175
|
-
new_data = @io.read_nonblock(2**16)
|
176
|
-
@data_listener.call(new_data) if @data_listener
|
177
|
-
rescue => e
|
178
|
-
close(e)
|
179
|
-
end
|
180
|
-
|
181
|
-
# @private
|
182
|
-
def to_io
|
183
|
-
@io
|
184
|
-
end
|
185
|
-
|
186
|
-
def to_s
|
187
|
-
state = 'inconsistent'
|
188
|
-
if connected?
|
189
|
-
state = 'connected'
|
190
|
-
elsif connecting?
|
191
|
-
state = 'connecting'
|
192
|
-
elsif closed?
|
193
|
-
state = 'closed'
|
194
|
-
end
|
195
|
-
%(#<#{self.class.name} #{state} #{@host}:#{@port}>)
|
196
|
-
end
|
197
|
-
|
198
62
|
private
|
199
63
|
|
200
|
-
def
|
201
|
-
@io = nil
|
64
|
+
def cleanup_on_close(cause)
|
202
65
|
if cause && !cause.is_a?(IoError)
|
203
66
|
cause = ConnectionError.new(cause.message)
|
204
67
|
end
|
205
|
-
unless
|
68
|
+
unless @connected_promise.future.completed?
|
206
69
|
@connected_promise.fail(cause)
|
207
70
|
end
|
208
|
-
@connected = false
|
209
|
-
if @closed_listener
|
210
|
-
@closed_listener.call(cause)
|
211
|
-
end
|
212
71
|
end
|
213
72
|
end
|
214
73
|
end
|
data/lib/ione/io/io_reactor.rb
CHANGED
@@ -171,6 +171,21 @@ module Ione
|
|
171
171
|
f
|
172
172
|
end
|
173
173
|
|
174
|
+
def bind(host, port, backlog, &block)
|
175
|
+
server = Acceptor.new(host, port, backlog, @unblocker, self)
|
176
|
+
f = server.bind
|
177
|
+
@io_loop.add_socket(server)
|
178
|
+
@unblocker.unblock!
|
179
|
+
f = f.map(&block) if block_given?
|
180
|
+
f
|
181
|
+
end
|
182
|
+
|
183
|
+
# @private
|
184
|
+
def accept(socket)
|
185
|
+
@io_loop.add_socket(socket)
|
186
|
+
@unblocker.unblock!
|
187
|
+
end
|
188
|
+
|
174
189
|
# Returns a future that completes after the specified number of seconds.
|
175
190
|
#
|
176
191
|
# @param timeout [Float] the number of seconds to wait until the returned
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Ione
|
4
|
+
module Io
|
5
|
+
class ServerConnection < BaseConnection
|
6
|
+
# @private
|
7
|
+
def initialize(socket, host, port, unblocker)
|
8
|
+
super(host, port)
|
9
|
+
@io = socket
|
10
|
+
@unblocker = unblocker
|
11
|
+
@lock = Mutex.new
|
12
|
+
@write_buffer = ByteBuffer.new
|
13
|
+
@state = :connected
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/ione/io.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
+
require 'socket'
|
4
|
+
|
5
|
+
|
3
6
|
module Ione
|
4
7
|
CancelledError = Class.new(StandardError)
|
5
8
|
IoError = Class.new(StandardError)
|
@@ -12,4 +15,7 @@ module Ione
|
|
12
15
|
end
|
13
16
|
|
14
17
|
require 'ione/io/io_reactor'
|
18
|
+
require 'ione/io/base_connection'
|
15
19
|
require 'ione/io/connection'
|
20
|
+
require 'ione/io/server_connection'
|
21
|
+
require 'ione/io/acceptor'
|
data/lib/ione/version.rb
CHANGED
data/spec/integration/io_spec.rb
CHANGED
@@ -8,7 +8,7 @@ describe 'An IO reactor' do
|
|
8
8
|
Ione::Io::IoReactor.new
|
9
9
|
end
|
10
10
|
|
11
|
-
context '
|
11
|
+
context 'connecting to a generic server' do
|
12
12
|
let :protocol_handler_factory do
|
13
13
|
lambda { |c| IoSpec::TestConnection.new(c) }
|
14
14
|
end
|
@@ -28,12 +28,12 @@ describe 'An IO reactor' do
|
|
28
28
|
end
|
29
29
|
|
30
30
|
it 'connects to the server' do
|
31
|
-
io_reactor.connect(ENV['SERVER_HOST'], fake_server.port, 1
|
31
|
+
io_reactor.connect(ENV['SERVER_HOST'], fake_server.port, 1, &protocol_handler_factory)
|
32
32
|
fake_server.await_connects!(1)
|
33
33
|
end
|
34
34
|
|
35
35
|
it 'receives data' do
|
36
|
-
protocol_handler = io_reactor.connect(ENV['SERVER_HOST'], fake_server.port, 1
|
36
|
+
protocol_handler = io_reactor.connect(ENV['SERVER_HOST'], fake_server.port, 1, &protocol_handler_factory).value
|
37
37
|
fake_server.await_connects!(1)
|
38
38
|
fake_server.broadcast!('hello world')
|
39
39
|
await { protocol_handler.data.bytesize > 0 }
|
@@ -41,13 +41,50 @@ describe 'An IO reactor' do
|
|
41
41
|
end
|
42
42
|
|
43
43
|
it 'receives data on multiple connections' do
|
44
|
-
protocol_handlers = Array.new(10) { io_reactor.connect(ENV['SERVER_HOST'], fake_server.port, 1
|
44
|
+
protocol_handlers = Array.new(10) { io_reactor.connect(ENV['SERVER_HOST'], fake_server.port, 1, &protocol_handler_factory).value }
|
45
45
|
fake_server.await_connects!(10)
|
46
46
|
fake_server.broadcast!('hello world')
|
47
47
|
await { protocol_handlers.all? { |c| c.data.bytesize > 0 } }
|
48
48
|
protocol_handlers.sample.data.should == 'hello world'
|
49
49
|
end
|
50
50
|
end
|
51
|
+
|
52
|
+
context 'running an echo server' do
|
53
|
+
let :protocol_handler_factory do
|
54
|
+
lambda do |acceptor|
|
55
|
+
acceptor.on_accept do |connection|
|
56
|
+
connection.on_data do |data|
|
57
|
+
connection.write(data)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
let :port do
|
64
|
+
2**15 + rand(2**15)
|
65
|
+
end
|
66
|
+
|
67
|
+
before do
|
68
|
+
io_reactor.start.value
|
69
|
+
end
|
70
|
+
|
71
|
+
after do
|
72
|
+
io_reactor.stop.value
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'starts a server' do
|
76
|
+
io_reactor.bind(ENV['SERVER_HOST'], port, 1, &protocol_handler_factory).value
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'starts a server that listens to the specified port' do
|
80
|
+
io_reactor.bind(ENV['SERVER_HOST'], port, 1, &protocol_handler_factory).value
|
81
|
+
socket = TCPSocket.new(ENV['SERVER_HOST'], port)
|
82
|
+
socket.puts('HELLO')
|
83
|
+
result = socket.read(5)
|
84
|
+
result.should == 'HELLO'
|
85
|
+
socket.close
|
86
|
+
end
|
87
|
+
end
|
51
88
|
end
|
52
89
|
|
53
90
|
module IoSpec
|