io-endpoint 0.15.2 → 0.16.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: 0ab97a46f319ca04819bf0c53b225163977979f70242a77d84f09f2a4da438be
4
- data.tar.gz: 396ba42209012cc921b2ecff5ef51eedcba214f215d7b4fcb62d4cb1f52347ea
3
+ metadata.gz: 85b2a321334cd16a75da466ebff7b9f380410788f173936e9bffd5482599e2ad
4
+ data.tar.gz: f207fe739fe89220f59037ec54b1020795509598d4f0deb7a9caaf2cb36e8ea5
5
5
  SHA512:
6
- metadata.gz: 77457ee3835cc1daa48a72ad6e025e855061dadbafbbd154090e81bf2854b2e96abe0a70f82eef58eb1893eaf1e968f668d6e58485879d2216ad0680a3a4263b
7
- data.tar.gz: 0f01d192468b9e6c38ec6e410086674bfe7a6ff96622ab82cd8739b5627e02c055bfbb193dbcd806fc4a87bea7880bf234c1a6c197e0a4f2d426dc66a49352f5
6
+ metadata.gz: 7154bdc644a2e8f2689567b9804d4019c263f07dd28f44ec98084ed4c3459ea1521724dcf0d4b90d2be9928a9eb4bbcb58895e18e10d461109f91dbdc6cb45ea
7
+ data.tar.gz: d8f01abe1ee5d1c93184505f03c2ec4226b6b4eed20754d413b8dcdfa5d5e339d3169069a420fac31b26eca2e157548ad7479e51f8457a14c8f3db5d1efe9e78
checksums.yaml.gz.sig CHANGED
Binary file
@@ -0,0 +1,113 @@
1
+ # Getting Started
2
+
3
+ This guide explains how to get started with `io-endpoint`, a library that provides a separation of concerns interface for network I/O endpoints.
4
+
5
+ ## Installation
6
+
7
+ Add the gem to your project:
8
+
9
+ ~~~ bash
10
+ $ bundle add io-endpoint
11
+ ~~~
12
+
13
+ ## Core Concepts
14
+
15
+ `io-endpoint` provides a unified interface for working with network endpoints, allowing you to write code that is agnostic to the underlying transport mechanism (TCP, UDP, UNIX sockets, SSL/TLS). This separation of concerns makes it easier to:
16
+
17
+ - **Write transport-agnostic code**: Your application logic doesn't need to know whether it's using TCP, UDP, or UNIX sockets.
18
+ - **Test with different transports**: Easily swap between transports during testing.
19
+ - **Handle multiple addresses**: Automatically handle IPv4 and IPv6 addresses.
20
+ - **Compose endpoints**: Combine multiple endpoints for failover or load distribution.
21
+
22
+ The library centers around the {ruby IO::Endpoint::Generic} class, which represents a network endpoint that can be bound (for servers) or connected to (for clients). Different endpoint types handle different scenarios:
23
+
24
+ - {ruby IO::Endpoint::HostEndpoint} - Resolves hostnames to addresses (e.g., "localhost:8080")
25
+ - {ruby IO::Endpoint::AddressEndpoint} - Works with specific network addresses
26
+ - {ruby IO::Endpoint::UNIXEndpoint} - Handles UNIX domain sockets
27
+ - {ruby IO::Endpoint::SSLEndpoint} - Wraps endpoints with SSL/TLS encryption
28
+ - {ruby IO::Endpoint::CompositeEndpoint} - Combines multiple endpoints
29
+
30
+ ## Usage
31
+
32
+ ### Creating a TCP Server
33
+
34
+ When you need to create a server that listens on a specific port, you can use {ruby IO::Endpoint.tcp} to create a TCP endpoint:
35
+
36
+ ```ruby
37
+ require "io/endpoint"
38
+
39
+ # Create a TCP endpoint listening on localhost port 8080:
40
+ endpoint = IO::Endpoint.tcp("localhost", 8080)
41
+
42
+ # Bind to the endpoint and accept connections:
43
+ endpoint.bind do |server|
44
+ # The server socket is automatically closed when the block exits
45
+ server.listen(10)
46
+
47
+ loop do
48
+ client, address = server.accept
49
+ # Handle the client connection
50
+ client.close
51
+ end
52
+ end
53
+ ```
54
+
55
+ ### Creating a TCP Client
56
+
57
+ To connect to a remote server, use the `connect` method:
58
+
59
+ ```ruby
60
+ require "io/endpoint"
61
+
62
+ # Create a TCP endpoint for the remote server:
63
+ endpoint = IO::Endpoint.tcp("example.com", 80)
64
+
65
+ # Connect to the server:
66
+ endpoint.connect do |socket|
67
+ # The socket is automatically closed when the block exits
68
+ socket.write("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
69
+ response = socket.read
70
+ puts response
71
+ end
72
+ ```
73
+
74
+ ### Using UNIX Domain Sockets
75
+
76
+ For inter-process communication on the same machine, UNIX domain sockets provide better performance than TCP:
77
+
78
+ ```ruby
79
+ require "io/endpoint"
80
+
81
+ # Create a UNIX socket endpoint:
82
+ endpoint = IO::Endpoint.unix("/tmp/myapp.sock")
83
+
84
+ # Bind to the socket:
85
+ endpoint.bind do |server|
86
+ server.listen(10)
87
+
88
+ loop do
89
+ client, address = server.accept
90
+ # Handle the client connection
91
+ client.close
92
+ end
93
+ end
94
+ ```
95
+
96
+ ### Using SSL/TLS
97
+
98
+ To add encryption to your connections, wrap a TCP endpoint with SSL:
99
+
100
+ ```ruby
101
+ require "io/endpoint"
102
+
103
+ # Create an SSL endpoint:
104
+ endpoint = IO::Endpoint.ssl("example.com", 443, hostname: "example.com")
105
+
106
+ # Connect with SSL encryption:
107
+ endpoint.connect do |socket|
108
+ # The socket is automatically encrypted
109
+ socket.write("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
110
+ response = socket.read
111
+ puts response
112
+ end
113
+ ```
@@ -0,0 +1,12 @@
1
+ # Automatically generated context index for Utopia::Project guides.
2
+ # Do not edit then files in this directory directly, instead edit the guides and then run `bake utopia:project:agent:context:update`.
3
+ ---
4
+ description: Provides a separation of concerns interface for IO endpoints.
5
+ metadata:
6
+ documentation_uri: https://socketry.github.io/io-endpoint
7
+ source_code_uri: https://github.com/socketry/io-endpoint.git
8
+ files:
9
+ - path: getting-started.md
10
+ title: Getting Started
11
+ description: This guide explains how to get started with `io-endpoint`, a library
12
+ that provides a separation of concerns interface for network I/O endpoints.
@@ -9,13 +9,19 @@ require_relative "generic"
9
9
  require_relative "wrapper"
10
10
 
11
11
  module IO::Endpoint
12
+ # Represents an endpoint for a specific network address.
12
13
  class AddressEndpoint < Generic
14
+ # Initialize a new address endpoint.
15
+ # @parameter address [Address] The network address for this endpoint.
16
+ # @parameter options [Hash] Additional options to pass to the parent class.
13
17
  def initialize(address, **options)
14
18
  super(**options)
15
19
 
16
20
  @address = address
17
21
  end
18
22
 
23
+ # Get a string representation of the endpoint.
24
+ # @returns [String] A string representation of the endpoint address.
19
25
  def to_s
20
26
  case @address.afamily
21
27
  when Socket::AF_INET
@@ -27,22 +33,25 @@ module IO::Endpoint
27
33
  end
28
34
  end
29
35
 
36
+ # Get a detailed string representation of the endpoint.
37
+ # @returns [String] A detailed string representation including the address.
30
38
  def inspect
31
39
  "\#<#{self.class} address=#{@address.inspect}>"
32
40
  end
33
41
 
42
+ # @attribute [Address] The network address for this endpoint.
34
43
  attr :address
35
44
 
36
45
  # Bind a socket to the given address. If a block is given, the socket will be automatically closed when the block exits.
37
- # @yield {|socket| ...} An optional block which will be passed the socket.
38
- # @parameter socket [Socket] The socket which has been bound.
39
- # @return [Array(Socket)] the bound socket
46
+ # @yields {|socket| ...} If a block is given, yields the bound socket.
47
+ # @parameter socket [Socket] The socket which has been bound.
48
+ # @returns [Array(Socket)] the bound socket
40
49
  def bind(wrapper = self.wrapper, &block)
41
50
  [wrapper.bind(@address, **@options, &block)]
42
51
  end
43
52
 
44
53
  # Connects a socket to the given address. If a block is given, the socket will be automatically closed when the block exits.
45
- # @return [Socket] the connected socket
54
+ # @returns [Socket] the connected socket
46
55
  def connect(wrapper = self.wrapper, &block)
47
56
  wrapper.connect(@address, **@options, &block)
48
57
  end
@@ -8,7 +8,13 @@ require_relative "composite_endpoint"
8
8
  require_relative "address_endpoint"
9
9
 
10
10
  module IO::Endpoint
11
+ # Represents an endpoint that has been bound to one or more sockets.
11
12
  class BoundEndpoint < Generic
13
+ # Create a bound endpoint from an existing endpoint.
14
+ # @parameter endpoint [Generic] The endpoint to bind.
15
+ # @option backlog [Integer] The maximum length of the queue of pending connections.
16
+ # @option close_on_exec [Boolean] Whether to close sockets on exec.
17
+ # @returns [BoundEndpoint] A new bound endpoint instance.
12
18
  def self.bound(endpoint, backlog: Socket::SOMAXCONN, close_on_exec: false)
13
19
  sockets = endpoint.bind
14
20
 
@@ -19,6 +25,10 @@ module IO::Endpoint
19
25
  return self.new(endpoint, sockets, **endpoint.options)
20
26
  end
21
27
 
28
+ # Initialize a new bound endpoint.
29
+ # @parameter endpoint [Generic] The original endpoint that was bound.
30
+ # @parameter sockets [Array(Socket)] The sockets that were bound.
31
+ # @parameter options [Hash] Additional options to pass to the parent class.
22
32
  def initialize(endpoint, sockets, **options)
23
33
  super(**options)
24
34
 
@@ -26,7 +36,9 @@ module IO::Endpoint
26
36
  @sockets = sockets
27
37
  end
28
38
 
39
+ # @attribute [Generic] The original endpoint that was bound.
29
40
  attr :endpoint
41
+ # @attribute [Array(Socket)] The sockets that were bound.
30
42
  attr :sockets
31
43
 
32
44
  # A endpoint for the local end of the bound socket.
@@ -49,19 +61,29 @@ module IO::Endpoint
49
61
  return CompositeEndpoint.new(endpoints)
50
62
  end
51
63
 
64
+ # Close all bound sockets.
52
65
  def close
53
66
  @sockets.each(&:close)
54
67
  @sockets.clear
55
68
  end
56
69
 
70
+ # Get a string representation of the bound endpoint.
71
+ # @returns [String] A string representation of the bound endpoint.
57
72
  def to_s
58
73
  "bound:#{@endpoint}"
59
74
  end
60
75
 
76
+ # Get a detailed string representation of the bound endpoint.
77
+ # @returns [String] A detailed string representation including socket count.
61
78
  def inspect
62
79
  "\#<#{self.class} #{@sockets.size} bound sockets for #{@endpoint}>"
63
80
  end
64
81
 
82
+ # Bind the endpoint using the given wrapper.
83
+ # @parameter wrapper [Wrapper] The wrapper to use for binding.
84
+ # @yields {|socket| ...} If a block is given, yields each bound socket.
85
+ # @parameter socket [Socket] A bound socket.
86
+ # @returns [Array(Socket)] An array of bound sockets.
65
87
  def bind(wrapper = self.wrapper, &block)
66
88
  @sockets.map do |server|
67
89
  if block_given?
@@ -76,6 +98,9 @@ module IO::Endpoint
76
98
  end
77
99
 
78
100
  class Generic
101
+ # Create a bound endpoint from this endpoint.
102
+ # @parameter options [Hash] Options to pass to {BoundEndpoint.bound}.
103
+ # @returns [BoundEndpoint] A new bound endpoint instance.
79
104
  def bound(**options)
80
105
  BoundEndpoint.bound(self, **options)
81
106
  end
@@ -8,6 +8,9 @@ require_relative "generic"
8
8
  module IO::Endpoint
9
9
  # A composite endpoint is a collection of endpoints that are used in order.
10
10
  class CompositeEndpoint < Generic
11
+ # Initialize a new composite endpoint.
12
+ # @parameter endpoints [Array(Generic)] The endpoints to compose.
13
+ # @parameter options [Hash] Additional options to pass to the parent class and propagate to endpoints.
11
14
  def initialize(endpoints, **options)
12
15
  super(**options)
13
16
 
@@ -19,18 +22,26 @@ module IO::Endpoint
19
22
  @endpoints = endpoints
20
23
  end
21
24
 
25
+ # Get a string representation of the composite endpoint.
26
+ # @returns [String] A string representation listing all endpoints.
22
27
  def to_s
23
28
  "composite:#{@endpoints.join(",")}"
24
29
  end
25
30
 
31
+ # Get a detailed string representation of the composite endpoint.
32
+ # @returns [String] A detailed string representation including all endpoints.
26
33
  def inspect
27
34
  "\#<#{self.class} endpoints=#{@endpoints}>"
28
35
  end
29
36
 
37
+ # Create a new composite endpoint with merged options.
38
+ # @parameter options [Hash] Additional options to merge with existing options.
39
+ # @returns [CompositeEndpoint] A new composite endpoint instance with merged options.
30
40
  def with(**options)
31
41
  self.class.new(endpoints.map{|endpoint| endpoint.with(**options)}, **@options.merge(options))
32
42
  end
33
43
 
44
+ # @attribute [Array(Generic)] The endpoints in this composite endpoint.
34
45
  attr :endpoints
35
46
 
36
47
  # The number of endpoints in the composite endpoint.
@@ -38,12 +49,21 @@ module IO::Endpoint
38
49
  @endpoints.size
39
50
  end
40
51
 
52
+ # Enumerate all endpoints in the composite endpoint.
53
+ # @yields {|endpoint| ...} For each endpoint in the composite, yields it.
54
+ # @parameter endpoint [Generic] An endpoint in the composite.
41
55
  def each(&block)
42
56
  @endpoints.each do |endpoint|
43
57
  endpoint.each(&block)
44
58
  end
45
59
  end
46
60
 
61
+ # Connect to the first endpoint that succeeds.
62
+ # @parameter wrapper [Wrapper] The wrapper to use for connecting.
63
+ # @yields {|socket| ...} If a block is given, yields the connected socket from the first successful endpoint.
64
+ # @parameter socket [Socket] The connected socket.
65
+ # @returns [Socket] The connected socket.
66
+ # @raises [Exception] If all endpoints fail to connect, raises the last error encountered.
47
67
  def connect(wrapper = self.wrapper, &block)
48
68
  last_error = nil
49
69
 
@@ -57,6 +77,11 @@ module IO::Endpoint
57
77
  raise last_error
58
78
  end
59
79
 
80
+ # Bind all endpoints in the composite.
81
+ # @parameter wrapper [Wrapper] The wrapper to use for binding.
82
+ # @yields {|socket| ...} For each endpoint that is bound, yields the bound socket.
83
+ # @parameter socket [Socket] A bound socket.
84
+ # @returns [Array(Socket)] An array of bound sockets if no block is given.
60
85
  def bind(wrapper = self.wrapper, &block)
61
86
  if block_given?
62
87
  @endpoints.each do |endpoint|
@@ -68,6 +93,10 @@ module IO::Endpoint
68
93
  end
69
94
  end
70
95
 
96
+ # Create a composite endpoint from multiple endpoints.
97
+ # @parameter endpoints [Array(Generic)] The endpoints to compose.
98
+ # @parameter options [Hash] Additional options to pass to the composite endpoint.
99
+ # @returns [CompositeEndpoint] A new composite endpoint instance.
71
100
  def self.composite(*endpoints, **options)
72
101
  CompositeEndpoint.new(endpoints, **options)
73
102
  end
@@ -10,7 +10,12 @@ require_relative "socket_endpoint"
10
10
  require "openssl"
11
11
 
12
12
  module IO::Endpoint
13
+ # Represents an endpoint that has been connected to a socket.
13
14
  class ConnectedEndpoint < Generic
15
+ # Create a connected endpoint from an existing endpoint.
16
+ # @parameter endpoint [Generic] The endpoint to connect.
17
+ # @option close_on_exec [Boolean] Whether to close the socket on exec.
18
+ # @returns [ConnectedEndpoint] A new connected endpoint instance.
14
19
  def self.connected(endpoint, close_on_exec: false)
15
20
  socket = endpoint.connect
16
21
 
@@ -19,6 +24,10 @@ module IO::Endpoint
19
24
  return self.new(endpoint, socket, **endpoint.options)
20
25
  end
21
26
 
27
+ # Initialize a new connected endpoint.
28
+ # @parameter endpoint [Generic] The original endpoint that was connected.
29
+ # @parameter socket [Socket] The socket that was connected.
30
+ # @parameter options [Hash] Additional options to pass to the parent class.
22
31
  def initialize(endpoint, socket, **options)
23
32
  super(**options)
24
33
 
@@ -26,7 +35,9 @@ module IO::Endpoint
26
35
  @socket = socket
27
36
  end
28
37
 
38
+ # @attribute [Generic] The original endpoint that was connected.
29
39
  attr :endpoint
40
+ # @attribute [Socket] The socket that was connected.
30
41
  attr :socket
31
42
 
32
43
  # A endpoint for the local end of the bound socket.
@@ -41,6 +52,11 @@ module IO::Endpoint
41
52
  AddressEndpoint.new(socket.to_io.remote_address, **options)
42
53
  end
43
54
 
55
+ # Connect using the already connected socket.
56
+ # @parameter wrapper [Wrapper] The wrapper to use (unused, socket is already connected).
57
+ # @yields {|socket| ...} If a block is given, yields the connected socket.
58
+ # @parameter socket [Socket] The connected socket.
59
+ # @returns [Socket] The connected socket or a duplicate if no block is given.
44
60
  def connect(wrapper = self.wrapper, &block)
45
61
  if block_given?
46
62
  yield @socket
@@ -49,6 +65,7 @@ module IO::Endpoint
49
65
  end
50
66
  end
51
67
 
68
+ # Close the connected socket.
52
69
  def close
53
70
  if @socket
54
71
  @socket.close
@@ -56,16 +73,23 @@ module IO::Endpoint
56
73
  end
57
74
  end
58
75
 
76
+ # Get a string representation of the connected endpoint.
77
+ # @returns [String] A string representation of the connected endpoint.
59
78
  def to_s
60
79
  "connected:#{@endpoint}"
61
80
  end
62
81
 
82
+ # Get a detailed string representation of the connected endpoint.
83
+ # @returns [String] A detailed string representation including the socket.
63
84
  def inspect
64
85
  "\#<#{self.class} #{@socket} connected for #{@endpoint}>"
65
86
  end
66
87
  end
67
-
88
+
68
89
  class Generic
90
+ # Create a connected endpoint from this endpoint.
91
+ # @parameter options [Hash] Options to pass to {ConnectedEndpoint.connected}.
92
+ # @returns [ConnectedEndpoint] A new connected endpoint instance.
69
93
  def connected(**options)
70
94
  ConnectedEndpoint.connected(self, **options)
71
95
  end
@@ -12,10 +12,15 @@ module IO::Endpoint
12
12
 
13
13
  # Endpoints represent a way of connecting or binding to an address.
14
14
  class Generic
15
+ # Initialize a new generic endpoint.
16
+ # @parameter options [Hash] Configuration options for the endpoint.
15
17
  def initialize(**options)
16
18
  @options = options.freeze
17
19
  end
18
20
 
21
+ # Create a new endpoint with merged options.
22
+ # @parameter options [Hash] Additional options to merge with existing options.
23
+ # @returns [Generic] A new endpoint instance with merged options.
19
24
  def with(**options)
20
25
  dup = self.dup
21
26
 
@@ -26,43 +31,43 @@ module IO::Endpoint
26
31
 
27
32
  attr_accessor :options
28
33
 
29
- # @return [String] The hostname of the bound socket.
34
+ # @returns [String] The hostname of the bound socket.
30
35
  def hostname
31
36
  @options[:hostname]
32
37
  end
33
38
 
34
39
  # If `SO_REUSEPORT` is enabled on a socket, the socket can be successfully bound even if there are existing sockets bound to the same address, as long as all prior bound sockets also had `SO_REUSEPORT` set before they were bound.
35
- # @return [Boolean, nil] The value for `SO_REUSEPORT`.
40
+ # @returns [Boolean, nil] The value for `SO_REUSEPORT`.
36
41
  def reuse_port?
37
42
  @options[:reuse_port]
38
43
  end
39
44
 
40
45
  # If `SO_REUSEADDR` is enabled on a socket prior to binding it, the socket can be successfully bound unless there is a conflict with another socket bound to exactly the same combination of source address and port. Additionally, when set, binding a socket to the address of an existing socket in `TIME_WAIT` is not an error.
41
- # @return [Boolean] The value for `SO_REUSEADDR`.
46
+ # @returns [Boolean] The value for `SO_REUSEADDR`.
42
47
  def reuse_address?
43
48
  @options[:reuse_address]
44
49
  end
45
50
 
46
51
  # Controls SO_LINGER. The amount of time the socket will stay in the `TIME_WAIT` state after being closed.
47
- # @return [Integer, nil] The value for SO_LINGER.
52
+ # @returns [Integer, nil] The value for SO_LINGER.
48
53
  def linger
49
54
  @options[:linger]
50
55
  end
51
56
 
52
- # @return [Numeric] The default timeout for socket operations.
57
+ # @returns [Numeric] The default timeout for socket operations.
53
58
  def timeout
54
59
  @options[:timeout]
55
60
  end
56
61
 
57
- # @return [Address] the address to bind to before connecting.
62
+ # @returns [Address] the address to bind to before connecting.
58
63
  def local_address
59
64
  @options[:local_address]
60
65
  end
61
66
 
62
67
  # Bind a socket to the given address. If a block is given, the socket will be automatically closed when the block exits.
63
68
  # @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.
69
+ # @yields {|socket| ...} If a block is given, yields the bound socket.
70
+ # @parameter socket [Socket] The socket which has been bound.
66
71
  # @returns [Array(Socket)] the bound socket
67
72
  def bind(wrapper = self.wrapper, &block)
68
73
  raise NotImplementedError
@@ -70,14 +75,15 @@ module IO::Endpoint
70
75
 
71
76
  # Connects a socket to the given address. If a block is given, the socket will be automatically closed when the block exits.
72
77
  # @parameter wrapper [Wrapper] The wrapper to use for connecting.
73
- # @return [Socket] the connected socket
78
+ # @returns [Socket] the connected socket
74
79
  def connect(wrapper = self.wrapper, &block)
75
80
  raise NotImplementedError
76
81
  end
77
82
 
78
83
  # Bind and accept connections on the given address.
79
84
  # @parameter wrapper [Wrapper] The wrapper to use for accepting connections.
80
- # @yields [Socket] The accepted socket.
85
+ # @yields {|socket| ...} For each accepted connection, yields the socket.
86
+ # @parameter socket [Socket] The accepted socket.
81
87
  def accept(wrapper = self.wrapper, &block)
82
88
  bind(wrapper) do |server|
83
89
  wrapper.accept(server, **@options, &block)
@@ -85,7 +91,7 @@ module IO::Endpoint
85
91
  end
86
92
 
87
93
  # Enumerate all discrete paths as endpoints.
88
- # @yields {|endpoint| ...} A block which will be passed each endpoint.
94
+ # @yields {|endpoint| ...} For each endpoint, yields it.
89
95
  # @parameter endpoint [Endpoint] The endpoint.
90
96
  def each
91
97
  return to_enum unless block_given?
@@ -97,8 +103,8 @@ module IO::Endpoint
97
103
  #
98
104
  # You should not use untrusted input as it may execute arbitrary code.
99
105
  #
100
- # @param string [String] URI as string. Scheme will decide implementation used.
101
- # @param options keyword arguments passed through to {#initialize}
106
+ # @parameter string [String] URI as string. Scheme will decide implementation used.
107
+ # @parameter options keyword arguments passed through to {#initialize}
102
108
  #
103
109
  # @see Endpoint.ssl ssl - invoked when parsing a URL with the ssl scheme "ssl://127.0.0.1"
104
110
  # @see Endpoint.tcp tcp - invoked when parsing a URL with the tcp scheme: "tcp://127.0.0.1"
@@ -6,17 +6,25 @@
6
6
  require_relative "address_endpoint"
7
7
 
8
8
  module IO::Endpoint
9
+ # Represents an endpoint for a hostname and service that resolves to multiple addresses.
9
10
  class HostEndpoint < Generic
11
+ # Initialize a new host endpoint.
12
+ # @parameter specification [Array] The host specification array containing nodename, service, family, socktype, protocol, and flags.
13
+ # @parameter options [Hash] Additional options to pass to the parent class.
10
14
  def initialize(specification, **options)
11
15
  super(**options)
12
16
 
13
17
  @specification = specification
14
18
  end
15
19
 
20
+ # Get a string representation of the host endpoint.
21
+ # @returns [String] A string representation showing hostname and service.
16
22
  def to_s
17
23
  "host:#{@specification[0]}:#{@specification[1]}"
18
24
  end
19
25
 
26
+ # Get a detailed string representation of the host endpoint.
27
+ # @returns [String] A detailed string representation including all specification parameters.
20
28
  def inspect
21
29
  nodename, service, family, socktype, protocol, flags = @specification
22
30
 
@@ -24,27 +32,34 @@ module IO::Endpoint
24
32
  end
25
33
 
26
34
 
35
+ # @attribute [Array] The host specification array.
27
36
  attr :specification
28
37
 
38
+ # Get the hostname from the specification.
39
+ # @returns [String, nil] The hostname (nodename) from the specification.
29
40
  def hostname
30
41
  @specification[0]
31
42
  end
32
43
 
44
+ # Get the service from the specification.
45
+ # @returns [String, Integer, nil] The service (port) from the specification.
33
46
  def service
34
47
  @specification[1]
35
48
  end
36
49
 
37
50
  # Try to connect to the given host by connecting to each address in sequence until a connection is made.
38
- # @yield [Socket] the socket which is being connected, may be invoked more than once
39
- # @return [Socket] the connected socket
40
- # @raise if no connection could complete successfully
51
+ # @yields {|socket| ...} If a block is given, yields the connected socket (may be invoked multiple times during connection attempts).
52
+ # @parameter socket [Socket] The socket which is being connected.
53
+ # @returns [Socket] the connected socket
54
+ # @raises [Exception] if no connection could complete successfully
41
55
  def connect(wrapper = self.wrapper, &block)
42
56
  last_error = nil
43
57
 
44
58
  Addrinfo.foreach(*@specification) do |address|
45
59
  begin
46
60
  socket = wrapper.connect(address, **@options)
47
- rescue Errno::ECONNREFUSED, Errno::ENETUNREACH, Errno::EAGAIN => last_error
61
+ rescue => last_error
62
+ Console.debug(self, "Failed to connect:", address, exception: last_error)
48
63
  # Try again unless if possible, otherwise raise...
49
64
  else
50
65
  return socket unless block_given?
@@ -61,15 +76,17 @@ module IO::Endpoint
61
76
  end
62
77
 
63
78
  # Invokes the given block for every address which can be bound to.
64
- # @yield [Socket] the bound socket
65
- # @return [Array<Socket>] an array of bound sockets
79
+ # @yields {|socket| ...} For each address that can be bound, yields the bound socket.
80
+ # @parameter socket [Socket] The bound socket.
81
+ # @returns [Array<Socket>] an array of bound sockets
66
82
  def bind(wrapper = self.wrapper, &block)
67
83
  Addrinfo.foreach(*@specification).map do |address|
68
84
  wrapper.bind(address, **@options, &block)
69
85
  end
70
86
  end
71
87
 
72
- # @yield [AddressEndpoint] address endpoints by resolving the given host specification
88
+ # @yields {|endpoint| ...} For each resolved address, yields an address endpoint.
89
+ # @parameter endpoint [AddressEndpoint] An address endpoint.
73
90
  def each
74
91
  return to_enum unless block_given?
75
92
 
@@ -79,20 +96,20 @@ module IO::Endpoint
79
96
  end
80
97
  end
81
98
 
82
- # @param arguments nodename, service, family, socktype, protocol, flags. `socktype` will be set to Socket::SOCK_STREAM.
83
- # @param options keyword arguments passed on to {HostEndpoint#initialize}
99
+ # @parameter arguments nodename, service, family, socktype, protocol, flags. `socktype` will be set to Socket::SOCK_STREAM.
100
+ # @parameter options keyword arguments passed on to {HostEndpoint#initialize}
84
101
  #
85
- # @return [HostEndpoint]
102
+ # @returns [HostEndpoint]
86
103
  def self.tcp(*arguments, **options)
87
104
  arguments[3] = ::Socket::SOCK_STREAM
88
105
 
89
106
  HostEndpoint.new(arguments, **options)
90
107
  end
91
-
92
- # @param arguments nodename, service, family, socktype, protocol, flags. `socktype` will be set to Socket::SOCK_DGRAM.
93
- # @param options keyword arguments passed on to {HostEndpoint#initialize}
108
+
109
+ # @parameter arguments nodename, service, family, socktype, protocol, flags. `socktype` will be set to Socket::SOCK_DGRAM.
110
+ # @parameter options keyword arguments passed on to {HostEndpoint#initialize}
94
111
  #
95
- # @return [HostEndpoint]
112
+ # @returns [HostEndpoint]
96
113
  def self.udp(*arguments, **options)
97
114
  arguments[3] = ::Socket::SOCK_DGRAM
98
115
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2023-2024, by Samuel Williams.
4
+ # Copyright, 2023-2025, by Samuel Williams.
5
5
 
6
6
  require_relative "bound_endpoint"
7
7
  require_relative "connected_endpoint"
@@ -1,29 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2023-2024, by Samuel Williams.
4
+ # Copyright, 2023-2025, by Samuel Williams.
5
5
 
6
6
  require_relative "generic"
7
7
 
8
8
  module IO::Endpoint
9
9
  # This class doesn't exert ownership over the specified socket, wraps a native ::IO.
10
10
  class SocketEndpoint < Generic
11
+ # Initialize a new socket endpoint.
12
+ # @parameter socket [Socket] The socket to wrap.
13
+ # @parameter options [Hash] Additional options to pass to the parent class.
11
14
  def initialize(socket, **options)
12
15
  super(**options)
13
16
 
14
17
  @socket = socket
15
18
  end
16
19
 
20
+ # Get a string representation of the socket endpoint.
21
+ # @returns [String] A string representation showing the socket.
17
22
  def to_s
18
23
  "socket:#{@socket}"
19
24
  end
20
25
 
26
+ # Get a detailed string representation of the socket endpoint.
27
+ # @returns [String] A detailed string representation including the socket.
21
28
  def inspect
22
29
  "\#<#{self.class} #{@socket.inspect}>"
23
30
  end
24
31
 
32
+ # @attribute [Socket] The wrapped socket.
25
33
  attr :socket
26
34
 
35
+ # Bind using the wrapped socket.
36
+ # @yields {|socket| ...} If a block is given, yields the socket.
37
+ # @parameter socket [Socket] The socket.
38
+ # @returns [Socket] The socket.
27
39
  def bind(&block)
28
40
  if block_given?
29
41
  yield @socket
@@ -32,6 +44,10 @@ module IO::Endpoint
32
44
  end
33
45
  end
34
46
 
47
+ # Connect using the wrapped socket.
48
+ # @yields {|socket| ...} If a block is given, yields the socket.
49
+ # @parameter socket [Socket] The socket.
50
+ # @returns [Socket] The socket.
35
51
  def connect(&block)
36
52
  if block_given?
37
53
  yield @socket
@@ -41,6 +57,10 @@ module IO::Endpoint
41
57
  end
42
58
  end
43
59
 
60
+ # Create a socket endpoint from an existing socket.
61
+ # @parameter socket [Socket] The socket to wrap.
62
+ # @parameter options [Hash] Additional options to pass to the socket endpoint.
63
+ # @returns [SocketEndpoint] A new socket endpoint instance.
44
64
  def self.socket(socket, **options)
45
65
  SocketEndpoint.new(socket, **options)
46
66
  end
@@ -1,55 +1,75 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2023-2024, by Samuel Williams.
4
+ # Copyright, 2023-2025, by Samuel Williams.
5
5
 
6
6
  require_relative "host_endpoint"
7
7
  require_relative "generic"
8
8
 
9
9
  require "openssl"
10
10
 
11
+ # @namespace
11
12
  module OpenSSL
13
+ # @namespace
12
14
  module SSL
15
+ # Represents an SSL socket with additional methods for compatibility.
13
16
  class SSLSocket
14
17
  unless method_defined?(:start)
18
+ # Start the SSL handshake (alias for accept).
15
19
  def start
16
20
  self.accept
17
21
  end
18
22
  end
19
23
  end
20
24
 
25
+ # Represents a module that forwards socket methods to the underlying IO object.
21
26
  module SocketForwarder
22
27
  unless method_defined?(:close_on_exec=)
28
+ # Set whether the socket should be closed on exec.
29
+ # @parameter value [Boolean] Whether to close on exec.
23
30
  def close_on_exec=(value)
24
31
  to_io.close_on_exec = value
25
32
  end
26
33
  end
27
34
 
28
35
  unless method_defined?(:local_address)
36
+ # Get the local address of the socket.
37
+ # @returns [Addrinfo] The local address.
29
38
  def local_address
30
39
  to_io.local_address
31
40
  end
32
41
  end
33
42
 
34
43
  unless method_defined?(:remote_address)
44
+ # Get the remote address of the socket.
45
+ # @returns [Addrinfo] The remote address.
35
46
  def remote_address
36
47
  to_io.remote_address
37
48
  end
38
49
  end
39
50
 
40
51
  unless method_defined?(:wait)
52
+ # Wait for the socket to become ready.
53
+ # @parameter arguments [Array] Arguments to pass to the underlying IO wait method.
54
+ # @returns [IO, nil] The socket if ready, nil otherwise.
41
55
  def wait(*arguments)
42
56
  to_io.wait(*arguments)
43
57
  end
44
58
  end
45
59
 
46
60
  unless method_defined?(:wait_readable)
61
+ # Wait for the socket to become readable.
62
+ # @parameter arguments [Array] Arguments to pass to the underlying IO wait_readable method.
63
+ # @returns [IO, nil] The socket if readable, nil otherwise.
47
64
  def wait_readable(*arguments)
48
65
  to_io.wait_readable(*arguments)
49
66
  end
50
67
  end
51
68
 
52
69
  unless method_defined?(:wait_writable)
70
+ # Wait for the socket to become writable.
71
+ # @parameter arguments [Array] Arguments to pass to the underlying IO wait_writable method.
72
+ # @returns [IO, nil] The socket if writable, nil otherwise.
53
73
  def wait_writable(*arguments)
54
74
  to_io.wait_writable(*arguments)
55
75
  end
@@ -57,12 +77,16 @@ module OpenSSL
57
77
 
58
78
  if IO.method_defined?(:timeout)
59
79
  unless method_defined?(:timeout)
80
+ # Get the timeout for socket operations.
81
+ # @returns [Numeric, nil] The timeout value.
60
82
  def timeout
61
83
  to_io.timeout
62
84
  end
63
85
  end
64
86
 
65
87
  unless method_defined?(:timeout=)
88
+ # Set the timeout for socket operations.
89
+ # @parameter value [Numeric, nil] The timeout value.
66
90
  def timeout=(value)
67
91
  to_io.timeout = value
68
92
  end
@@ -73,7 +97,12 @@ module OpenSSL
73
97
  end
74
98
 
75
99
  module IO::Endpoint
100
+ # Represents an SSL/TLS endpoint that wraps another endpoint.
76
101
  class SSLEndpoint < Generic
102
+ # Initialize a new SSL endpoint.
103
+ # @parameter endpoint [Generic] The underlying endpoint to wrap with SSL.
104
+ # @option ssl_context [OpenSSL::SSL::SSLContext, nil] An optional SSL context to use.
105
+ # @parameter options [Hash] Additional options including `:ssl_params` and `:hostname`.
77
106
  def initialize(endpoint, **options)
78
107
  super(**options)
79
108
 
@@ -86,29 +115,44 @@ module IO::Endpoint
86
115
  end
87
116
  end
88
117
 
118
+ # Get a string representation of the SSL endpoint.
119
+ # @returns [String] A string representation showing the underlying endpoint.
89
120
  def to_s
90
121
  "ssl:#{@endpoint}"
91
122
  end
92
123
 
124
+ # Get a detailed string representation of the SSL endpoint.
125
+ # @returns [String] A detailed string representation including the underlying endpoint.
93
126
  def inspect
94
127
  "\#<#{self.class} endpoint=#{@endpoint.inspect}>"
95
128
  end
96
129
 
130
+ # Get the address from the underlying endpoint.
131
+ # @returns [Address, nil] The address from the underlying endpoint.
97
132
  def address
98
133
  @endpoint.address
99
134
  end
100
135
 
136
+ # Get the hostname for SSL verification.
137
+ # @returns [String, nil] The hostname from options or the underlying endpoint.
101
138
  def hostname
102
139
  @options[:hostname] || @endpoint.hostname
103
140
  end
104
141
 
142
+ # @attribute [Generic] The underlying endpoint.
105
143
  attr :endpoint
144
+ # @attribute [Hash] The options hash.
106
145
  attr :options
107
146
 
147
+ # Get SSL parameters from options.
148
+ # @returns [Hash, nil] SSL parameters if specified in options.
108
149
  def params
109
150
  @options[:ssl_params]
110
151
  end
111
152
 
153
+ # Build an SSL context with configured parameters.
154
+ # @parameter context [OpenSSL::SSL::SSLContext] An optional SSL context to configure.
155
+ # @returns [OpenSSL::SSL::SSLContext] The configured SSL context.
112
156
  def build_context(context = ::OpenSSL::SSL::SSLContext.new)
113
157
  if params = self.params
114
158
  context.set_params(params)
@@ -120,16 +164,24 @@ module IO::Endpoint
120
164
  return context
121
165
  end
122
166
 
167
+ # Get or build the SSL context.
168
+ # @returns [OpenSSL::SSL::SSLContext] The SSL context.
123
169
  def context
124
170
  @context ||= build_context
125
171
  end
126
172
 
173
+ # Create an SSL server socket from an IO object.
174
+ # @parameter io [IO] The underlying IO object.
175
+ # @returns [OpenSSL::SSL::SSLServer] A new SSL server socket.
127
176
  def make_server(io)
128
177
  ::OpenSSL::SSL::SSLServer.new(io, self.context).tap do |server|
129
178
  server.start_immediately = false
130
179
  end
131
180
  end
132
181
 
182
+ # Create an SSL client socket from an IO object.
183
+ # @parameter io [IO] The underlying IO object.
184
+ # @returns [OpenSSL::SSL::SSLSocket] A new SSL client socket.
133
185
  def make_socket(io)
134
186
  ::OpenSSL::SSL::SSLSocket.new(io, self.context).tap do |socket|
135
187
  # We consider the underlying IO is owned by the SSL socket:
@@ -138,8 +190,9 @@ module IO::Endpoint
138
190
  end
139
191
 
140
192
  # Connect to the underlying endpoint and establish a SSL connection.
141
- # @yield [Socket] the socket which is being connected
142
- # @return [Socket] the connected socket
193
+ # @yields {|socket| ...} If a block is given, yields the SSL server socket.
194
+ # @parameter socket [Socket] The SSL server socket.
195
+ # @returns [Socket] the connected socket
143
196
  def bind(*arguments, **options, &block)
144
197
  if block_given?
145
198
  @endpoint.bind(*arguments, **options) do |server|
@@ -153,8 +206,9 @@ module IO::Endpoint
153
206
  end
154
207
 
155
208
  # Connect to the underlying endpoint and establish a SSL connection.
156
- # @yield [Socket] the socket which is being connected
157
- # @return [Socket] the connected socket
209
+ # @yields {|socket| ...} If a block is given, yields the connected SSL socket.
210
+ # @parameter socket [Socket] The connected SSL socket.
211
+ # @returns [Socket] the connected socket
158
212
  def connect(&block)
159
213
  socket = self.make_socket(@endpoint.connect)
160
214
 
@@ -170,7 +224,7 @@ module IO::Endpoint
170
224
  end
171
225
 
172
226
  return socket unless block_given?
173
-
227
+
174
228
  begin
175
229
  yield socket
176
230
  ensure
@@ -178,6 +232,9 @@ module IO::Endpoint
178
232
  end
179
233
  end
180
234
 
235
+ # Enumerate all endpoints by wrapping each underlying endpoint with SSL.
236
+ # @yields {|endpoint| ...} For each underlying endpoint, yields an SSL-wrapped endpoint.
237
+ # @parameter endpoint [SSLEndpoint] An SSL endpoint.
181
238
  def each
182
239
  return to_enum unless block_given?
183
240
 
@@ -186,13 +243,13 @@ module IO::Endpoint
186
243
  end
187
244
  end
188
245
  end
189
-
190
- # @param arguments
191
- # @param ssl_context [OpenSSL::SSL::SSLContext, nil]
192
- # @param hostname [String, nil]
193
- # @param options keyword arguments passed through to {Endpoint.tcp}
246
+
247
+ # @parameter arguments
248
+ # @parameter ssl_context [OpenSSL::SSL::SSLContext, nil]
249
+ # @parameter hostname [String, nil]
250
+ # @parameter options keyword arguments passed through to {Endpoint.tcp}
194
251
  #
195
- # @return [SSLEndpoint]
252
+ # @returns [SSLEndpoint]
196
253
  def self.ssl(*arguments, ssl_context: nil, hostname: nil, **options)
197
254
  SSLEndpoint.new(self.tcp(*arguments, **options), ssl_context: ssl_context, hostname: hostname)
198
255
  end
@@ -1,13 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2023-2024, by Samuel Williams.
4
+ # Copyright, 2023-2025, by Samuel Williams.
5
5
 
6
6
  require_relative "address_endpoint"
7
7
 
8
8
  module IO::Endpoint
9
9
  # This class doesn't exert ownership over the specified unix socket and ensures exclusive access by using `flock` where possible.
10
10
  class UNIXEndpoint < AddressEndpoint
11
+ # Initialize a new UNIX domain socket endpoint.
12
+ # @parameter path [String] The path to the UNIX socket.
13
+ # @parameter type [Integer] The socket type (defaults to Socket::SOCK_STREAM).
14
+ # @parameter options [Hash] Additional options to pass to the parent class.
11
15
  def initialize(path, type = Socket::SOCK_STREAM, **options)
12
16
  # I wonder if we should implement chdir behaviour in here if path is longer than 104 characters.
13
17
  super(Address.unix(path, type), **options)
@@ -15,16 +19,23 @@ module IO::Endpoint
15
19
  @path = path
16
20
  end
17
21
 
22
+ # Get a string representation of the UNIX endpoint.
23
+ # @returns [String] A string representation showing the socket path.
18
24
  def to_s
19
25
  "unix:#{@path}"
20
26
  end
21
27
 
28
+ # Get a detailed string representation of the UNIX endpoint.
29
+ # @returns [String] A detailed string representation including the path.
22
30
  def inspect
23
31
  "\#<#{self.class} path=#{@path.inspect}>"
24
32
  end
25
33
 
34
+ # @attribute [String] The path to the UNIX socket.
26
35
  attr :path
27
36
 
37
+ # Check if the socket is currently bound and accepting connections.
38
+ # @returns [Boolean] True if the socket is bound and accepting connections, false otherwise.
28
39
  def bound?
29
40
  self.connect do
30
41
  return true
@@ -35,6 +46,11 @@ module IO::Endpoint
35
46
  return false
36
47
  end
37
48
 
49
+ # Bind the UNIX socket, handling stale socket files.
50
+ # @yields {|socket| ...} If a block is given, yields the bound socket.
51
+ # @parameter socket [Socket] The bound socket.
52
+ # @returns [Array(Socket)] The bound socket.
53
+ # @raises [Errno::EADDRINUSE] If the socket is still in use by another process.
38
54
  def bind(...)
39
55
  super
40
56
  rescue Errno::EADDRINUSE
@@ -48,11 +64,11 @@ module IO::Endpoint
48
64
  end
49
65
  end
50
66
 
51
- # @param path [String]
52
- # @param type Socket type
53
- # @param options keyword arguments passed through to {UNIXEndpoint#initialize}
67
+ # @parameter path [String]
68
+ # @parameter type Socket type
69
+ # @parameter options keyword arguments passed through to {UNIXEndpoint#initialize}
54
70
  #
55
- # @return [UNIXEndpoint]
71
+ # @returns [UNIXEndpoint]
56
72
  def self.unix(path = "", type = ::Socket::SOCK_STREAM, **options)
57
73
  UNIXEndpoint.new(path, type, **options)
58
74
  end
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2023-2024, by Samuel Williams.
4
+ # Copyright, 2023-2025, by Samuel Williams.
5
5
 
6
+ # @namespace
6
7
  class IO
8
+ # @namespace
7
9
  module Endpoint
8
- VERSION = "0.15.2"
10
+ VERSION = "0.16.0"
9
11
  end
10
12
  end
@@ -6,10 +6,15 @@
6
6
  require "socket"
7
7
 
8
8
  module IO::Endpoint
9
+ # Represents a wrapper for socket operations that provides scheduling and configuration.
9
10
  class Wrapper
10
11
  include ::Socket::Constants
11
12
 
12
13
  if Fiber.respond_to?(:scheduler)
14
+ # Schedule a block to run asynchronously.
15
+ # Uses Fiber scheduler if available, otherwise falls back to Thread.
16
+ # @yields { ...} The block to schedule.
17
+ # @returns [Fiber, Thread] The scheduled fiber or thread.
13
18
  def schedule(&block)
14
19
  if Fiber.scheduler
15
20
  Fiber.schedule(&block)
@@ -18,6 +23,10 @@ module IO::Endpoint
18
23
  end
19
24
  end
20
25
  else
26
+ # Schedule a block to run asynchronously.
27
+ # Uses Thread for scheduling.
28
+ # @yields { ...} The block to schedule.
29
+ # @returns [Thread] The scheduled thread.
21
30
  def schedule(&block)
22
31
  Thread.new(&block)
23
32
  end
@@ -28,12 +37,18 @@ module IO::Endpoint
28
37
  schedule(&block)
29
38
  end
30
39
 
40
+ # Set the timeout for an IO object.
41
+ # @parameter io [IO] The IO object to set timeout on.
42
+ # @parameter timeout [Numeric, nil] The timeout value.
31
43
  def set_timeout(io, timeout)
32
44
  if io.respond_to?(:timeout=)
33
45
  io.timeout = timeout
34
46
  end
35
47
  end
36
48
 
49
+ # Set whether a socket should be buffered.
50
+ # @parameter socket [Socket] The socket to configure.
51
+ # @parameter buffered [Boolean] Whether the socket should be buffered.
37
52
  def set_buffered(socket, buffered)
38
53
  case buffered
39
54
  when true
@@ -220,6 +235,8 @@ module IO::Endpoint
220
235
 
221
236
  DEFAULT = new
222
237
 
238
+ # Get the default wrapper instance.
239
+ # @returns [Wrapper] The default wrapper instance.
223
240
  def self.default
224
241
  DEFAULT
225
242
  end
data/lib/io/endpoint.rb CHANGED
@@ -7,7 +7,10 @@ require_relative "endpoint/version"
7
7
  require_relative "endpoint/generic"
8
8
  require_relative "endpoint/shared_endpoint"
9
9
 
10
+ # Represents a collection of endpoint classes for network I/O operations.
10
11
  module IO::Endpoint
12
+ # Get the current file descriptor limit for the process.
13
+ # @returns [Integer] The soft limit for the number of open file descriptors.
11
14
  def self.file_descriptor_limit
12
15
  Process.getrlimit(Process::RLIMIT_NOFILE).first
13
16
  end
data/readme.md CHANGED
@@ -6,7 +6,9 @@ Provides a separation of concerns interface for IO endpoints. This allows you to
6
6
 
7
7
  ## Usage
8
8
 
9
- Please see the [documentation](https://socketry.github.io/io-endpoint) for more information.
9
+ Please see the [project documentation](https://socketry.github.io/io-endpoint) for more details.
10
+
11
+ - [Getting Started](https://socketry.github.io/io-endpointguides/getting-started/index) - This guide explains how to get started with `io-endpoint`, a library that provides a separation of concerns interface for network I/O endpoints.
10
12
 
11
13
  ## Contributing
12
14
 
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.15.2
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -36,12 +36,14 @@ cert_chain:
36
36
  Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
37
37
  voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
38
38
  -----END CERTIFICATE-----
39
- date: 2025-02-20 00:00:00.000000000 Z
39
+ date: 1980-01-02 00:00:00.000000000 Z
40
40
  dependencies: []
41
41
  executables: []
42
42
  extensions: []
43
43
  extra_rdoc_files: []
44
44
  files:
45
+ - context/getting-started.md
46
+ - context/index.yaml
45
47
  - lib/io/endpoint.rb
46
48
  - lib/io/endpoint/address_endpoint.rb
47
49
  - lib/io/endpoint/bound_endpoint.rb
@@ -70,14 +72,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
70
72
  requirements:
71
73
  - - ">="
72
74
  - !ruby/object:Gem::Version
73
- version: '3.1'
75
+ version: '3.2'
74
76
  required_rubygems_version: !ruby/object:Gem::Requirement
75
77
  requirements:
76
78
  - - ">="
77
79
  - !ruby/object:Gem::Version
78
80
  version: '0'
79
81
  requirements: []
80
- rubygems_version: 3.6.2
82
+ rubygems_version: 3.6.9
81
83
  specification_version: 4
82
84
  summary: Provides a separation of concerns interface for IO endpoints.
83
85
  test_files: []
metadata.gz.sig CHANGED
Binary file