io-endpoint 0.3.0 → 0.5.0
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
- checksums.yaml.gz.sig +0 -0
- data/lib/io/connected.rb +27 -0
- data/lib/io/endpoint/bound_endpoint.rb +86 -0
- data/lib/io/endpoint/composite_endpoint.rb +4 -3
- data/lib/io/endpoint/connected_endpoint.rb +69 -0
- data/lib/io/endpoint/generic.rb +29 -19
- data/lib/io/endpoint/shared_endpoint.rb +3 -132
- data/lib/io/endpoint/ssl_endpoint.rb +52 -34
- data/lib/io/endpoint/version.rb +1 -1
- data/lib/io/endpoint/wrapper.rb +80 -58
- data/lib/io/endpoint.rb +1 -0
- data.tar.gz.sig +0 -0
- metadata +5 -2
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 204ed5172990d0df3a5d02e63b8e75d8caaf161808fea4e99b67f0d422241cc6
|
4
|
+
data.tar.gz: 7ba7e7052949565c4cb327076c493db15d6f3057493f93d4210ec5ac5bc7808b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 12aa8f963bc0787de988e9efcf80fbff38a857e8130e663ed8c329402048ab9e1940dcc2637f17de2f77f43d9222914808b5ec89e91420ed0d07475dbabc9d0e
|
7
|
+
data.tar.gz: ce830974878205da1f7e91378af0bdb05f1a4017cc60715c2dca5607573992f38d97712e6b879d99ab31db95a5b12c40ab3df959da12cc56f4b2189dd37b314e
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/lib/io/connected.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
class IO
|
4
|
+
def connected?
|
5
|
+
!closed?
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class Socket
|
10
|
+
def connected?
|
11
|
+
# Is it likely that the socket is still connected?
|
12
|
+
# May return false positive, but won't return false negative.
|
13
|
+
return false unless super
|
14
|
+
|
15
|
+
# If we can wait for the socket to become readable, we know that the socket may still be open.
|
16
|
+
result = to_io.recv_nonblock(1, MSG_PEEK, exception: false)
|
17
|
+
|
18
|
+
# No data was available - newer Ruby can return nil instead of empty string:
|
19
|
+
return false if result.nil?
|
20
|
+
|
21
|
+
# Either there was some data available, or we can wait to see if there is data avaialble.
|
22
|
+
return !result.empty? || result == :wait_readable
|
23
|
+
rescue Errno::ECONNRESET
|
24
|
+
# This might be thrown by recv_nonblock.
|
25
|
+
return false
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2023, by Samuel Williams.
|
5
|
+
|
6
|
+
require_relative 'generic'
|
7
|
+
require_relative 'composite_endpoint'
|
8
|
+
require_relative 'address_endpoint'
|
9
|
+
|
10
|
+
module IO::Endpoint
|
11
|
+
class BoundEndpoint < Generic
|
12
|
+
def self.bound(endpoint, backlog: Socket::SOMAXCONN, close_on_exec: false)
|
13
|
+
sockets = endpoint.bind
|
14
|
+
|
15
|
+
sockets.each do |server|
|
16
|
+
# This is somewhat optional. We want to have a generic interface as much as possible so that users of this interface can just call it without knowing a lot of internal details. Therefore, we ignore errors here if it's because the underlying socket does not support the operation.
|
17
|
+
begin
|
18
|
+
server.listen(backlog)
|
19
|
+
rescue Errno::EOPNOTSUPP
|
20
|
+
# Ignore.
|
21
|
+
end
|
22
|
+
|
23
|
+
server.close_on_exec = close_on_exec
|
24
|
+
end
|
25
|
+
|
26
|
+
return self.new(endpoint, sockets, **endpoint.options)
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(endpoint, sockets, **options)
|
30
|
+
super(**options)
|
31
|
+
|
32
|
+
@endpoint = endpoint
|
33
|
+
@sockets = sockets
|
34
|
+
end
|
35
|
+
|
36
|
+
attr :endpoint
|
37
|
+
attr :sockets
|
38
|
+
|
39
|
+
# A endpoint for the local end of the bound socket.
|
40
|
+
# @returns [CompositeEndpoint] A composite endpoint for the local end of the bound socket.
|
41
|
+
def local_address_endpoint(**options)
|
42
|
+
endpoints = @sockets.map do |socket|
|
43
|
+
AddressEndpoint.new(socket.to_io.local_address, **options)
|
44
|
+
end
|
45
|
+
|
46
|
+
return CompositeEndpoint.new(endpoints)
|
47
|
+
end
|
48
|
+
|
49
|
+
# A endpoint for the remote end of the bound socket.
|
50
|
+
# @returns [CompositeEndpoint] A composite endpoint for the remote end of the bound socket.
|
51
|
+
def remote_address_endpoint(**options)
|
52
|
+
endpoints = @sockets.map do |wrapper|
|
53
|
+
AddressEndpoint.new(socket.to_io.remote_address, **options)
|
54
|
+
end
|
55
|
+
|
56
|
+
return CompositeEndpoint.new(endpoints)
|
57
|
+
end
|
58
|
+
|
59
|
+
def close
|
60
|
+
@sockets.each(&:close)
|
61
|
+
@sockets.clear
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_s
|
65
|
+
"\#<#{self.class} #{@sockets.size} bound sockets for #{@endpoint}>"
|
66
|
+
end
|
67
|
+
|
68
|
+
def bind(wrapper = Wrapper.default, &block)
|
69
|
+
@sockets.map do |server|
|
70
|
+
if block_given?
|
71
|
+
wrapper.async do
|
72
|
+
yield server
|
73
|
+
end
|
74
|
+
else
|
75
|
+
server.dup
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class Generic
|
82
|
+
def bound(**options)
|
83
|
+
BoundEndpoint.bound(self, **options)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -6,6 +6,7 @@
|
|
6
6
|
require_relative 'generic'
|
7
7
|
|
8
8
|
module IO::Endpoint
|
9
|
+
# A composite endpoint is a collection of endpoints that are used in order.
|
9
10
|
class CompositeEndpoint < Generic
|
10
11
|
def initialize(endpoints, **options)
|
11
12
|
super(**options)
|
@@ -18,12 +19,12 @@ module IO::Endpoint
|
|
18
19
|
end
|
19
20
|
end
|
20
21
|
|
21
|
-
def connect(&block)
|
22
|
+
def connect(wrapper = Wrapper.default, &block)
|
22
23
|
last_error = nil
|
23
24
|
|
24
25
|
@endpoints.each do |endpoint|
|
25
26
|
begin
|
26
|
-
return endpoint.connect(&block)
|
27
|
+
return endpoint.connect(wrapper, &block)
|
27
28
|
rescue => last_error
|
28
29
|
end
|
29
30
|
end
|
@@ -31,7 +32,7 @@ module IO::Endpoint
|
|
31
32
|
raise last_error
|
32
33
|
end
|
33
34
|
|
34
|
-
def bind(&block)
|
35
|
+
def bind(wrapper = Wrapper.default, &block)
|
35
36
|
if block_given?
|
36
37
|
@endpoints.each do |endpoint|
|
37
38
|
endpoint.bind(&block)
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2023, by Samuel Williams.
|
5
|
+
|
6
|
+
require_relative 'generic'
|
7
|
+
require_relative 'composite_endpoint'
|
8
|
+
require_relative 'socket_endpoint'
|
9
|
+
|
10
|
+
require 'openssl'
|
11
|
+
|
12
|
+
module IO::Endpoint
|
13
|
+
class ConnectedEndpoint < Generic
|
14
|
+
def self.connected(endpoint, close_on_exec: false)
|
15
|
+
socket = endpoint.connect
|
16
|
+
|
17
|
+
socket.close_on_exec = close_on_exec
|
18
|
+
|
19
|
+
return self.new(endpoint, socket, **endpoint.options)
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(endpoint, socket, **options)
|
23
|
+
super(**options)
|
24
|
+
|
25
|
+
@endpoint = endpoint
|
26
|
+
@socket = socket
|
27
|
+
end
|
28
|
+
|
29
|
+
attr :endpoint
|
30
|
+
attr :socket
|
31
|
+
|
32
|
+
# A endpoint for the local end of the bound socket.
|
33
|
+
# @returns [AddressEndpoint] A endpoint for the local end of the connected socket.
|
34
|
+
def local_address_endpoint(**options)
|
35
|
+
AddressEndpoint.new(socket.to_io.local_address, **options)
|
36
|
+
end
|
37
|
+
|
38
|
+
# A endpoint for the remote end of the bound socket.
|
39
|
+
# @returns [AddressEndpoint] A endpoint for the remote end of the connected socket.
|
40
|
+
def remote_address_endpoint(**options)
|
41
|
+
AddressEndpoint.new(socket.to_io.remote_address, **options)
|
42
|
+
end
|
43
|
+
|
44
|
+
def connect(wrapper = Wrapper.default, &block)
|
45
|
+
if block_given?
|
46
|
+
yield @socket
|
47
|
+
else
|
48
|
+
return @socket.dup
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def close
|
53
|
+
if @socket
|
54
|
+
@socket.close
|
55
|
+
@socket = nil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_s
|
60
|
+
"\#<#{self.class} #{@socket} connected for #{@endpoint}>"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class Generic
|
65
|
+
def connected(**options)
|
66
|
+
ConnectedEndpoint.connected(self, **options)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/io/endpoint/generic.rb
CHANGED
@@ -59,31 +59,41 @@ module IO::Endpoint
|
|
59
59
|
@options[:local_address]
|
60
60
|
end
|
61
61
|
|
62
|
-
#
|
63
|
-
# @
|
62
|
+
# Bind a socket to the given address. If a block is given, the socket will be automatically closed when the block exits.
|
63
|
+
# @parameter wrapper [Wrapper] The wrapper to use for binding.
|
64
|
+
# @yields {|socket| ...} An optional block which will be passed the socket.
|
65
|
+
# @parameter socket [Socket] The socket which has been bound.
|
66
|
+
# @returns [Array(Socket)] the bound socket
|
67
|
+
def bind(wrapper = Wrapper.default, &block)
|
68
|
+
raise NotImplementedError
|
69
|
+
end
|
70
|
+
|
71
|
+
# Connects a socket to the given address. If a block is given, the socket will be automatically closed when the block exits.
|
72
|
+
# @parameter wrapper [Wrapper] The wrapper to use for connecting.
|
73
|
+
# @return [Socket] the connected socket
|
74
|
+
def connect(wrapper = Wrapper.default, &block)
|
75
|
+
raise NotImplementedError
|
76
|
+
end
|
77
|
+
|
78
|
+
# Bind and accept connections on the given address.
|
79
|
+
# @parameter wrapper [Wrapper] The wrapper to use for accepting connections.
|
80
|
+
# @yields [Socket] The accepted socket.
|
81
|
+
def accept(wrapper = Wrapper.default, &block)
|
82
|
+
bind(wrapper) do |server|
|
83
|
+
wrapper.accept(server, **@options, &block)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Enumerate all discrete paths as endpoints.
|
88
|
+
# @yields {|endpoint| ...} A block which will be passed each endpoint.
|
89
|
+
# @parameter endpoint [Endpoint] The endpoint.
|
64
90
|
def each
|
65
91
|
return to_enum unless block_given?
|
66
92
|
|
67
93
|
yield self
|
68
94
|
end
|
69
95
|
|
70
|
-
#
|
71
|
-
# @param backlog [Integer] the number of connections to listen for.
|
72
|
-
def accept(backlog: Socket::SOMAXCONN, &block)
|
73
|
-
bind do |server|
|
74
|
-
server.listen(backlog) if backlog
|
75
|
-
|
76
|
-
while true
|
77
|
-
socket, address = server.accept
|
78
|
-
|
79
|
-
Fiber.schedule do
|
80
|
-
yield socket, address
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
# Create an Endpoint instance by URI scheme. The host and port of the URI will be passed to the Endpoint factory method, along with any options.\
|
96
|
+
# Create an Endpoint instance by URI scheme. The host and port of the URI will be passed to the Endpoint factory method, along with any options.
|
87
97
|
#
|
88
98
|
# You should not use untrusted input as it may execute arbitrary code.
|
89
99
|
#
|
@@ -1,136 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright,
|
4
|
+
# Copyright, 2024, by Samuel Williams.
|
5
5
|
|
6
|
-
require_relative '
|
7
|
-
require_relative '
|
8
|
-
require_relative 'socket_endpoint'
|
9
|
-
|
10
|
-
require 'openssl'
|
11
|
-
|
12
|
-
module IO::Endpoint
|
13
|
-
# Pre-connect and pre-bind sockets so that it can be used between processes.
|
14
|
-
class SharedEndpoint < Generic
|
15
|
-
# Create a new `SharedEndpoint` by binding to the given endpoint.
|
16
|
-
def self.bound(endpoint, backlog: Socket::SOMAXCONN, close_on_exec: false, **options)
|
17
|
-
sockets = endpoint.bind(**options)
|
18
|
-
|
19
|
-
sockets.each do |server|
|
20
|
-
# This is somewhat optional. We want to have a generic interface as much as possible so that users of this interface can just call it without knowing a lot of internal details. Therefore, we ignore errors here if it's because the underlying socket does not support the operation.
|
21
|
-
begin
|
22
|
-
server.listen(backlog)
|
23
|
-
rescue Errno::EOPNOTSUPP
|
24
|
-
# Ignore.
|
25
|
-
end
|
26
|
-
|
27
|
-
server.close_on_exec = close_on_exec
|
28
|
-
end
|
29
|
-
|
30
|
-
return self.new(endpoint, sockets)
|
31
|
-
end
|
32
|
-
|
33
|
-
# Create a new `SharedEndpoint` by connecting to the given endpoint.
|
34
|
-
def self.connected(endpoint, close_on_exec: false)
|
35
|
-
wrapper = endpoint.connect
|
36
|
-
|
37
|
-
wrapper.close_on_exec = close_on_exec
|
38
|
-
|
39
|
-
return self.new(endpoint, [wrapper])
|
40
|
-
end
|
41
|
-
|
42
|
-
def initialize(endpoint, sockets, **options)
|
43
|
-
super(**options)
|
44
|
-
|
45
|
-
raise TypeError, "sockets must be an Array" unless sockets.is_a?(Array)
|
46
|
-
|
47
|
-
@endpoint = endpoint
|
48
|
-
@sockets = sockets
|
49
|
-
end
|
50
|
-
|
51
|
-
attr :endpoint
|
52
|
-
attr :sockets
|
53
|
-
|
54
|
-
def local_address_endpoint(**options)
|
55
|
-
endpoints = @sockets.map do |wrapper|
|
56
|
-
AddressEndpoint.new(wrapper.to_io.local_address)
|
57
|
-
end
|
58
|
-
|
59
|
-
return CompositeEndpoint.new(endpoints, **options)
|
60
|
-
end
|
61
|
-
|
62
|
-
def remote_address_endpoint(**options)
|
63
|
-
endpoints = @sockets.map do |wrapper|
|
64
|
-
AddressEndpoint.new(wrapper.to_io.remote_address)
|
65
|
-
end
|
66
|
-
|
67
|
-
return CompositeEndpoint.new(endpoints, **options)
|
68
|
-
end
|
69
|
-
|
70
|
-
# Close all the internal sockets.
|
71
|
-
def close
|
72
|
-
@sockets.each(&:close)
|
73
|
-
@sockets.clear
|
74
|
-
end
|
75
|
-
|
76
|
-
def each(&block)
|
77
|
-
return to_enum unless block_given?
|
78
|
-
|
79
|
-
@sockets.each do |socket|
|
80
|
-
yield SocketEndpoint.new(socket.dup)
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
def bind(wrapper = Wrapper.default, &block)
|
85
|
-
@sockets.each.map do |server|
|
86
|
-
server = server.dup
|
87
|
-
|
88
|
-
if block_given?
|
89
|
-
wrapper.async do
|
90
|
-
begin
|
91
|
-
yield server
|
92
|
-
ensure
|
93
|
-
server.close
|
94
|
-
end
|
95
|
-
end
|
96
|
-
else
|
97
|
-
server
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
def connect(wrapper = Wrapper.default, &block)
|
103
|
-
@sockets.each do |socket|
|
104
|
-
socket = socket.dup
|
105
|
-
|
106
|
-
return socket unless block_given?
|
107
|
-
|
108
|
-
begin
|
109
|
-
return yield(socket)
|
110
|
-
ensure
|
111
|
-
socket.close
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
def accept(**options, &block)
|
117
|
-
bind do |server|
|
118
|
-
server.accept(&block)
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
def to_s
|
123
|
-
"\#<#{self.class} #{@sockets.size} descriptors for #{@endpoint}>"
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
class Generic
|
128
|
-
def bound(**options)
|
129
|
-
SharedEndpoint.bound(self, **options)
|
130
|
-
end
|
131
|
-
|
132
|
-
def connected(**options)
|
133
|
-
SharedEndpoint.connected(self, **options)
|
134
|
-
end
|
135
|
-
end
|
136
|
-
end
|
6
|
+
require_relative 'bound_endpoint'
|
7
|
+
require_relative 'connected_endpoint'
|
@@ -8,34 +8,52 @@ require_relative 'generic'
|
|
8
8
|
|
9
9
|
require 'openssl'
|
10
10
|
|
11
|
-
module OpenSSL
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
11
|
+
module OpenSSL
|
12
|
+
module SSL
|
13
|
+
module SocketForwarder
|
14
|
+
unless method_defined?(:close_on_exec=)
|
15
|
+
def close_on_exec=(value)
|
16
|
+
to_io.close_on_exec = value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
unless method_defined?(:close_on_exec)
|
21
|
+
def local_address
|
22
|
+
to_io.local_address
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
unless method_defined?(:wait)
|
27
|
+
def wait(*arguments)
|
28
|
+
to_io.wait(*arguments)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
unless method_defined?(:wait_readable)
|
33
|
+
def wait_readable(*arguments)
|
34
|
+
to_io.wait_readable(*arguments)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
unless method_defined?(:wait_writable)
|
39
|
+
def wait_writable(*arguments)
|
40
|
+
to_io.wait_writable(*arguments)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
if IO.method_defined?(:timeout)
|
45
|
+
unless method_defined?(:timeout)
|
46
|
+
def timeout
|
47
|
+
to_io.timeout
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
unless method_defined?(:timeout=)
|
52
|
+
def timeout=(value)
|
53
|
+
to_io.timeout = value
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
39
57
|
end
|
40
58
|
end
|
41
59
|
end
|
@@ -91,14 +109,14 @@ module IO::Endpoint
|
|
91
109
|
# Connect to the underlying endpoint and establish a SSL connection.
|
92
110
|
# @yield [Socket] the socket which is being connected
|
93
111
|
# @return [Socket] the connected socket
|
94
|
-
def bind
|
112
|
+
def bind(*arguments, **options, &block)
|
95
113
|
if block_given?
|
96
|
-
@endpoint.bind do |server|
|
97
|
-
yield ::OpenSSL::SSL::SSLServer.new(server, context)
|
114
|
+
@endpoint.bind(*arguments, **options) do |server|
|
115
|
+
yield ::OpenSSL::SSL::SSLServer.new(server, self.context)
|
98
116
|
end
|
99
117
|
else
|
100
|
-
@endpoint.bind.map do |server|
|
101
|
-
::OpenSSL::SSL::SSLServer.new(server, context)
|
118
|
+
@endpoint.bind(*arguments, **options).map do |server|
|
119
|
+
::OpenSSL::SSL::SSLServer.new(server, self.context)
|
102
120
|
end
|
103
121
|
end
|
104
122
|
end
|
@@ -107,7 +125,7 @@ module IO::Endpoint
|
|
107
125
|
# @yield [Socket] the socket which is being connected
|
108
126
|
# @return [Socket] the connected socket
|
109
127
|
def connect(&block)
|
110
|
-
socket = ::OpenSSL::SSL::SSLSocket.new(@endpoint.connect, context)
|
128
|
+
socket = ::OpenSSL::SSL::SSLSocket.new(@endpoint.connect, self.context)
|
111
129
|
|
112
130
|
if hostname = self.hostname
|
113
131
|
socket.hostname = hostname
|
data/lib/io/endpoint/version.rb
CHANGED
data/lib/io/endpoint/wrapper.rb
CHANGED
@@ -30,7 +30,6 @@ module IO::Endpoint
|
|
30
30
|
# On Darwin, sometimes occurs when the connection is not yet fully formed. Empirically, TCP_NODELAY is enabled despite this result.
|
31
31
|
rescue Errno::EOPNOTSUPP
|
32
32
|
# Some platforms may simply not support the operation.
|
33
|
-
# Console.logger.warn(self) {"Unable to set sync=#{value}!"}
|
34
33
|
rescue Errno::ENOPROTOOPT
|
35
34
|
# It may not be supported by the protocol (e.g. UDP). ¯\_(ツ)_/¯
|
36
35
|
end
|
@@ -39,50 +38,30 @@ module IO::Endpoint
|
|
39
38
|
raise NotImplementedError
|
40
39
|
end
|
41
40
|
|
42
|
-
# Build and wrap the underlying io.
|
43
|
-
# @option reuse_port [Boolean] Allow this port to be bound in multiple processes.
|
44
|
-
# @option reuse_address [Boolean] Allow this port to be bound in multiple processes.
|
45
|
-
# @option linger [Boolean] Wait for data to be sent before closing the socket.
|
46
|
-
# @option buffered [Boolean] Enable or disable Nagle's algorithm for TCP sockets.
|
47
|
-
def build(*arguments, timeout: nil, reuse_address: true, reuse_port: nil, linger: nil, buffered: false)
|
48
|
-
socket = ::Socket.new(*arguments)
|
49
|
-
|
50
|
-
# Set the timeout:
|
51
|
-
if timeout
|
52
|
-
set_timeout(socket, timeout)
|
53
|
-
end
|
54
|
-
|
55
|
-
if reuse_address
|
56
|
-
socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
|
57
|
-
end
|
58
|
-
|
59
|
-
if reuse_port
|
60
|
-
socket.setsockopt(SOL_SOCKET, SO_REUSEPORT, 1)
|
61
|
-
end
|
62
|
-
|
63
|
-
if linger
|
64
|
-
socket.setsockopt(SOL_SOCKET, SO_LINGER, 1)
|
65
|
-
end
|
66
|
-
|
67
|
-
if buffered == false
|
68
|
-
set_buffered(socket, buffered)
|
69
|
-
end
|
70
|
-
|
71
|
-
yield socket if block_given?
|
72
|
-
|
73
|
-
return socket
|
74
|
-
rescue
|
75
|
-
socket&.close
|
76
|
-
raise
|
77
|
-
end
|
78
|
-
|
79
41
|
# Establish a connection to a given `remote_address`.
|
80
42
|
# @example
|
81
43
|
# socket = Async::IO::Socket.connect(Async::IO::Address.tcp("8.8.8.8", 53))
|
82
|
-
# @
|
83
|
-
# @
|
84
|
-
|
85
|
-
|
44
|
+
# @parameter remote_address [Address] The remote address to connect to.
|
45
|
+
# @parameter linger [Boolean] Wait for data to be sent before closing the socket.
|
46
|
+
# @parameter local_address [Address] The local address to bind to before connecting.
|
47
|
+
def connect(remote_address, local_address: nil, linger: nil, timeout: nil, buffered: false, **options)
|
48
|
+
socket = nil
|
49
|
+
|
50
|
+
begin
|
51
|
+
socket = ::Socket.new(remote_address.afamily, remote_address.socktype, remote_address.protocol)
|
52
|
+
|
53
|
+
if linger
|
54
|
+
socket.setsockopt(SOL_SOCKET, SO_LINGER, 1)
|
55
|
+
end
|
56
|
+
|
57
|
+
if buffered == false
|
58
|
+
set_buffered(socket, buffered)
|
59
|
+
end
|
60
|
+
|
61
|
+
if timeout
|
62
|
+
set_timeout(socket, timeout)
|
63
|
+
end
|
64
|
+
|
86
65
|
if local_address
|
87
66
|
if defined?(IP_BIND_ADDRESS_NO_PORT)
|
88
67
|
# Inform the kernel (Linux 4.2+) to not reserve an ephemeral port when using bind(2) with a port number of 0. The port will later be automatically chosen at connect(2) time, in a way that allows sharing a source port as long as the 4-tuple is unique.
|
@@ -91,6 +70,9 @@ module IO::Endpoint
|
|
91
70
|
|
92
71
|
socket.bind(local_address.to_sockaddr)
|
93
72
|
end
|
73
|
+
rescue
|
74
|
+
socket&.close
|
75
|
+
raise
|
94
76
|
end
|
95
77
|
|
96
78
|
begin
|
@@ -109,14 +91,48 @@ module IO::Endpoint
|
|
109
91
|
end
|
110
92
|
end
|
111
93
|
|
94
|
+
# JRuby requires ServerSocket
|
95
|
+
if defined?(::ServerSocket)
|
96
|
+
ServerSocket = ::ServerSocket
|
97
|
+
else
|
98
|
+
ServerSocket = ::Socket
|
99
|
+
end
|
100
|
+
|
112
101
|
# Bind to a local address.
|
113
102
|
# @example
|
114
103
|
# socket = Async::IO::Socket.bind(Async::IO::Address.tcp("0.0.0.0", 9090))
|
115
|
-
# @
|
116
|
-
# @
|
117
|
-
|
118
|
-
|
104
|
+
# @parameter local_address [Address] The local address to bind to.
|
105
|
+
# @parameter reuse_port [Boolean] Allow this port to be bound in multiple processes.
|
106
|
+
# @parameter reuse_address [Boolean] Allow this port to be bound in multiple processes.
|
107
|
+
# @parameter linger [Boolean] Wait for data to be sent before closing the socket.
|
108
|
+
# @parameter protocol [Integer] The socket protocol to use.
|
109
|
+
def bind(local_address, protocol: 0, reuse_address: true, reuse_port: nil, linger: nil, bound_timeout: nil, **options, &block)
|
110
|
+
socket = nil
|
111
|
+
|
112
|
+
begin
|
113
|
+
socket = ServerSocket.new(local_address.afamily, local_address.socktype, protocol)
|
114
|
+
|
115
|
+
if reuse_address
|
116
|
+
socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
|
117
|
+
end
|
118
|
+
|
119
|
+
if reuse_port
|
120
|
+
socket.setsockopt(SOL_SOCKET, SO_REUSEPORT, 1)
|
121
|
+
end
|
122
|
+
|
123
|
+
if linger
|
124
|
+
socket.setsockopt(SOL_SOCKET, SO_LINGER, 1)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Set the timeout:
|
128
|
+
if bound_timeout
|
129
|
+
set_timeout(socket, bound_timeout)
|
130
|
+
end
|
131
|
+
|
119
132
|
socket.bind(local_address.to_sockaddr)
|
133
|
+
rescue
|
134
|
+
socket&.close
|
135
|
+
raise
|
120
136
|
end
|
121
137
|
|
122
138
|
return socket unless block_given?
|
@@ -131,14 +147,14 @@ module IO::Endpoint
|
|
131
147
|
end
|
132
148
|
|
133
149
|
# Bind to a local address and accept connections in a loop.
|
134
|
-
def accept(
|
135
|
-
|
136
|
-
server.
|
150
|
+
def accept(server, timeout: nil, &block)
|
151
|
+
while true
|
152
|
+
socket, address = server.accept
|
153
|
+
|
154
|
+
set_timeout(socket, timeout) if timeout != false
|
137
155
|
|
138
156
|
async do
|
139
|
-
|
140
|
-
server.accept(&block)
|
141
|
-
end
|
157
|
+
yield socket, address
|
142
158
|
end
|
143
159
|
end
|
144
160
|
end
|
@@ -146,20 +162,26 @@ module IO::Endpoint
|
|
146
162
|
|
147
163
|
class ThreadWrapper < Wrapper
|
148
164
|
def async(&block)
|
149
|
-
Thread.new(&block)
|
165
|
+
::Thread.new(&block)
|
150
166
|
end
|
151
167
|
end
|
152
168
|
|
153
169
|
class FiberWrapper < Wrapper
|
154
170
|
def async(&block)
|
155
|
-
Fiber.schedule(&block)
|
171
|
+
::Fiber.schedule(&block)
|
156
172
|
end
|
157
173
|
end
|
158
174
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
175
|
+
if Fiber.respond_to?(:scheduler)
|
176
|
+
def Wrapper.default
|
177
|
+
if Fiber.scheduler
|
178
|
+
FiberWrapper.new
|
179
|
+
else
|
180
|
+
ThreadWrapper.new
|
181
|
+
end
|
182
|
+
end
|
183
|
+
else
|
184
|
+
def Wrapper.default
|
163
185
|
ThreadWrapper.new
|
164
186
|
end
|
165
187
|
end
|
data/lib/io/endpoint.rb
CHANGED
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: io-endpoint
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
@@ -37,7 +37,7 @@ cert_chain:
|
|
37
37
|
Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
|
38
38
|
voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
|
39
39
|
-----END CERTIFICATE-----
|
40
|
-
date:
|
40
|
+
date: 2024-01-25 00:00:00.000000000 Z
|
41
41
|
dependencies: []
|
42
42
|
description:
|
43
43
|
email:
|
@@ -45,9 +45,12 @@ executables: []
|
|
45
45
|
extensions: []
|
46
46
|
extra_rdoc_files: []
|
47
47
|
files:
|
48
|
+
- lib/io/connected.rb
|
48
49
|
- lib/io/endpoint.rb
|
49
50
|
- lib/io/endpoint/address_endpoint.rb
|
51
|
+
- lib/io/endpoint/bound_endpoint.rb
|
50
52
|
- lib/io/endpoint/composite_endpoint.rb
|
53
|
+
- lib/io/endpoint/connected_endpoint.rb
|
51
54
|
- lib/io/endpoint/generic.rb
|
52
55
|
- lib/io/endpoint/host_endpoint.rb
|
53
56
|
- lib/io/endpoint/shared_endpoint.rb
|
metadata.gz.sig
CHANGED
Binary file
|