io-endpoint 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c10e7e5b154e1ad990982315ba03565d5e880f392eb404a696c63eb3c9240cb7
4
+ data.tar.gz: 37ebf4a7b0c0d7905c0e4b9d23373e1665f595637ab049163dafab7d7c9df535
5
+ SHA512:
6
+ metadata.gz: f8446d2169e82a34b836edaaf76b4e13409202dc1f0cebf64c80f693728bfe4dcd133dddd2ee7fd932bbc13b45e3d37b8ab2a7b928b3a39629455cd1fd9e62bf
7
+ data.tar.gz: 0ca66dd6bffc9925e7e782d46234b61777d432df527b8595ed10d26655a2f4d30a0eb74e5cff54ae5ec31b21a3e94af79eb1609c546d9c2b552817937ef47adc
checksums.yaml.gz.sig ADDED
@@ -0,0 +1,3 @@
1
+ lй��\�YAuQV[�tW��?]���k�J�bW%�O� ����dv]�����ɹD�
2
+ $�,��5(E��i&jB��2:B�T>B,Pd���`4���UZs�ؽ�|.�O?�]�< g��?1�|�@�
3
+ ̓��i�L�������iS�Rk�4����G��o��r'�
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2023, by Samuel Williams.
5
+
6
+ require 'socket'
7
+
8
+ require_relative 'generic'
9
+ require_relative 'wrapper'
10
+
11
+ module IO::Endpoint
12
+ class AddressEndpoint < Generic
13
+ def initialize(address, **options)
14
+ super(**options)
15
+
16
+ @address = address
17
+ end
18
+
19
+ def to_s
20
+ "\#<#{self.class} #{@address.inspect}>"
21
+ end
22
+
23
+ attr :address
24
+
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
28
+ def bind(wrapper = Wrapper.default, &block)
29
+ wrapper.bind(@address, **@options, &block)
30
+ end
31
+
32
+ # Connects a socket to the given address. If a block is given, the socket will be automatically closed when the block exits.
33
+ # @return [Socket] the connected socket
34
+ def connect(wrapper = Wrapper.default, &block)
35
+ wrapper.connect(@address, **@options, &block)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2023, by Samuel Williams.
5
+
6
+ require_relative 'generic'
7
+
8
+ module IO::Endpoint
9
+ class CompositeEndpoint < Generic
10
+ def initialize(endpoints, **options)
11
+ super(**options)
12
+ @endpoints = endpoints
13
+ end
14
+
15
+ def each(&block)
16
+ @endpoints.each(&block)
17
+ end
18
+
19
+ def connect(&block)
20
+ last_error = nil
21
+
22
+ @endpoints.each do |endpoint|
23
+ begin
24
+ return endpoint.connect(&block)
25
+ rescue => last_error
26
+ end
27
+ end
28
+
29
+ raise last_error
30
+ end
31
+
32
+ def bind(&block)
33
+ @endpoints.map(&:bind)
34
+ end
35
+ end
36
+
37
+ class Endpoint
38
+ def self.composite(*endpoints, **options)
39
+ CompositeEndpoint.new(endpoints, **options)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2023, by Samuel Williams.
5
+
6
+ # require_relative 'address'
7
+ require 'uri'
8
+ require 'socket'
9
+
10
+ module IO::Endpoint
11
+ Address = Addrinfo
12
+
13
+ # Endpoints represent a way of connecting or binding to an address.
14
+ class Generic
15
+ def initialize(**options)
16
+ @options = options.freeze
17
+ end
18
+
19
+ def with(**options)
20
+ dup = self.dup
21
+
22
+ dup.options = @options.merge(options)
23
+
24
+ return dup
25
+ end
26
+
27
+ attr_accessor :options
28
+
29
+ # @return [String] The hostname of the bound socket.
30
+ def hostname
31
+ @options[:hostname]
32
+ end
33
+
34
+ # 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`.
36
+ def reuse_port?
37
+ @options[:reuse_port]
38
+ end
39
+
40
+ # 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`.
42
+ def reuse_address?
43
+ @options[:reuse_address]
44
+ end
45
+
46
+ # 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.
48
+ def linger
49
+ @options[:linger]
50
+ end
51
+
52
+ # @return [Numeric] The default timeout for socket operations.
53
+ def timeout
54
+ @options[:timeout]
55
+ end
56
+
57
+ # @return [Address] the address to bind to before connecting.
58
+ def local_address
59
+ @options[:local_address]
60
+ end
61
+
62
+ # Endpoints sometimes have multiple paths.
63
+ # @yield [Endpoint] Enumerate all discrete paths as endpoints.
64
+ def each
65
+ return to_enum unless block_given?
66
+
67
+ yield self
68
+ end
69
+
70
+ # Accept connections from the specified endpoint.
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
83
+ end
84
+ end
85
+
86
+ # Map all endpoints by invoking `#bind`.
87
+ # @yield the bound wrapper.
88
+ def bound
89
+ wrappers = []
90
+
91
+ self.each do |endpoint|
92
+ wrapper = endpoint.bind
93
+ wrappers << wrapper
94
+
95
+ yield wrapper
96
+ end
97
+
98
+ return wrappers
99
+ ensure
100
+ wrappers.each(&:close) if $!
101
+ end
102
+
103
+ # 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.
104
+ #
105
+ # @param string [String] URI as string. Scheme will decide implementation used.
106
+ # @param options keyword arguments passed through to {#initialize}
107
+ #
108
+ # @see Endpoint.ssl ssl - invoked when parsing a URL with the ssl scheme "ssl://127.0.0.1"
109
+ # @see Endpoint.tcp tcp - invoked when parsing a URL with the tcp scheme: "tcp://127.0.0.1"
110
+ # @see Endpoint.udp udp - invoked when parsing a URL with the udp scheme: "udp://127.0.0.1"
111
+ # @see Endpoint.unix unix - invoked when parsing a URL with the unix scheme: "unix://127.0.0.1"
112
+ def self.parse(string, **options)
113
+ uri = URI.parse(string)
114
+
115
+ self.public_send(uri.scheme, uri.host, uri.port, **options)
116
+ end
117
+
118
+ protected
119
+
120
+ def accepted(socket)
121
+ socket
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2023, by Samuel Williams.
5
+
6
+ require_relative 'address_endpoint'
7
+
8
+ module IO::Endpoint
9
+ class HostEndpoint < Generic
10
+ def initialize(specification, **options)
11
+ super(**options)
12
+
13
+ @specification = specification
14
+ end
15
+
16
+ def to_s
17
+ nodename, service, family, socktype, protocol, flags = @specification
18
+
19
+ "\#<#{self.class} name=#{nodename.inspect} service=#{service.inspect} family=#{family.inspect} type=#{socktype.inspect} protocol=#{protocol.inspect} flags=#{flags.inspect}>"
20
+ end
21
+
22
+ def address
23
+ @specification
24
+ end
25
+
26
+ def hostname
27
+ @specification.first
28
+ end
29
+
30
+ # Try to connect to the given host by connecting to each address in sequence until a connection is made.
31
+ # @yield [Socket] the socket which is being connected, may be invoked more than once
32
+ # @return [Socket] the connected socket
33
+ # @raise if no connection could complete successfully
34
+ def connect
35
+ last_error = nil
36
+
37
+ Addrinfo.foreach(*@specification) do |address|
38
+ begin
39
+ socket = Socket.connect(address, **@options)
40
+ rescue Errno::ECONNREFUSED, Errno::ENETUNREACH, Errno::EAGAIN => last_error
41
+ else
42
+ return socket unless block_given?
43
+
44
+ begin
45
+ return yield(socket)
46
+ ensure
47
+ socket.close
48
+ end
49
+ end
50
+ end
51
+
52
+ raise last_error
53
+ end
54
+
55
+ # Invokes the given block for every address which can be bound to.
56
+ # @yield [Socket] the bound socket
57
+ # @return [Array<Socket>] an array of bound sockets
58
+ def bind(&block)
59
+ Addrinfo.foreach(*@specification).map do |address|
60
+ Socket.bind(address, **@options, &block)
61
+ end
62
+ end
63
+
64
+ # @yield [AddressEndpoint] address endpoints by resolving the given host specification
65
+ def each
66
+ return to_enum unless block_given?
67
+
68
+ Addrinfo.foreach(*@specification) do |address|
69
+ yield AddressEndpoint.new(address, **@options)
70
+ end
71
+ end
72
+ end
73
+
74
+ # @param args nodename, service, family, socktype, protocol, flags. `socktype` will be set to Socket::SOCK_STREAM.
75
+ # @param options keyword arguments passed on to {HostEndpoint#initialize}
76
+ #
77
+ # @return [HostEndpoint]
78
+ def self.tcp(*args, **options)
79
+ args[3] = ::Socket::SOCK_STREAM
80
+
81
+ HostEndpoint.new(args, **options)
82
+ end
83
+
84
+ # @param args nodename, service, family, socktype, protocol, flags. `socktype` will be set to Socket::SOCK_DGRAM.
85
+ # @param options keyword arguments passed on to {HostEndpoint#initialize}
86
+ #
87
+ # @return [HostEndpoint]
88
+ def self.udp(*args, **options)
89
+ args[3] = ::Socket::SOCK_DGRAM
90
+
91
+ HostEndpoint.new(args, **options)
92
+ end
93
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2023, by Samuel Williams.
5
+
6
+ require_relative 'generic'
7
+ require_relative 'composite_endpoint'
8
+
9
+ module IO::Endpoint
10
+ # Pre-connect and pre-bind sockets so that it can be used between processes.
11
+ class SharedEndpoint < Generic
12
+ # Create a new `SharedEndpoint` by binding to the given endpoint.
13
+ def self.bound(endpoint, backlog: Socket::SOMAXCONN, close_on_exec: false)
14
+ wrappers = endpoint.bound do |server|
15
+ # 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.
16
+ begin
17
+ server.listen(backlog)
18
+ rescue Errno::EOPNOTSUPP
19
+ # Ignore.
20
+ end
21
+
22
+ server.close_on_exec = close_on_exec
23
+ end
24
+
25
+ return self.new(endpoint, wrappers)
26
+ end
27
+
28
+ # Create a new `SharedEndpoint` by connecting to the given endpoint.
29
+ def self.connected(endpoint, close_on_exec: false)
30
+ wrapper = endpoint.connect
31
+
32
+ wrapper.close_on_exec = close_on_exec
33
+
34
+ return self.new(endpoint, [wrapper])
35
+ end
36
+
37
+ def initialize(endpoint, wrappers, **options)
38
+ super(**options)
39
+
40
+ @endpoint = endpoint
41
+ @wrappers = wrappers
42
+ end
43
+
44
+ attr :endpoint
45
+ attr :wrappers
46
+
47
+ def local_address_endpoint(**options)
48
+ endpoints = @wrappers.map do |wrapper|
49
+ AddressEndpoint.new(wrapper.to_io.local_address)
50
+ end
51
+
52
+ return CompositeEndpoint.new(endpoints, **options)
53
+ end
54
+
55
+ def remote_address_endpoint(**options)
56
+ endpoints = @wrappers.map do |wrapper|
57
+ AddressEndpoint.new(wrapper.to_io.remote_address)
58
+ end
59
+
60
+ return CompositeEndpoint.new(endpoints, **options)
61
+ end
62
+
63
+ # Close all the internal wrappers.
64
+ def close
65
+ @wrappers.each(&:close)
66
+ @wrappers.clear
67
+ end
68
+
69
+ def bind
70
+ @wrappers.each do |server|
71
+ server = server.dup
72
+
73
+ begin
74
+ yield server
75
+ ensure
76
+ server.close
77
+ end
78
+ end
79
+ end
80
+
81
+ def connect
82
+ @wrappers.each do |peer|
83
+ peer = peer.dup
84
+
85
+ begin
86
+ yield peer
87
+ ensure
88
+ peer.close
89
+ end
90
+ end
91
+ end
92
+
93
+ def accept(backlog = nil, &block)
94
+ bind do |server|
95
+ server.accept_each(&block)
96
+ end
97
+ end
98
+
99
+ def to_s
100
+ "\#<#{self.class} #{@wrappers.size} descriptors for #{@endpoint}>"
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2023, by Samuel Williams.
5
+
6
+ require_relative 'generic'
7
+
8
+ module IO::Endpoint
9
+ # This class doesn't exert ownership over the specified socket, wraps a native ::IO.
10
+ class SocketEndpoint < Generic
11
+ def initialize(socket, **options)
12
+ super(**options)
13
+
14
+ @socket = socket
15
+ end
16
+
17
+ def to_s
18
+ "\#<#{self.class} #{@socket.inspect}>"
19
+ end
20
+
21
+ attr :socket
22
+
23
+ def bind(&block)
24
+ if block_given?
25
+ yield @socket
26
+ else
27
+ return @socket
28
+ end
29
+ end
30
+
31
+ def connect(&block)
32
+ if block_given?
33
+ yield @socket
34
+ else
35
+ return @socket
36
+ end
37
+ end
38
+ end
39
+
40
+ def self.socket(socket, **options)
41
+ SocketEndpoint.new(socket, **options)
42
+ end
43
+ end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2023, by Samuel Williams.
5
+
6
+ require_relative 'host_endpoint'
7
+ require_relative 'generic'
8
+
9
+ require 'openssl'
10
+
11
+ module IO::Endpoint
12
+ class SSLEndpoint < Generic
13
+ def initialize(endpoint, **options)
14
+ super(**options)
15
+
16
+ @endpoint = endpoint
17
+
18
+ if ssl_context = options[:ssl_context]
19
+ @context = build_context(ssl_context)
20
+ else
21
+ @context = nil
22
+ end
23
+ end
24
+
25
+ def to_s
26
+ "\#<#{self.class} #{@endpoint}>"
27
+ end
28
+
29
+ def address
30
+ @endpoint.address
31
+ end
32
+
33
+ def hostname
34
+ @options[:hostname] || @endpoint.hostname
35
+ end
36
+
37
+ attr :endpoint
38
+ attr :options
39
+
40
+ def params
41
+ @options[:ssl_params]
42
+ end
43
+
44
+ def build_context(context = OpenSSL::SSL::SSLContext.new)
45
+ if params = self.params
46
+ context.set_params(params)
47
+ end
48
+
49
+ context.setup
50
+ context.freeze
51
+
52
+ return context
53
+ end
54
+
55
+ def context
56
+ @context ||= build_context
57
+ end
58
+
59
+ # Connect to the underlying endpoint and establish a SSL connection.
60
+ # @yield [Socket] the socket which is being connected
61
+ # @return [Socket] the connected socket
62
+ def bind
63
+ if block_given?
64
+ @endpoint.bind do |server|
65
+ yield OpenSSL::SSL::SSLServer.new(server, context)
66
+ end
67
+ else
68
+ return OpenSSL::SSL::SSLServer.new(@endpoint.bind, context)
69
+ end
70
+ end
71
+
72
+ # Connect to the underlying endpoint and establish a SSL connection.
73
+ # @yield [Socket] the socket which is being connected
74
+ # @return [Socket] the connected socket
75
+ def connect(&block)
76
+ socket = OpenSSL::SSL::SSLSocket.new(@endpoint.connect, context)
77
+
78
+ if hostname = self.hostname
79
+ socket.hostname = hostname
80
+ end
81
+
82
+ begin
83
+ socket.connect
84
+ rescue
85
+ socket.close
86
+ raise
87
+ end
88
+
89
+ return socket unless block_given?
90
+
91
+ begin
92
+ yield socket
93
+ ensure
94
+ socket.close
95
+ end
96
+ end
97
+
98
+ def each
99
+ return to_enum unless block_given?
100
+
101
+ @endpoint.each do |endpoint|
102
+ yield self.class.new(endpoint, **@options)
103
+ end
104
+ end
105
+
106
+ protected
107
+
108
+ def accepted(socket)
109
+ socket.accept
110
+ socket
111
+ end
112
+ end
113
+
114
+ # @param args
115
+ # @param ssl_context [OpenSSL::SSL::SSLContext, nil]
116
+ # @param hostname [String, nil]
117
+ # @param options keyword arguments passed through to {Endpoint.tcp}
118
+ #
119
+ # @return [SSLEndpoint]
120
+ def self.ssl(*args, ssl_context: nil, hostname: nil, **options)
121
+ SSLEndpoint.new(self.tcp(*args, **options), ssl_context: ssl_context, hostname: hostname)
122
+ end
123
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2023, by Samuel Williams.
5
+
6
+ require_relative 'address_endpoint'
7
+
8
+ module IO::Endpoint
9
+ # This class doesn't exert ownership over the specified unix socket and ensures exclusive access by using `flock` where possible.
10
+ class UNIXEndpoint < AddressEndpoint
11
+ def initialize(path, type = Socket::SOCK_STREAM, **options)
12
+ # I wonder if we should implement chdir behaviour in here if path is longer than 104 characters.
13
+ super(Address.unix(path, type), **options)
14
+
15
+ @path = path
16
+ end
17
+
18
+ def to_s
19
+ "\#<#{self.class} #{@path.inspect}>"
20
+ end
21
+
22
+ attr :path
23
+
24
+ def bound?
25
+ self.connect do
26
+ return true
27
+ end
28
+ rescue Errno::ECONNREFUSED
29
+ return false
30
+ end
31
+
32
+ def bind(&block)
33
+ super
34
+ rescue Errno::EADDRINUSE
35
+ # If you encounter EADDRINUSE from `bind()`, you can check if the socket is actually accepting connections by attempting to `connect()` to it. If the socket is still bound by an active process, the connection will succeed. Otherwise, it should be safe to `unlink()` the path and try again.
36
+ if !bound? && File.exist?(@path)
37
+ File.unlink(@path)
38
+ retry
39
+ else
40
+ raise
41
+ end
42
+ end
43
+ end
44
+
45
+ # @param path [String]
46
+ # @param type Socket type
47
+ # @param options keyword arguments passed through to {UNIXEndpoint#initialize}
48
+ #
49
+ # @return [UNIXEndpoint]
50
+ def self.unix(path = "", type = ::Socket::SOCK_STREAM, **options)
51
+ UNIXEndpoint.new(path, type, **options)
52
+ end
53
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2023, by Samuel Williams.
5
+
6
+ class IO
7
+ module Endpoint
8
+ VERSION = "0.1.0"
9
+ end
10
+ end
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2023, by Samuel Williams.
5
+
6
+ require 'socket'
7
+
8
+ module IO::Endpoint
9
+ class Wrapper
10
+ include ::Socket::Constants
11
+
12
+ if $stdin.respond_to?(:timeout=)
13
+ def self.set_timeout(io, timeout)
14
+ io.timeout = timeout
15
+ end
16
+ else
17
+ def self.set_timeout(io, timeout)
18
+ warn "IO#timeout= not supported on this platform."
19
+ end
20
+ end
21
+
22
+ def async
23
+ raise NotImplementedError
24
+ end
25
+
26
+ # Build and wrap the underlying io.
27
+ # @option reuse_port [Boolean] Allow this port to be bound in multiple processes.
28
+ # @option reuse_address [Boolean] Allow this port to be bound in multiple processes.
29
+ def build(*arguments, timeout: nil, reuse_address: true, reuse_port: nil, linger: nil)
30
+ socket = ::Socket.new(*arguments)
31
+
32
+ # Set the timeout:
33
+ if timeout
34
+ set_timeout(socket, timeout)
35
+ end
36
+
37
+ if reuse_address
38
+ socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
39
+ end
40
+
41
+ if reuse_port
42
+ socket.setsockopt(SOL_SOCKET, SO_REUSEPORT, 1)
43
+ end
44
+
45
+ if linger
46
+ socket.setsockopt(SOL_SOCKET, SO_LINGER, linger)
47
+ end
48
+
49
+ yield socket if block_given?
50
+
51
+ return socket
52
+ rescue
53
+ socket&.close
54
+ end
55
+
56
+ # Establish a connection to a given `remote_address`.
57
+ # @example
58
+ # socket = Async::IO::Socket.connect(Async::IO::Address.tcp("8.8.8.8", 53))
59
+ # @param remote_address [Address] The remote address to connect to.
60
+ # @option local_address [Address] The local address to bind to before connecting.
61
+ def connect(remote_address, local_address: nil, **options)
62
+ socket = build(remote_address.afamily, remote_address.socktype, remote_address.protocol, **options) do |socket|
63
+ if local_address
64
+ if defined?(IP_BIND_ADDRESS_NO_PORT)
65
+ # Inform the kernel (Linux 4.2+) to not reserve an ephemeral port when using bind(2) with a port number of 0. The port will later be automatically chosen at connect(2) time, in a way that allows sharing a source port as long as the 4-tuple is unique.
66
+ socket.setsockopt(SOL_IP, IP_BIND_ADDRESS_NO_PORT, 1)
67
+ end
68
+
69
+ socket.bind(local_address.to_sockaddr)
70
+ end
71
+ end
72
+
73
+ begin
74
+ socket.connect(remote_address.to_sockaddr)
75
+ rescue Exception
76
+ socket.close
77
+ raise
78
+ end
79
+
80
+ return socket unless block_given?
81
+
82
+ begin
83
+ yield socket
84
+ ensure
85
+ socket.close
86
+ end
87
+ end
88
+
89
+ # Bind to a local address.
90
+ # @example
91
+ # socket = Async::IO::Socket.bind(Async::IO::Address.tcp("0.0.0.0", 9090))
92
+ # @param local_address [Address] The local address to bind to.
93
+ # @option protocol [Integer] The socket protocol to use.
94
+ def bind(local_address, protocol: 0, **options, &block)
95
+ socket = build(local_address.afamily, local_address.socktype, protocol, **options) do |socket|
96
+ socket.bind(local_address.to_sockaddr)
97
+ end
98
+
99
+ return socket unless block_given?
100
+
101
+ async do
102
+ begin
103
+ yield socket
104
+ ensure
105
+ socket.close
106
+ end
107
+ end
108
+ end
109
+
110
+ # Bind to a local address and accept connections in a loop.
111
+ def accept(*arguments, backlog: SOMAXCONN, **options, &block)
112
+ bind(*arguments, **options) do |server|
113
+ server.listen(backlog) if backlog
114
+
115
+ async do
116
+ while true
117
+ server.accept(&block)
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ class ThreadWrapper < Wrapper
125
+ def async(&block)
126
+ Thread.new(&block)
127
+ end
128
+ end
129
+
130
+ class FiberWrapper < Wrapper
131
+ def async(&block)
132
+ Fiber.schedule(&block)
133
+ end
134
+ end
135
+
136
+ def Wrapper.default
137
+ if Fiber.scheduler
138
+ FiberWrapper.new
139
+ else
140
+ ThreadWrapper.new
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2023, by Samuel Williams.
5
+
6
+ require_relative "endpoint/version"
7
+ require_relative "endpoint/generic"
8
+
9
+ module IO::Endpoint
10
+ def self.file_descriptor_limit
11
+ Process.getrlimit(Process::RLIMIT_NOFILE).first
12
+ end
13
+ end
data/license.md ADDED
@@ -0,0 +1,21 @@
1
+ # MIT License
2
+
3
+ Copyright, 2023, by Samuel Williams.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/readme.md ADDED
@@ -0,0 +1,23 @@
1
+ # IO::Endpoint
2
+
3
+ Provides a separation of concerns interface for IO endpoints. This allows you to write code which is agnostic to the underlying IO implementation.
4
+
5
+ [![Development Status](https://github.com/socketry/io-endpoint/workflows/Test/badge.svg)](https://github.com/socketry/io-endpoint/actions?workflow=Test)
6
+
7
+ ## Usage
8
+
9
+ Please see the [documentation](https://socketry.github.io/io-endpoint) for more information.
10
+
11
+ ## Contributing
12
+
13
+ We welcome contributions to this project.
14
+
15
+ 1. Fork it.
16
+ 2. Create your feature branch (`git checkout -b my-new-feature`).
17
+ 3. Commit your changes (`git commit -am 'Add some feature'`).
18
+ 4. Push to the branch (`git push origin my-new-feature`).
19
+ 5. Create new Pull Request.
20
+
21
+ ## See Also
22
+
23
+ - [async-io](https://github.com/socketry/async-io) — Asynchronous IO primitives.
data.tar.gz.sig ADDED
@@ -0,0 +1 @@
1
+ ���m,�ԡ,��!FWp�$2@�A>���(�����d:y��h�7D��?�Wm�F'���}.uQ�}�ٛ����P��+[�o�1�ˏh2Z�Ճj�;*���, �X�$|�]�T6b�q����VC�m �u����h�u�KeL����yi��\�O����p$,��
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: io-endpoint
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Samuel Williams
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain:
11
+ - |
12
+ -----BEGIN CERTIFICATE-----
13
+ MIIE2DCCA0CgAwIBAgIBATANBgkqhkiG9w0BAQsFADBhMRgwFgYDVQQDDA9zYW11
14
+ ZWwud2lsbGlhbXMxHTAbBgoJkiaJk/IsZAEZFg1vcmlvbnRyYW5zZmVyMRIwEAYK
15
+ CZImiZPyLGQBGRYCY28xEjAQBgoJkiaJk/IsZAEZFgJuejAeFw0yMjA4MDYwNDUz
16
+ MjRaFw0zMjA4MDMwNDUzMjRaMGExGDAWBgNVBAMMD3NhbXVlbC53aWxsaWFtczEd
17
+ MBsGCgmSJomT8ixkARkWDW9yaW9udHJhbnNmZXIxEjAQBgoJkiaJk/IsZAEZFgJj
18
+ bzESMBAGCgmSJomT8ixkARkWAm56MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIB
19
+ igKCAYEAomvSopQXQ24+9DBB6I6jxRI2auu3VVb4nOjmmHq7XWM4u3HL+pni63X2
20
+ 9qZdoq9xt7H+RPbwL28LDpDNflYQXoOhoVhQ37Pjn9YDjl8/4/9xa9+NUpl9XDIW
21
+ sGkaOY0eqsQm1pEWkHJr3zn/fxoKPZPfaJOglovdxf7dgsHz67Xgd/ka+Wo1YqoE
22
+ e5AUKRwUuvaUaumAKgPH+4E4oiLXI4T1Ff5Q7xxv6yXvHuYtlMHhYfgNn8iiW8WN
23
+ XibYXPNP7NtieSQqwR/xM6IRSoyXKuS+ZNGDPUUGk8RoiV/xvVN4LrVm9upSc0ss
24
+ RZ6qwOQmXCo/lLcDUxJAgG95cPw//sI00tZan75VgsGzSWAOdjQpFM0l4dxvKwHn
25
+ tUeT3ZsAgt0JnGqNm2Bkz81kG4A2hSyFZTFA8vZGhp+hz+8Q573tAR89y9YJBdYM
26
+ zp0FM4zwMNEUwgfRzv1tEVVUEXmoFCyhzonUUw4nE4CFu/sE3ffhjKcXcY//qiSW
27
+ xm4erY3XAgMBAAGjgZowgZcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0O
28
+ BBYEFO9t7XWuFf2SKLmuijgqR4sGDlRsMC4GA1UdEQQnMCWBI3NhbXVlbC53aWxs
29
+ aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MC4GA1UdEgQnMCWBI3NhbXVlbC53aWxs
30
+ aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MA0GCSqGSIb3DQEBCwUAA4IBgQB5sxkE
31
+ cBsSYwK6fYpM+hA5B5yZY2+L0Z+27jF1pWGgbhPH8/FjjBLVn+VFok3CDpRqwXCl
32
+ xCO40JEkKdznNy2avOMra6PFiQyOE74kCtv7P+Fdc+FhgqI5lMon6tt9rNeXmnW/
33
+ c1NaMRdxy999hmRGzUSFjozcCwxpy/LwabxtdXwXgSay4mQ32EDjqR1TixS1+smp
34
+ 8C/NCWgpIfzpHGJsjvmH2wAfKtTTqB9CVKLCWEnCHyCaRVuKkrKjqhYCdmMBqCws
35
+ JkxfQWC+jBVeG9ZtPhQgZpfhvh+6hMhraUYRQ6XGyvBqEUe+yo6DKIT3MtGE2+CP
36
+ eX9i9ZWBydWb8/rvmwmX2kkcBbX0hZS1rcR593hGc61JR6lvkGYQ2MYskBveyaxt
37
+ Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
38
+ voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
39
+ -----END CERTIFICATE-----
40
+ date: 2023-06-15 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'
84
+ description:
85
+ email:
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - lib/io/endpoint.rb
91
+ - lib/io/endpoint/address_endpoint.rb
92
+ - lib/io/endpoint/composite_endpoint.rb
93
+ - lib/io/endpoint/generic.rb
94
+ - lib/io/endpoint/host_endpoint.rb
95
+ - lib/io/endpoint/shared_endpoint.rb
96
+ - lib/io/endpoint/socket_endpoint.rb
97
+ - lib/io/endpoint/ssl_endpoint.rb
98
+ - lib/io/endpoint/unix_endpoint.rb
99
+ - lib/io/endpoint/version.rb
100
+ - lib/io/endpoint/wrapper.rb
101
+ - license.md
102
+ - readme.md
103
+ homepage: https://github.com/socketry/io-endpoint
104
+ licenses:
105
+ - MIT
106
+ metadata:
107
+ documentation_uri: https://socketry.github.io/io-endpoint
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '3.0'
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubygems_version: 3.4.7
124
+ signing_key:
125
+ specification_version: 4
126
+ summary: Provides a separation of concerns interface for IO endpoints.
127
+ test_files: []
metadata.gz.sig ADDED
Binary file