diff --git a/LICENSE b/LICENSE index b9dd1f4..4938430 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2017-2021, Mike Blumtritt. All rights reserved. +Copyright (c) 2017-2025, Mike Blumtritt. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -26,3 +26,4 @@ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/lib/tcp-client.rb b/lib/tcp-client.rb index 21e47c8..a2a4305 100644 --- a/lib/tcp-client.rb +++ b/lib/tcp-client.rb @@ -125,7 +125,7 @@ def closed? = @socket.nil? || @socket.closed? def close @socket&.close self - rescue *NETWORK_ERRORS + rescue *@@net_errors self ensure @socket = @deadline = nil @@ -338,26 +338,24 @@ def create_socket(timeout, exception) def stem_errors(except = nil) yield - rescue *NETWORK_ERRORS => e + rescue *@@net_errors => e raise unless @configuration.normalize_network_errors except && e.is_a?(except) ? raise : raise(NetworkError, e) end - NETWORK_ERRORS = - [ - Errno::EADDRNOTAVAIL, - Errno::ECONNABORTED, - Errno::ECONNREFUSED, - Errno::ECONNRESET, - Errno::EHOSTUNREACH, - Errno::EINVAL, - Errno::ENETUNREACH, - Errno::EPIPE, - IOError, - SocketError - ].tap do |errors| - errors << ::OpenSSL::SSL::SSLError if defined?(::OpenSSL::SSL::SSLError) - end - .freeze - private_constant(:NETWORK_ERRORS) + @@net_errors = [ + Errno::EADDRNOTAVAIL, + Errno::ECONNABORTED, + Errno::ECONNREFUSED, + Errno::EHOSTUNREACH, + Errno::ENETUNREACH, + Errno::ECONNRESET, + Errno::EINVAL, + Errno::EPIPE, + Errno::EPERM, + SocketError, + IOError + ] + @@net_errors << ::OpenSSL::SSL::SSLError if defined?(::OpenSSL::SSL::SSLError) + @@net_errors.freeze end diff --git a/lib/tcp-client/address.rb b/lib/tcp-client/address.rb index 24fc314..53e5eaa 100644 --- a/lib/tcp-client/address.rb +++ b/lib/tcp-client/address.rb @@ -100,10 +100,11 @@ def to_s = host.index(':') ? "[#{host}]:#{port}" : "#{host}:#{port}" # @return [Address] itself # def freeze - return super if frozen? - solve - @addrinfo.freeze - @host.freeze + unless frozen? + solve + @addrinfo.freeze + @host.freeze + end super end diff --git a/lib/tcp-client/configuration.rb b/lib/tcp-client/configuration.rb index 551146e..ea5a0bc 100644 --- a/lib/tcp-client/configuration.rb +++ b/lib/tcp-client/configuration.rb @@ -43,7 +43,6 @@ def self.create(options = nil) # def initialize(options = nil) @buffered = @keep_alive = @reverse_lookup = true - self.timeout = @ssl_params = nil @connect_timeout_error = ConnectTimeoutError @read_timeout_error = ReadTimeoutError @write_timeout_error = WriteTimeoutError @@ -107,7 +106,7 @@ def ssl? = @ssl_params ? true : false def ssl_params=(value) @ssl_params = if value.respond_to?(:to_hash) - Hash[value.to_hash] + value.to_hash.dup elsif value.respond_to?(:to_h) value.nil? ? nil : value.to_h.dup else diff --git a/lib/tcp-client/errors.rb b/lib/tcp-client/errors.rb index fe3346e..2b9f3b7 100644 --- a/lib/tcp-client/errors.rb +++ b/lib/tcp-client/errors.rb @@ -129,19 +129,4 @@ class WriteTimeoutError < TimeoutError # def action = :write end - - NoOpenSSL = NoOpenSSLError # @!visibility private - NoBlockGiven = NoBlockGivenError # @!visibility private - InvalidDeadLine = InvalidDeadLineError # @!visibility private - UnknownAttribute = UnknownAttributeError # @!visibility private - NotAnException = NotAnExceptionError # @!visibility private - NotConnected = NotConnectedError # @!visibility private - deprecate_constant( - :NoOpenSSL, - :NoBlockGiven, - :InvalidDeadLine, - :UnknownAttribute, - :NotAnException, - :NotConnected - ) end diff --git a/lib/tcp-client/ssl_socket.rb b/lib/tcp-client/ssl_socket.rb index aac266b..7abee2d 100644 --- a/lib/tcp-client/ssl_socket.rb +++ b/lib/tcp-client/ssl_socket.rb @@ -53,6 +53,8 @@ def should_verify?(ssl_params) CONTEXT_CACHE_MODE = ::OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT | ::OpenSSL::SSL::SSLContext::SESSION_CACHE_NO_INTERNAL_STORE + + private_constant(:CONTEXT_CACHE_MODE) end private_constant(:SSLSocket) diff --git a/lib/tcp-client/tcp_socket.rb b/lib/tcp-client/tcp_socket.rb index ab1f290..9c7ca17 100644 --- a/lib/tcp-client/tcp_socket.rb +++ b/lib/tcp-client/tcp_socket.rb @@ -9,18 +9,17 @@ class TCPSocket < ::Socket include WithDeadline def initialize(address, configuration, deadline) - super(address.addrinfo.ipv6? ? :INET6 : :INET, :STREAM) + addrinfo = address.addrinfo + super(addrinfo.ipv6? ? :INET6 : :INET, :STREAM) configure(configuration) - connect_to(as_addr_in(address), deadline) + connect_to( + ::Socket.pack_sockaddr_in(addrinfo.ip_port, addrinfo.ip_address), + deadline + ) end private - def as_addr_in(address) - addrinfo = address.addrinfo - ::Socket.pack_sockaddr_in(addrinfo.ip_port, addrinfo.ip_address) - end - def connect_to(addr, deadline) return connect(addr) unless deadline.valid? with_deadline(deadline) { connect_nonblock(addr, exception: false) } diff --git a/lib/tcp-client/version.rb b/lib/tcp-client/version.rb index 610e47d..190ea14 100644 --- a/lib/tcp-client/version.rb +++ b/lib/tcp-client/version.rb @@ -2,5 +2,5 @@ class TCPClient # The current version number. - VERSION = '1.0.1' + VERSION = '1.0.2' end diff --git a/lib/tcp-client/with_deadline.rb b/lib/tcp-client/with_deadline.rb index ca2e6ad..cc7ee00 100644 --- a/lib/tcp-client/with_deadline.rb +++ b/lib/tcp-client/with_deadline.rb @@ -6,8 +6,8 @@ def read_with_deadline(nbytes, deadline) deadline.remaining_time return fetch_avail(deadline) if nbytes.nil? @read_buffer ||= ''.b - while @read_buffer.bytesize < nbytes - read = fetch_next(deadline) + while (diff = nbytes - @read_buffer.bytesize) > 0 + read = fetch_next(deadline, diff) read ? @read_buffer << read : (break close) end fetch_slice(nbytes) @@ -57,8 +57,8 @@ def fetch_slice(size) result end - def fetch_next(deadline) - with_deadline(deadline) { read_nonblock(65_536, exception: false) } + def fetch_next(deadline, size = 65_536) + with_deadline(deadline) { read_nonblock(size, exception: false) } end def with_deadline(deadline) diff --git a/spec/lib/tcp-client_spec.rb b/spec/lib/tcp-client_spec.rb index 618799c..d15ca81 100644 --- a/spec/lib/tcp-client_spec.rb +++ b/spec/lib/tcp-client_spec.rb @@ -6,7 +6,7 @@ TCPClient::Configuration.create(buffered: false, reverse_lookup: false) end - describe 'a new instance' do + context 'with a new instance' do subject(:client) { TCPClient.new } it { is_expected.to be_closed } @@ -34,7 +34,7 @@ end end - describe 'a connected instance' do + context 'with a connected instance' do before { allow_any_instance_of(Socket).to receive(:connect) } it { is_expected.not_to be_closed } @@ -75,7 +75,7 @@ end end - describe 'an instance after #connect failed' do + context 'with an instance after #connect failed' do subject(:client) do TCPClient.new.tap do |instance| instance.connect('', configuration)