-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Separate interfaces for bound and connected sockets.
- Loading branch information
Showing
7 changed files
with
217 additions
and
163 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
# frozen_string_literal: true | ||
|
||
# Released under the MIT License. | ||
# Copyright, 2023, by Samuel Williams. | ||
|
||
require_relative 'generic' | ||
require_relative 'composite_endpoint' | ||
require_relative 'address_endpoint' | ||
|
||
module IO::Endpoint | ||
class BoundEndpoint < Generic | ||
def self.bound(endpoint, backlog: Socket::SOMAXCONN, close_on_exec: false) | ||
sockets = endpoint.bind | ||
|
||
sockets.each do |server| | ||
# 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. | ||
begin | ||
server.listen(backlog) | ||
rescue Errno::EOPNOTSUPP | ||
# Ignore. | ||
end | ||
|
||
server.close_on_exec = close_on_exec | ||
end | ||
|
||
return self.new(endpoint, sockets, **endpoint.options) | ||
end | ||
|
||
def initialize(endpoint, sockets, **options) | ||
super(**options) | ||
|
||
@endpoint = endpoint | ||
@sockets = sockets | ||
end | ||
|
||
attr :endpoint | ||
attr :sockets | ||
|
||
# A endpoint for the local end of the bound socket. | ||
# @returns [CompositeEndpoint] A composite endpoint for the local end of the bound socket. | ||
def local_address_endpoint(**options) | ||
endpoints = @sockets.map do |socket| | ||
AddressEndpoint.new(socket.to_io.local_address, **options) | ||
end | ||
|
||
return CompositeEndpoint.new(endpoints) | ||
end | ||
|
||
# A endpoint for the remote end of the bound socket. | ||
# @returns [CompositeEndpoint] A composite endpoint for the remote end of the bound socket. | ||
def remote_address_endpoint(**options) | ||
endpoints = @sockets.map do |wrapper| | ||
AddressEndpoint.new(socket.to_io.remote_address, **options) | ||
end | ||
|
||
return CompositeEndpoint.new(endpoints) | ||
end | ||
|
||
def close | ||
@sockets.each(&:close) | ||
@sockets.clear | ||
end | ||
|
||
def to_s | ||
"\#<#{self.class} #{@sockets.size} bound sockets for #{@endpoint}>" | ||
end | ||
|
||
def bind(wrapper = Wrapper.default, &block) | ||
@sockets.each.map do |server| | ||
if block_given? | ||
wrapper.async do | ||
yield server | ||
end | ||
else | ||
server.dup | ||
end | ||
end | ||
end | ||
end | ||
|
||
class Generic | ||
def bound(**options) | ||
BoundEndpoint.bound(self, **options) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
# frozen_string_literal: true | ||
|
||
# Released under the MIT License. | ||
# Copyright, 2023, by Samuel Williams. | ||
|
||
require_relative 'generic' | ||
require_relative 'composite_endpoint' | ||
require_relative 'socket_endpoint' | ||
|
||
require 'openssl' | ||
|
||
module IO::Endpoint | ||
class ConnectedEndpoint < Generic | ||
def self.connected(endpoint, close_on_exec: false) | ||
socket = endpoint.connect | ||
|
||
socket.close_on_exec = close_on_exec | ||
|
||
return self.new(endpoint, socket, **endpoint.options) | ||
end | ||
|
||
def initialize(endpoint, socket, **options) | ||
super(**options) | ||
|
||
@endpoint = endpoint | ||
@socket = socket | ||
end | ||
|
||
attr :endpoint | ||
attr :socket | ||
|
||
# A endpoint for the local end of the bound socket. | ||
# @returns [AddressEndpoint] A endpoint for the local end of the connected socket. | ||
def local_address_endpoint(**options) | ||
AddressEndpoint.new(socket.to_io.local_address, **options) | ||
end | ||
|
||
# A endpoint for the remote end of the bound socket. | ||
# @returns [AddressEndpoint] A endpoint for the remote end of the connected socket. | ||
def remote_address_endpoint(**options) | ||
AddressEndpoint.new(socket.to_io.remote_address, **options) | ||
end | ||
|
||
def connect(wrapper = Wrapper.default, &block) | ||
if block_given? | ||
yield @socket | ||
else | ||
return @socket.dup | ||
end | ||
end | ||
|
||
def close | ||
if @socket | ||
@socket.close | ||
@socket = nil | ||
end | ||
end | ||
|
||
def to_s | ||
"\#<#{self.class} #{@socket} connected for #{@endpoint}>" | ||
end | ||
end | ||
|
||
class Generic | ||
def connected(**options) | ||
ConnectedEndpoint.connected(self, **options) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,136 +1,7 @@ | ||
# frozen_string_literal: true | ||
|
||
# Released under the MIT License. | ||
# Copyright, 2023, by Samuel Williams. | ||
# Copyright, 2024, by Samuel Williams. | ||
|
||
require_relative 'generic' | ||
require_relative 'composite_endpoint' | ||
require_relative 'socket_endpoint' | ||
|
||
require 'openssl' | ||
|
||
module IO::Endpoint | ||
# Pre-connect and pre-bind sockets so that it can be used between processes. | ||
class SharedEndpoint < Generic | ||
# Create a new `SharedEndpoint` by binding to the given endpoint. | ||
def self.bound(endpoint, backlog: Socket::SOMAXCONN, close_on_exec: false, **options) | ||
sockets = endpoint.bind(**options) | ||
|
||
sockets.each do |server| | ||
# 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. | ||
begin | ||
server.listen(backlog) | ||
rescue Errno::EOPNOTSUPP | ||
# Ignore. | ||
end | ||
|
||
server.close_on_exec = close_on_exec | ||
end | ||
|
||
return self.new(endpoint, sockets) | ||
end | ||
|
||
# Create a new `SharedEndpoint` by connecting to the given endpoint. | ||
def self.connected(endpoint, close_on_exec: false) | ||
socket = endpoint.connect | ||
|
||
socket.close_on_exec = close_on_exec | ||
|
||
return self.new(endpoint, [socket]) | ||
end | ||
|
||
def initialize(endpoint, sockets, **options) | ||
super(**options) | ||
|
||
raise TypeError, "sockets must be an Array" unless sockets.is_a?(Array) | ||
|
||
@endpoint = endpoint | ||
@sockets = sockets | ||
end | ||
|
||
attr :endpoint | ||
attr :sockets | ||
|
||
def local_address_endpoint(**options) | ||
endpoints = @sockets.map do |wrapper| | ||
AddressEndpoint.new(wrapper.to_io.local_address, **options) | ||
end | ||
|
||
return CompositeEndpoint.new(endpoints) | ||
end | ||
|
||
def remote_address_endpoint(**options) | ||
endpoints = @sockets.map do |wrapper| | ||
AddressEndpoint.new(wrapper.to_io.remote_address, **options) | ||
end | ||
|
||
return CompositeEndpoint.new(endpoints) | ||
end | ||
|
||
# Close all the internal sockets. | ||
def close | ||
@sockets.each(&:close) | ||
@sockets.clear | ||
end | ||
|
||
def each(&block) | ||
return to_enum unless block_given? | ||
|
||
@sockets.each do |socket| | ||
yield SocketEndpoint.new(socket.dup) | ||
end | ||
end | ||
|
||
def bind(wrapper = Wrapper.default, &block) | ||
@sockets.each.map do |server| | ||
server = server.dup | ||
|
||
if block_given? | ||
wrapper.async do | ||
begin | ||
yield server | ||
ensure | ||
server.close | ||
end | ||
end | ||
else | ||
server | ||
end | ||
end | ||
end | ||
|
||
def connect(wrapper = Wrapper.default, &block) | ||
@sockets.each do |socket| | ||
socket = socket.dup | ||
|
||
return socket unless block_given? | ||
|
||
begin | ||
return yield(socket) | ||
ensure | ||
socket.close | ||
end | ||
end | ||
end | ||
|
||
def accept(wrapper = Wrapper.default, &block) | ||
bind(wrapper) do |server| | ||
wrapper.accept(server, &block) | ||
end | ||
end | ||
|
||
def to_s | ||
"\#<#{self.class} #{@sockets.size} descriptors for #{@endpoint}>" | ||
end | ||
end | ||
|
||
class Generic | ||
def bound(**options) | ||
SharedEndpoint.bound(self, **options) | ||
end | ||
|
||
def connected(**options) | ||
SharedEndpoint.connected(self, **options) | ||
end | ||
end | ||
end | ||
require_relative 'bound_endpoint' | ||
require_relative 'connected_endpoint' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.