async-io 1.4.0 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/examples/broken_ssl.rb +14 -0
- data/lib/async/io/{wrap/tcp.rb → address.rb} +2 -37
- data/lib/async/io/endpoint.rb +126 -101
- data/lib/async/io/generic.rb +30 -2
- data/lib/async/io/protocol/line.rb +4 -0
- data/lib/async/io/socket.rb +44 -31
- data/lib/async/io/ssl_socket.rb +40 -8
- data/lib/async/io/standard.rb +29 -0
- data/lib/async/io/tcp_socket.rb +16 -1
- data/lib/async/io/unix_socket.rb +6 -5
- data/lib/async/io/version.rb +1 -1
- data/spec/async/io/c10k_spec.rb +1 -1
- data/spec/async/io/endpoint_spec.rb +3 -3
- data/spec/async/io/generic_examples.rb +24 -0
- data/spec/async/io/generic_spec.rb +6 -0
- data/spec/async/io/socket/tcp_spec.rb +17 -48
- data/spec/async/io/socket/udp_spec.rb +14 -24
- data/spec/async/io/socket_spec.rb +26 -0
- data/spec/async/io/ssl_server_spec.rb +60 -0
- data/spec/async/io/ssl_socket_spec.rb +22 -18
- data/spec/async/io/standard_spec.rb +45 -0
- data/spec/async/io/tcp_socket_spec.rb +8 -1
- data/spec/async/io/udp_socket_spec.rb +23 -21
- data/spec/async/io/unix_socket_spec.rb +23 -18
- data/spec/async/io/wrap/tcp_spec.rb +49 -13
- metadata +11 -3
data/lib/async/io/ssl_socket.rb
CHANGED
@@ -28,11 +28,42 @@ module Async
|
|
28
28
|
|
29
29
|
# Asynchronous TCP socket wrapper.
|
30
30
|
class SSLSocket < Generic
|
31
|
-
wraps ::OpenSSL::SSL::SSLSocket, :alpn_protocol, :cert, :cipher, :client_ca, :hostname=, :npn_protocol, :peer_cert, :peer_cert_chain, :pending, :post_connection_check, :session, :session=, :session_reused?, :ssl_version, :state
|
31
|
+
wraps ::OpenSSL::SSL::SSLSocket, :alpn_protocol, :cert, :cipher, :client_ca, :close, :context, :hostname, :hostname=, :npn_protocol, :peer_cert, :peer_cert_chain, :pending, :post_connection_check, :session, :session=, :session_reused?, :ssl_version, :state, :sync_close, :sync_close=, :sysclose, :verify_result, :tmp_key
|
32
32
|
|
33
33
|
wrap_blocking_method :accept, :accept_nonblock
|
34
34
|
wrap_blocking_method :connect, :connect_nonblock
|
35
35
|
|
36
|
+
alias syswrite write
|
37
|
+
alias sysread read
|
38
|
+
|
39
|
+
# It's hard to know what #to_io / #io should do. So, they are omitted.
|
40
|
+
|
41
|
+
def self.connect(socket, context, hostname = nil, &block)
|
42
|
+
client = self.wrap(socket, context)
|
43
|
+
|
44
|
+
# Used for SNI:
|
45
|
+
if hostname
|
46
|
+
client.hostname = hostname
|
47
|
+
end
|
48
|
+
|
49
|
+
begin
|
50
|
+
client.connect
|
51
|
+
rescue
|
52
|
+
# If the connection fails (e.g. certificates are invalid), the caller never sees the socket, so we close it and raise the exception up the chain.
|
53
|
+
client.close
|
54
|
+
|
55
|
+
raise
|
56
|
+
end
|
57
|
+
|
58
|
+
return client unless block_given?
|
59
|
+
|
60
|
+
begin
|
61
|
+
yield client
|
62
|
+
ensure
|
63
|
+
client.close
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
36
67
|
def local_address
|
37
68
|
@io.to_io.local_address
|
38
69
|
end
|
@@ -41,9 +72,8 @@ module Async
|
|
41
72
|
@io.to_io.remote_address
|
42
73
|
end
|
43
74
|
|
44
|
-
|
45
|
-
|
46
|
-
io = wrapped_klass.new(socket.to_io, context)
|
75
|
+
def self.wrap(socket, context)
|
76
|
+
io = @wrapped_klass.new(socket.to_io, context)
|
47
77
|
|
48
78
|
# This ensures that when the internal IO is closed, it also closes the internal socket:
|
49
79
|
io.sync_close = true
|
@@ -52,6 +82,7 @@ module Async
|
|
52
82
|
end
|
53
83
|
end
|
54
84
|
|
85
|
+
# We reimplement this from scratch because the native implementation doesn't expose the underlying server/context that we need to implement non-blocking accept.
|
55
86
|
class SSLServer
|
56
87
|
extend Forwardable
|
57
88
|
|
@@ -60,13 +91,11 @@ module Async
|
|
60
91
|
@context = context
|
61
92
|
end
|
62
93
|
|
63
|
-
def_delegators :@server, :local_address, :setsockopt, :getsockopt
|
94
|
+
def_delegators :@server, :local_address, :setsockopt, :getsockopt, :close
|
64
95
|
|
65
96
|
attr :server
|
66
97
|
attr :context
|
67
98
|
|
68
|
-
include ServerSocket
|
69
|
-
|
70
99
|
def listen(*args)
|
71
100
|
@server.listen(*args)
|
72
101
|
end
|
@@ -74,7 +103,7 @@ module Async
|
|
74
103
|
def accept(task: Task.current)
|
75
104
|
peer, address = @server.accept
|
76
105
|
|
77
|
-
wrapper = SSLSocket.
|
106
|
+
wrapper = SSLSocket.wrap(peer, @context)
|
78
107
|
|
79
108
|
return wrapper, address unless block_given?
|
80
109
|
|
@@ -82,6 +111,7 @@ module Async
|
|
82
111
|
task.annotate "accepting secure connection #{address}"
|
83
112
|
|
84
113
|
begin
|
114
|
+
# You want to do this in a nested async task or you might suffer from head-of-line blocking.
|
85
115
|
wrapper.accept
|
86
116
|
|
87
117
|
yield wrapper, address
|
@@ -92,6 +122,8 @@ module Async
|
|
92
122
|
end
|
93
123
|
end
|
94
124
|
end
|
125
|
+
|
126
|
+
include Server
|
95
127
|
end
|
96
128
|
end
|
97
129
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require_relative 'generic'
|
22
|
+
|
23
|
+
module Async
|
24
|
+
module IO
|
25
|
+
STDIN = Generic.new($stdin)
|
26
|
+
STDOUT = Generic.new($stdout)
|
27
|
+
STDERR = Generic.new($stderr)
|
28
|
+
end
|
29
|
+
end
|
data/lib/async/io/tcp_socket.rb
CHANGED
@@ -49,6 +49,10 @@ module Async
|
|
49
49
|
@buffer = Stream.new(self)
|
50
50
|
end
|
51
51
|
|
52
|
+
class << self
|
53
|
+
alias open new
|
54
|
+
end
|
55
|
+
|
52
56
|
attr :buffer
|
53
57
|
|
54
58
|
def_delegators :@buffer, :gets, :puts, :flush
|
@@ -72,8 +76,19 @@ module Async
|
|
72
76
|
|
73
77
|
wrapper = TCPSocket.new(peer)
|
74
78
|
|
75
|
-
return wrapper, address
|
79
|
+
return wrapper, address unless block_given?
|
80
|
+
|
81
|
+
begin
|
82
|
+
yield wrapper, address
|
83
|
+
ensure
|
84
|
+
wrapper.close
|
85
|
+
end
|
76
86
|
end
|
87
|
+
|
88
|
+
alias accept_nonblock accept
|
89
|
+
alias sysaccept accept
|
90
|
+
|
91
|
+
include Server
|
77
92
|
end
|
78
93
|
end
|
79
94
|
end
|
data/lib/async/io/unix_socket.rb
CHANGED
@@ -23,14 +23,12 @@ require_relative 'socket'
|
|
23
23
|
module Async
|
24
24
|
module IO
|
25
25
|
class UNIXSocket < BasicSocket
|
26
|
-
|
27
|
-
|
28
|
-
# TODO Currently unimplemented.
|
29
|
-
# , :send_io, :recv_io
|
26
|
+
# `send_io`, `recv_io` and `recvfrom` may block but no non-blocking implementation available.
|
27
|
+
wraps ::UNIXSocket, :path, :addr, :peeraddr, :send_io, :recv_io, :recvfrom
|
30
28
|
end
|
31
29
|
|
32
30
|
class UNIXServer < UNIXSocket
|
33
|
-
wraps ::UNIXServer
|
31
|
+
wraps ::UNIXServer, :listen
|
34
32
|
|
35
33
|
def accept
|
36
34
|
peer = async_send(:accept_nonblock)
|
@@ -44,6 +42,9 @@ module Async
|
|
44
42
|
wrapper.close
|
45
43
|
end
|
46
44
|
end
|
45
|
+
|
46
|
+
alias sysaccept accept
|
47
|
+
alias accept_nonblock accept
|
47
48
|
end
|
48
49
|
end
|
49
50
|
end
|
data/lib/async/io/version.rb
CHANGED
data/spec/async/io/c10k_spec.rb
CHANGED
@@ -27,7 +27,7 @@ RSpec.describe "echo client/server" do
|
|
27
27
|
# sudo sysctl -w net.inet.ip.portrange.hifirst=10000
|
28
28
|
# Probably due to the use of select.
|
29
29
|
|
30
|
-
let(:repeats) {10000}
|
30
|
+
let(:repeats) {RUBY_PLATFORM =~ /darwin/ ? 100 : 10000}
|
31
31
|
let(:server_address) {Async::IO::Address.tcp('0.0.0.0', 10102)}
|
32
32
|
|
33
33
|
def echo_server(server_address)
|
@@ -18,8 +18,8 @@
|
|
18
18
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
|
-
|
22
|
-
|
21
|
+
require 'async/io/endpoint'
|
22
|
+
require 'async/io/tcp_socket'
|
23
23
|
|
24
24
|
RSpec.describe Async::IO::Endpoint do
|
25
25
|
include_context Async::RSpec::Reactor
|
@@ -35,7 +35,7 @@ RSpec.describe Async::IO::Endpoint do
|
|
35
35
|
describe Async::IO::SocketEndpoint.new(TCPServer.new('0.0.0.0', 1234)) do
|
36
36
|
it "should bind to given socket" do
|
37
37
|
subject.bind do |server|
|
38
|
-
expect(server
|
38
|
+
expect(server).to be == subject.socket
|
39
39
|
end
|
40
40
|
end
|
41
41
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
|
2
|
+
RSpec.shared_examples Async::IO::Generic do |ignore_methods|
|
3
|
+
let(:instance_methods) {described_class.wrapped_klass.instance_methods(false) - (ignore_methods || [])}
|
4
|
+
let(:wrapped_instance_methods) {described_class.instance_methods}
|
5
|
+
|
6
|
+
it "should wrap a class" do
|
7
|
+
expect(described_class.wrapped_klass).to_not be_nil
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should wrap underlying instance methods" do
|
11
|
+
expect(wrapped_instance_methods.sort).to include(*instance_methods.sort)
|
12
|
+
end
|
13
|
+
|
14
|
+
# This needs to be reviewed in more detail.
|
15
|
+
#
|
16
|
+
# let(:singleton_methods) {described_class.wrapped_klass.singleton_methods(false)}
|
17
|
+
# let(:wrapped_singleton_methods) {described_class.singleton_methods(false)}
|
18
|
+
#
|
19
|
+
# it "should wrap underlying class methods" do
|
20
|
+
# singleton_methods.each do |method|
|
21
|
+
# expect(wrapped_singleton_methods).to include(method)
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
end
|
@@ -20,9 +20,15 @@
|
|
20
20
|
|
21
21
|
require 'async/io'
|
22
22
|
|
23
|
+
require_relative 'generic_examples'
|
24
|
+
|
23
25
|
RSpec.describe Async::IO::Generic do
|
24
26
|
include_context Async::RSpec::Reactor
|
25
27
|
|
28
|
+
it_should_behave_like Async::IO::Generic, [
|
29
|
+
:bytes, :chars, :codepoints, :each, :each_byte, :each_char, :each_codepoint, :each_line, :getbyte, :getc, :gets, :lineno, :lineno=, :lines, :print, :printf, :putc, :puts, :readbyte, :readchar, :readline, :readlines, :ungetbyte, :ungetc
|
30
|
+
]
|
31
|
+
|
26
32
|
let(:pipe) {IO.pipe}
|
27
33
|
let(:input) {Async::IO::Generic.new(pipe.first)}
|
28
34
|
let(:output) {Async::IO::Generic.new(pipe.last)}
|
@@ -20,16 +20,16 @@
|
|
20
20
|
|
21
21
|
require 'async/io/tcp_socket'
|
22
22
|
|
23
|
-
RSpec.describe Async::
|
24
|
-
include_context Async::RSpec::
|
23
|
+
RSpec.describe Async::IO::Socket do
|
24
|
+
include_context Async::RSpec::Reactor
|
25
25
|
|
26
26
|
# Shared port for localhost network tests.
|
27
27
|
let(:server_address) {Async::IO::Address.tcp("localhost", 6788)}
|
28
28
|
let(:data) {"The quick brown fox jumped over the lazy dog."}
|
29
29
|
|
30
|
-
|
30
|
+
let!(:server_task) do
|
31
31
|
# Accept a single incoming connection and then finish.
|
32
|
-
|
32
|
+
reactor.async do |task|
|
33
33
|
Async::IO::Socket.bind(server_address) do |server|
|
34
34
|
server.listen(10)
|
35
35
|
|
@@ -39,21 +39,14 @@ RSpec.describe Async::Reactor do
|
|
39
39
|
end
|
40
40
|
end
|
41
41
|
end
|
42
|
-
|
43
|
-
result = example.run
|
44
|
-
|
45
|
-
if result.is_a? Exception
|
46
|
-
result
|
47
|
-
else
|
48
|
-
subject.run
|
49
|
-
end
|
50
42
|
end
|
51
43
|
|
52
44
|
describe 'basic tcp server' do
|
53
45
|
it "should start server and send data" do
|
54
|
-
|
46
|
+
reactor.async do
|
55
47
|
Async::IO::Socket.connect(server_address) do |client|
|
56
48
|
client.write(data)
|
49
|
+
|
57
50
|
expect(client.read(512)).to be == data
|
58
51
|
end
|
59
52
|
end
|
@@ -62,53 +55,29 @@ RSpec.describe Async::Reactor do
|
|
62
55
|
|
63
56
|
describe 'non-blocking tcp connect' do
|
64
57
|
it "should start server and send data" do
|
65
|
-
|
58
|
+
reactor.async do |task|
|
66
59
|
Async::IO::Socket.connect(server_address) do |client|
|
67
60
|
client.write(data)
|
61
|
+
|
68
62
|
expect(client.read(512)).to be == data
|
69
63
|
end
|
70
64
|
end
|
71
65
|
end
|
72
66
|
|
73
67
|
it "can connect socket and read/write in a different task" do
|
74
|
-
|
75
|
-
|
76
|
-
subject.async do |task|
|
68
|
+
reactor.async do |task|
|
77
69
|
socket = Async::IO::Socket.connect(server_address)
|
78
70
|
|
79
|
-
|
80
|
-
subject.stop
|
81
|
-
end
|
82
|
-
|
83
|
-
subject.run
|
84
|
-
|
85
|
-
expect(socket).to_not be_nil
|
86
|
-
expect(socket).to be_kind_of Async::Wrapper
|
87
|
-
|
88
|
-
subject.async do
|
89
|
-
socket.write(data)
|
90
|
-
|
91
|
-
expect(socket.read(512)).to be == data
|
92
|
-
end
|
93
|
-
|
94
|
-
subject.run
|
95
|
-
|
96
|
-
socket.close
|
97
|
-
end
|
98
|
-
|
99
|
-
it "can't use a socket in nested tasks" do
|
100
|
-
subject.async do |task|
|
101
|
-
socket = Async::IO::Socket.connect(server_address)
|
71
|
+
expect(socket).to_not be_nil
|
102
72
|
expect(socket).to be_kind_of Async::Wrapper
|
103
73
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
socket.close
|
74
|
+
reactor.async do
|
75
|
+
socket.write(data)
|
76
|
+
|
77
|
+
expect(socket.read(512)).to be == data
|
78
|
+
|
79
|
+
socket.close
|
80
|
+
end
|
112
81
|
end
|
113
82
|
end
|
114
83
|
end
|
@@ -20,24 +20,26 @@
|
|
20
20
|
|
21
21
|
require 'async/io/udp_socket'
|
22
22
|
|
23
|
-
RSpec.describe Async::
|
24
|
-
include_context Async::RSpec::
|
23
|
+
RSpec.describe Async::IO::Socket do
|
24
|
+
include_context Async::RSpec::Reactor
|
25
25
|
|
26
26
|
# Shared port for localhost network tests.
|
27
27
|
let(:server_address) {Async::IO::Address.udp("127.0.0.1", 6778)}
|
28
28
|
let(:data) {"The quick brown fox jumped over the lazy dog."}
|
29
29
|
|
30
|
+
let!(:server_task) do
|
31
|
+
reactor.async do
|
32
|
+
Async::IO::Socket.bind(server_address) do |server|
|
33
|
+
packet, address = server.recvfrom(512)
|
34
|
+
|
35
|
+
server.send(packet, 0, address)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
30
40
|
describe 'basic udp server' do
|
31
41
|
it "should echo data back to peer" do
|
32
|
-
|
33
|
-
Async::IO::Socket.bind(server_address) do |server|
|
34
|
-
packet, address = server.recvfrom(512)
|
35
|
-
|
36
|
-
server.send(packet, 0, address)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
subject.async do
|
42
|
+
reactor.async do
|
41
43
|
Async::IO::Socket.connect(server_address) do |client|
|
42
44
|
client.send(data)
|
43
45
|
response = client.recv(512)
|
@@ -45,20 +47,10 @@ RSpec.describe Async::Reactor do
|
|
45
47
|
expect(response).to be == data
|
46
48
|
end
|
47
49
|
end
|
48
|
-
|
49
|
-
subject.run
|
50
50
|
end
|
51
51
|
|
52
52
|
it "should use unconnected socket" do
|
53
|
-
|
54
|
-
Async::IO::Socket.bind(server_address) do |server|
|
55
|
-
packet, address = server.recvfrom(512)
|
56
|
-
|
57
|
-
server.send(packet, 0, address)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
subject.async do
|
53
|
+
reactor.async do
|
62
54
|
Async::IO::UDPSocket.wrap(server_address.afamily) do |client|
|
63
55
|
client.send(data, 0, server_address)
|
64
56
|
response, address = client.recvfrom(512)
|
@@ -66,8 +58,6 @@ RSpec.describe Async::Reactor do
|
|
66
58
|
expect(response).to be == data
|
67
59
|
end
|
68
60
|
end
|
69
|
-
|
70
|
-
subject.run
|
71
61
|
end
|
72
62
|
end
|
73
63
|
end
|