io-endpoint 0.2.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cbc52bab1fde2330c0b971cf987db15dc5865f9f3ce72a130121db97d00671ba
4
- data.tar.gz: a7503b553781fd02e86a082015e82f1c5af24b5fa3a735dbdeb7415498901da8
3
+ metadata.gz: 10203d535af830bf9ff71034262b5851ec323024a9de4db527f061cb1cd76102
4
+ data.tar.gz: b150061a0414c379afb7fb5f2c142767afc5ed1822e5b106b78ece023d1cf459
5
5
  SHA512:
6
- metadata.gz: 40343e43190bd695c0152fb5ec45946efe281245eb033eacae1eeda9291c37b4bc975dffac6db9d007b060bf9d375cdf3beed31f70a81f344c65f17b6772bff4
7
- data.tar.gz: 40a34915f55409d96fddfaea9925c71c5bcddee36c0f6a6cc001982ec1e884a7694a3f4d89aad534353df3c182eb9b94f618619fdec9a77fc36d723620b5af09
6
+ metadata.gz: 82930d72359dee91627b19c771f79c9f2c6cc43dc93b9514a372a4568d1fb92c653ff2da1403c8519eddc4470dc14ef2356abe41cd202fba80bc35ba10e2d053
7
+ data.tar.gz: 556e87a95eb514e643a3e6bb40468ac851cceea5da6fb6f72db37767dca18882bb9b63e61e05d2e69629a1e4eef1859c3a2b3e0797b36e0e4eb1202684f8d44c
checksums.yaml.gz.sig CHANGED
Binary file
@@ -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
@@ -23,10 +23,11 @@ module IO::Endpoint
23
23
  attr :address
24
24
 
25
25
  # Bind a socket to the given address. If a block is given, the socket will be automatically closed when the block exits.
26
- # @yield [Socket] the bound socket
27
- # @return [Socket] the bound socket
26
+ # @yield {|socket| ...} An optional block which will be passed the socket.
27
+ # @parameter socket [Socket] The socket which has been bound.
28
+ # @return [Array(Socket)] the bound socket
28
29
  def bind(wrapper = Wrapper.default, &block)
29
- wrapper.bind(@address, **@options, &block)
30
+ [wrapper.bind(@address, **@options, &block)]
30
31
  end
31
32
 
32
33
  # Connects a socket to the given address. If a block is given, the socket will be automatically closed when the block exits.
@@ -13,15 +13,17 @@ module IO::Endpoint
13
13
  end
14
14
 
15
15
  def each(&block)
16
- @endpoints.each(&block)
16
+ @endpoints.each do |endpoint|
17
+ endpoint.each(&block)
18
+ end
17
19
  end
18
20
 
19
- def connect(&block)
21
+ def connect(wrapper = Wrapper.default, &block)
20
22
  last_error = nil
21
23
 
22
24
  @endpoints.each do |endpoint|
23
25
  begin
24
- return endpoint.connect(&block)
26
+ return endpoint.connect(wrapper, &block)
25
27
  rescue => last_error
26
28
  end
27
29
  end
@@ -29,8 +31,14 @@ module IO::Endpoint
29
31
  raise last_error
30
32
  end
31
33
 
32
- def bind(&block)
33
- @endpoints.map(&:bind)
34
+ def bind(wrapper = Wrapper.default, &block)
35
+ if block_given?
36
+ @endpoints.each do |endpoint|
37
+ endpoint.bind(&block)
38
+ end
39
+ else
40
+ @endpoints.map(&:bind).flatten.compact
41
+ end
34
42
  end
35
43
  end
36
44
 
@@ -69,22 +69,16 @@ module IO::Endpoint
69
69
 
70
70
  # Accept connections from the specified endpoint.
71
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 accepted(socket), address
81
- end
82
- end
72
+ def accept(wrapper = Wrapper.default, *arguments, **options, &block)
73
+ bind(wrapper, *arguments, **options) do |server|
74
+ wrapper.accept(server, &block)
83
75
  end
84
76
  end
85
77
 
86
78
  # 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
79
  #
80
+ # You should not use untrusted input as it may execute arbitrary code.
81
+ #
88
82
  # @param string [String] URI as string. Scheme will decide implementation used.
89
83
  # @param options keyword arguments passed through to {#initialize}
90
84
  #
@@ -95,13 +89,7 @@ module IO::Endpoint
95
89
  def self.parse(string, **options)
96
90
  uri = URI.parse(string)
97
91
 
98
- self.public_send(uri.scheme, uri.host, uri.port, **options)
99
- end
100
-
101
- protected
102
-
103
- def accepted(socket)
104
- socket
92
+ IO::Endpoint.public_send(uri.scheme, uri.host, uri.port, **options)
105
93
  end
106
94
  end
107
95
  end
@@ -23,7 +23,11 @@ module IO::Endpoint
23
23
  attr :specification
24
24
 
25
25
  def hostname
26
- @specification.first
26
+ @specification[0]
27
+ end
28
+
29
+ def service
30
+ @specification[1]
27
31
  end
28
32
 
29
33
  # Try to connect to the given host by connecting to each address in sequence until a connection is made.
@@ -35,7 +39,7 @@ module IO::Endpoint
35
39
 
36
40
  Addrinfo.foreach(*@specification) do |address|
37
41
  begin
38
- socket = wrapper.connect(@address, **@options)
42
+ socket = wrapper.connect(address, **@options)
39
43
  rescue Errno::ECONNREFUSED, Errno::ENETUNREACH, Errno::EAGAIN => last_error
40
44
  # Try again unless if possible, otherwise raise...
41
45
  else
@@ -7,16 +7,16 @@ require_relative 'generic'
7
7
  require_relative 'composite_endpoint'
8
8
  require_relative 'socket_endpoint'
9
9
 
10
+ require 'openssl'
11
+
10
12
  module IO::Endpoint
11
13
  # Pre-connect and pre-bind sockets so that it can be used between processes.
12
14
  class SharedEndpoint < Generic
13
15
  # Create a new `SharedEndpoint` by binding to the given endpoint.
14
16
  def self.bound(endpoint, backlog: Socket::SOMAXCONN, close_on_exec: false, **options)
15
- sockets = []
17
+ sockets = endpoint.bind(**options)
16
18
 
17
- endpoint.each do |server_endpoint|
18
- server = server_endpoint.bind(**options)
19
-
19
+ sockets.each do |server|
20
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
21
  begin
22
22
  server.listen(backlog)
@@ -25,8 +25,6 @@ module IO::Endpoint
25
25
  end
26
26
 
27
27
  server.close_on_exec = close_on_exec
28
-
29
- sockets << server
30
28
  end
31
29
 
32
30
  return self.new(endpoint, sockets)
@@ -34,11 +32,11 @@ module IO::Endpoint
34
32
 
35
33
  # Create a new `SharedEndpoint` by connecting to the given endpoint.
36
34
  def self.connected(endpoint, close_on_exec: false)
37
- wrapper = endpoint.connect
35
+ socket = endpoint.connect
38
36
 
39
- wrapper.close_on_exec = close_on_exec
37
+ socket.close_on_exec = close_on_exec
40
38
 
41
- return self.new(endpoint, [wrapper])
39
+ return self.new(endpoint, [socket])
42
40
  end
43
41
 
44
42
  def initialize(endpoint, sockets, **options)
@@ -55,18 +53,18 @@ module IO::Endpoint
55
53
 
56
54
  def local_address_endpoint(**options)
57
55
  endpoints = @sockets.map do |wrapper|
58
- AddressEndpoint.new(wrapper.to_io.local_address)
56
+ AddressEndpoint.new(wrapper.to_io.local_address, **options)
59
57
  end
60
58
 
61
- return CompositeEndpoint.new(endpoints, **options)
59
+ return CompositeEndpoint.new(endpoints)
62
60
  end
63
61
 
64
62
  def remote_address_endpoint(**options)
65
63
  endpoints = @sockets.map do |wrapper|
66
- AddressEndpoint.new(wrapper.to_io.remote_address)
64
+ AddressEndpoint.new(wrapper.to_io.remote_address, **options)
67
65
  end
68
66
 
69
- return CompositeEndpoint.new(endpoints, **options)
67
+ return CompositeEndpoint.new(endpoints)
70
68
  end
71
69
 
72
70
  # Close all the internal sockets.
@@ -115,9 +113,9 @@ module IO::Endpoint
115
113
  end
116
114
  end
117
115
 
118
- def accept(**options, &block)
119
- bind do |server|
120
- server.accept(&block)
116
+ def accept(wrapper = Wrapper.default, &block)
117
+ bind(wrapper) do |server|
118
+ wrapper.accept(server, &block)
121
119
  end
122
120
  end
123
121
 
@@ -8,6 +8,38 @@ require_relative 'generic'
8
8
 
9
9
  require 'openssl'
10
10
 
11
+ module OpenSSL::SSL::SocketForwarder
12
+ unless method_defined?(:close_on_exec=)
13
+ def close_on_exec=(value)
14
+ to_io.close_on_exec = value
15
+ end
16
+ end
17
+
18
+ unless method_defined?(:close_on_exec)
19
+ def local_address
20
+ to_io.local_address
21
+ end
22
+ end
23
+
24
+ unless method_defined?(:wait)
25
+ def wait(*arguments)
26
+ to_io.wait(*arguments)
27
+ end
28
+ end
29
+
30
+ unless method_defined?(:wait_readable)
31
+ def wait_readable(*arguments)
32
+ to_io.wait_readable(*arguments)
33
+ end
34
+ end
35
+
36
+ unless method_defined?(:wait_writable)
37
+ def wait_writable(*arguments)
38
+ to_io.wait_writable(*arguments)
39
+ end
40
+ end
41
+ end
42
+
11
43
  module IO::Endpoint
12
44
  class SSLEndpoint < Generic
13
45
  def initialize(endpoint, **options)
@@ -41,13 +73,13 @@ module IO::Endpoint
41
73
  @options[:ssl_params]
42
74
  end
43
75
 
44
- def build_context(context = OpenSSL::SSL::SSLContext.new)
76
+ def build_context(context = ::OpenSSL::SSL::SSLContext.new)
45
77
  if params = self.params
46
78
  context.set_params(params)
47
79
  end
48
80
 
49
- context.setup
50
- context.freeze
81
+ # context.setup
82
+ # context.freeze
51
83
 
52
84
  return context
53
85
  end
@@ -59,13 +91,15 @@ module IO::Endpoint
59
91
  # Connect to the underlying endpoint and establish a SSL connection.
60
92
  # @yield [Socket] the socket which is being connected
61
93
  # @return [Socket] the connected socket
62
- def bind
94
+ def bind(*arguments, **options, &block)
63
95
  if block_given?
64
- @endpoint.bind do |server|
65
- yield OpenSSL::SSL::SSLServer.new(server, context)
96
+ @endpoint.bind(*arguments, **options) do |server|
97
+ yield ::OpenSSL::SSL::SSLServer.new(server, self.context)
66
98
  end
67
99
  else
68
- return OpenSSL::SSL::SSLServer.new(@endpoint.bind, context)
100
+ @endpoint.bind(*arguments, **options).map do |server|
101
+ ::OpenSSL::SSL::SSLServer.new(server, self.context)
102
+ end
69
103
  end
70
104
  end
71
105
 
@@ -73,7 +107,7 @@ module IO::Endpoint
73
107
  # @yield [Socket] the socket which is being connected
74
108
  # @return [Socket] the connected socket
75
109
  def connect(&block)
76
- socket = OpenSSL::SSL::SSLSocket.new(@endpoint.connect, context)
110
+ socket = ::OpenSSL::SSL::SSLSocket.new(@endpoint.connect, self.context)
77
111
 
78
112
  if hostname = self.hostname
79
113
  socket.hostname = hostname
@@ -102,13 +136,6 @@ module IO::Endpoint
102
136
  yield self.class.new(endpoint, **@options)
103
137
  end
104
138
  end
105
-
106
- protected
107
-
108
- def accepted(socket)
109
- socket.accept
110
- socket
111
- end
112
139
  end
113
140
 
114
141
  # @param arguments
@@ -5,6 +5,6 @@
5
5
 
6
6
  class IO
7
7
  module Endpoint
8
- VERSION = "0.2.0"
8
+ VERSION = "0.4.0"
9
9
  end
10
10
  end
@@ -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
@@ -131,14 +130,14 @@ module IO::Endpoint
131
130
  end
132
131
 
133
132
  # Bind to a local address and accept connections in a loop.
134
- def accept(*arguments, backlog: SOMAXCONN, **options, &block)
135
- bind(*arguments, **options) do |server|
136
- server.listen(backlog) if backlog
133
+ def accept(server, timeout: server.timeout, &block)
134
+ while true
135
+ socket, address = server.accept
136
+
137
+ socket.timeout = timeout if timeout != false
137
138
 
138
139
  async do
139
- while true
140
- server.accept(&block)
141
- end
140
+ yield socket, address
142
141
  end
143
142
  end
144
143
  end
data/readme.md CHANGED
@@ -18,6 +18,14 @@ We welcome contributions to this project.
18
18
  4. Push to the branch (`git push origin my-new-feature`).
19
19
  5. Create new Pull Request.
20
20
 
21
+ ### Developer Certificate of Origin
22
+
23
+ This project uses the [Developer Certificate of Origin](https://developercertificate.org/). All contributors to this project must agree to this document to have their contributions accepted.
24
+
25
+ ### Contributor Covenant
26
+
27
+ This project is governed by the [Contributor Covenant](https://www.contributor-covenant.org/). All contributors and participants agree to abide by its terms.
28
+
21
29
  ## See Also
22
30
 
23
31
  - [async-io](https://github.com/socketry/async-io) — Asynchronous IO primitives.
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.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -37,56 +37,15 @@ cert_chain:
37
37
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
38
38
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
39
39
  -----END CERTIFICATE-----
40
- date: 2023-12-16 00:00:00.000000000 Z
41
- dependencies:
42
- - !ruby/object:Gem::Dependency
43
- name: bake
44
- requirement: !ruby/object:Gem::Requirement
45
- requirements:
46
- - - ">="
47
- - !ruby/object:Gem::Version
48
- version: '0'
49
- type: :development
50
- prerelease: false
51
- version_requirements: !ruby/object:Gem::Requirement
52
- requirements:
53
- - - ">="
54
- - !ruby/object:Gem::Version
55
- version: '0'
56
- - !ruby/object:Gem::Dependency
57
- name: covered
58
- requirement: !ruby/object:Gem::Requirement
59
- requirements:
60
- - - ">="
61
- - !ruby/object:Gem::Version
62
- version: '0'
63
- type: :development
64
- prerelease: false
65
- version_requirements: !ruby/object:Gem::Requirement
66
- requirements:
67
- - - ">="
68
- - !ruby/object:Gem::Version
69
- version: '0'
70
- - !ruby/object:Gem::Dependency
71
- name: sus
72
- requirement: !ruby/object:Gem::Requirement
73
- requirements:
74
- - - ">="
75
- - !ruby/object:Gem::Version
76
- version: '0'
77
- type: :development
78
- prerelease: false
79
- version_requirements: !ruby/object:Gem::Requirement
80
- requirements:
81
- - - ">="
82
- - !ruby/object:Gem::Version
83
- version: '0'
40
+ date: 2024-01-01 00:00:00.000000000 Z
41
+ dependencies: []
84
42
  description:
85
43
  email:
86
44
  executables: []
87
45
  extensions: []
88
46
  extra_rdoc_files: []
89
47
  files:
48
+ - lib/io/connected.rb
90
49
  - lib/io/endpoint.rb
91
50
  - lib/io/endpoint/address_endpoint.rb
92
51
  - lib/io/endpoint/composite_endpoint.rb
@@ -120,7 +79,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
120
79
  - !ruby/object:Gem::Version
121
80
  version: '0'
122
81
  requirements: []
123
- rubygems_version: 3.4.22
82
+ rubygems_version: 3.4.10
124
83
  signing_key:
125
84
  specification_version: 4
126
85
  summary: Provides a separation of concerns interface for IO endpoints.
metadata.gz.sig CHANGED
Binary file