io-endpoint 0.2.0 → 0.4.0

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