io-endpoint 0.15.2 → 0.17.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/context/getting-started.md +113 -0
- data/context/index.yaml +17 -0
- data/context/named-endpoints.md +140 -0
- data/lib/io/endpoint/address_endpoint.rb +13 -4
- data/lib/io/endpoint/bound_endpoint.rb +25 -0
- data/lib/io/endpoint/composite_endpoint.rb +29 -0
- data/lib/io/endpoint/connected_endpoint.rb +25 -1
- data/lib/io/endpoint/generic.rb +19 -13
- data/lib/io/endpoint/host_endpoint.rb +31 -14
- data/lib/io/endpoint/named_endpoints.rb +74 -0
- data/lib/io/endpoint/shared_endpoint.rb +1 -1
- data/lib/io/endpoint/socket_endpoint.rb +21 -1
- data/lib/io/endpoint/ssl_endpoint.rb +69 -12
- data/lib/io/endpoint/unix_endpoint.rb +21 -5
- data/lib/io/endpoint/version.rb +4 -2
- data/lib/io/endpoint/wrapper.rb +17 -0
- data/lib/io/endpoint.rb +4 -1
- data/license.md +1 -1
- data/readme.md +55 -5
- data/releases.md +107 -0
- data.tar.gz.sig +0 -0
- metadata +9 -4
- 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: 99b21a2454e10904ebbf717bf4c1f5dcc337db1a50c88cefea5ef25116759ef2
|
|
4
|
+
data.tar.gz: 4f789220815ddc651cf8397697e30924660c231bebcdb2375f14eac1785fed39
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4c1720d279b749cd199455eb42ef4fe023a62287100230734d989b6dd8a944b8585c5f5565fa5899aa30b8563ae6350e938632e0c7b2a0b816d80f7273ddf81a
|
|
7
|
+
data.tar.gz: 06baff00190678e64cd396e6ac90e4f6a3e136295fabbaa92b6e7ab7d3894ab9345e084430745d6e0ba0633af7ed240a8478009fafc12134d3886aad4c642e05
|
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
|
+
```
|
data/context/index.yaml
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
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.
|
|
13
|
+
- path: named-endpoints.md
|
|
14
|
+
title: Named Endpoints
|
|
15
|
+
description: This guide explains how to use `IO::Endpoint::NamedEndpoints` to manage
|
|
16
|
+
multiple endpoints by name, enabling scenarios like running the same application
|
|
17
|
+
on different protocols or ports.
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Named Endpoints
|
|
2
|
+
|
|
3
|
+
This guide explains how to use `IO::Endpoint::NamedEndpoints` to manage multiple endpoints by name, enabling scenarios like running the same application on different protocols or ports.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`NamedEndpoints` is a collection of endpoints that can be accessed by symbolic names. Unlike {ruby IO::Endpoint::CompositeEndpoint}, which treats endpoints as an ordered list for failover, `NamedEndpoints` allows you to:
|
|
8
|
+
|
|
9
|
+
- **Access endpoints by name**: Use symbolic keys like `:http1` or `:http2` instead of array indices.
|
|
10
|
+
- **Run multiple configurations**: Serve the same application on different protocols, ports, or transports simultaneously.
|
|
11
|
+
- **Iterate over endpoints**: Process all endpoints while maintaining their names for configuration lookup.
|
|
12
|
+
|
|
13
|
+
## When to Use NamedEndpoints
|
|
14
|
+
|
|
15
|
+
Use `NamedEndpoints` when you need to:
|
|
16
|
+
|
|
17
|
+
- Run the same server application on multiple endpoints with different configurations (e.g., HTTP/1 and HTTP/2).
|
|
18
|
+
- Access endpoints by symbolic names rather than position.
|
|
19
|
+
- Bind multiple endpoints and create servers for each one.
|
|
20
|
+
- Manage a collection of endpoints where each has a specific role or configuration.
|
|
21
|
+
|
|
22
|
+
If you need failover behavior (trying endpoints in order until one succeeds), use {ruby IO::Endpoint::CompositeEndpoint} instead.
|
|
23
|
+
|
|
24
|
+
## Creating Named Endpoints
|
|
25
|
+
|
|
26
|
+
### Using the Constructor
|
|
27
|
+
|
|
28
|
+
Create a `NamedEndpoints` instance by passing a hash of endpoints:
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
require "io/endpoint"
|
|
32
|
+
|
|
33
|
+
http1_endpoint = IO::Endpoint.tcp("localhost", 8080)
|
|
34
|
+
http2_endpoint = IO::Endpoint.tcp("localhost", 8090)
|
|
35
|
+
|
|
36
|
+
named = IO::Endpoint::NamedEndpoints.new(
|
|
37
|
+
http1: http1_endpoint,
|
|
38
|
+
http2: http2_endpoint
|
|
39
|
+
)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Using the Factory Method
|
|
43
|
+
|
|
44
|
+
The `IO::Endpoint.named` factory method provides a convenient way to create named endpoints:
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
require "io/endpoint"
|
|
48
|
+
|
|
49
|
+
named = IO::Endpoint.named(
|
|
50
|
+
http1: IO::Endpoint.tcp("localhost", 8080),
|
|
51
|
+
http2: IO::Endpoint.tcp("localhost", 8090),
|
|
52
|
+
https: IO::Endpoint.ssl("localhost", 8443)
|
|
53
|
+
)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Accessing Endpoints
|
|
57
|
+
|
|
58
|
+
Access endpoints by their names using the `[]` operator:
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
named = IO::Endpoint.named(
|
|
62
|
+
http1: IO::Endpoint.tcp("localhost", 8080),
|
|
63
|
+
http2: IO::Endpoint.tcp("localhost", 8090)
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Access by name
|
|
67
|
+
http1 = named[:http1]
|
|
68
|
+
http2 = named[:http2]
|
|
69
|
+
|
|
70
|
+
# Returns nil if not found
|
|
71
|
+
missing = named[:nonexistent] # => nil
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Iterating Over Endpoints
|
|
75
|
+
|
|
76
|
+
### Using `each`
|
|
77
|
+
|
|
78
|
+
The `each` method yields both the name and endpoint:
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
named = IO::Endpoint.named(
|
|
82
|
+
http1: IO::Endpoint.tcp("localhost", 8080),
|
|
83
|
+
http2: IO::Endpoint.tcp("localhost", 8090)
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
named.each do |name, endpoint|
|
|
87
|
+
puts "Endpoint #{name} is bound to #{endpoint}"
|
|
88
|
+
end
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
To map over endpoint values, use `endpoints.values.map`:
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
protocols = named.endpoints.values.map do |endpoint|
|
|
95
|
+
endpoint.protocol.to_s
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# => ["HTTP1", "HTTP2"]
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Binding Endpoints
|
|
102
|
+
|
|
103
|
+
To bind endpoints, iterate over the collection and bind each endpoint individually, or use the `bound` method to create a new collection with all endpoints bound.
|
|
104
|
+
|
|
105
|
+
The `bound` method creates a new `NamedEndpoints` instance where all endpoints are bound:
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
named = IO::Endpoint.named(
|
|
109
|
+
http1: IO::Endpoint.tcp("localhost", 8080),
|
|
110
|
+
http2: IO::Endpoint.tcp("localhost", 8090)
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
bound_named = named.bound(reuse_address: true)
|
|
114
|
+
|
|
115
|
+
# All endpoints are now bound
|
|
116
|
+
bound_named.each do |name, bound_endpoint|
|
|
117
|
+
server = bound_endpoint.sockets.first
|
|
118
|
+
server.listen(10)
|
|
119
|
+
end
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Connecting to Endpoints
|
|
123
|
+
|
|
124
|
+
To connect to a specific endpoint, access it by name and call `connect` on that endpoint:
|
|
125
|
+
|
|
126
|
+
```ruby
|
|
127
|
+
named = IO::Endpoint.named(
|
|
128
|
+
primary: IO::Endpoint.tcp("server1.example.com", 80),
|
|
129
|
+
secondary: IO::Endpoint.tcp("server2.example.com", 80)
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Connect to a specific endpoint by name
|
|
133
|
+
named[:primary].connect do |socket|
|
|
134
|
+
socket.write("GET / HTTP/1.1\r\n\r\n")
|
|
135
|
+
response = socket.read
|
|
136
|
+
puts response
|
|
137
|
+
end
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
If you need failover behavior (trying endpoints in order until one succeeds), use {ruby IO::Endpoint::CompositeEndpoint} instead.
|
|
@@ -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
|
-
# @
|
|
38
|
-
#
|
|
39
|
-
# @
|
|
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
|
-
# @
|
|
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
|
data/lib/io/endpoint/generic.rb
CHANGED
|
@@ -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
|
-
# @
|
|
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
|
-
# @
|
|
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
|
-
# @
|
|
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
|
-
# @
|
|
52
|
+
# @returns [Integer, nil] The value for SO_LINGER.
|
|
48
53
|
def linger
|
|
49
54
|
@options[:linger]
|
|
50
55
|
end
|
|
51
56
|
|
|
52
|
-
# @
|
|
57
|
+
# @returns [Numeric] The default timeout for socket operations.
|
|
53
58
|
def timeout
|
|
54
59
|
@options[:timeout]
|
|
55
60
|
end
|
|
56
61
|
|
|
57
|
-
# @
|
|
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| ...}
|
|
65
|
-
#
|
|
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
|
-
# @
|
|
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
|
|
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| ...}
|
|
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
|
-
# @
|
|
101
|
-
# @
|
|
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"
|