Class: TCPClient

Inherits:
Object
  • Object
show all
Defined in:
lib/tcp-client.rb,
lib/tcp-client/errors.rb,
lib/tcp-client/address.rb,
lib/tcp-client/version.rb,
lib/tcp-client/deadline.rb,
lib/tcp-client/ssl_socket.rb,
lib/tcp-client/tcp_socket.rb,
lib/tcp-client/configuration.rb,
lib/tcp-client/default_configuration.rb,
lib/tcp-client/mixin/io_with_deadline.rb

Overview

Client class to communicate with a server via TCP w/o SSL.

All connect/read/write actions can be monitored to ensure that all actions terminate before given time limit - or raise an exception.

Examples:

request to Google.com and limit network interactions to 1.5 seconds

# create a configuration to use at least TLS 1.2
cfg = TCPClient::Configuration.create(ssl_params: {min_version: :TLS1_2})

response =
  TCPClient.with_deadline(1.5, 'www.google.com:443', cfg) do |client|
    client.write("GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n") #=> 40
    client.readline("\r\n\r\n") #=> see response
  end
# response contains the returned message and header

Defined Under Namespace

Classes: Address, Configuration, ConnectTimeoutError, InvalidDeadLineError, NetworkError, NoBlockGivenError, NoOpenSSLError, NotAnExceptionError, NotConnectedError, ReadTimeoutError, TimeoutError, UnknownAttributeError, WriteTimeoutError

Constant Summary collapse

VERSION =

The current version number.

'0.14.0'

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Class Attribute Details

.default_configurationConfiguration (readonly)

The default configuration. This is used by default if no dedicated configuration was specified to open or #connect.

Returns:



16
17
18
# File 'lib/tcp-client/default_configuration.rb', line 16

def default_configuration
  @default_configuration
end

Instance Attribute Details

#:closed?(: closed?) ⇒ Boolean (readonly)

Returns whether the connection is closed.

Returns:

  • (Boolean)

    whether the connection is closed



118
# File 'lib/tcp-client.rb', line 118

def closed? = @socket.nil? || @socket.closed?

#addressAddress (readonly)

Returns the address used by this client instance.

Returns:

  • (Address)

    the address used by this client instance



107
108
109
# File 'lib/tcp-client.rb', line 107

def address
  @address
end

#configurationConfiguration (readonly)

Returns the configuration used by this client instance.

Returns:

  • (Configuration)

    the configuration used by this client instance



112
113
114
# File 'lib/tcp-client.rb', line 112

def configuration
  @configuration
end

Class Method Details

.configure(options = nil) {|cfg| ... } ⇒ Configuration

Configure the default_configuration which is used if no dedicated configuration was specified to open or #connect.

Examples:

TCPClient.configure do |cfg|
  cfg.buffered = false
  cfg.ssl_params = { min_version: :TLS1_2, max_version: :TLS1_3 }
end

Parameters:

Yield Parameters:

Returns:



34
35
36
# File 'lib/tcp-client/default_configuration.rb', line 34

def configure(options = nil, &block)
  @default_configuration = Configuration.create(options, &block)
end

.open(address, configuration = nil) {|client| ... } ⇒ Object .open(address, configuration = nil) ⇒ TCPClient

Creates a new instance which is connected to the server on the given address.

If no configuration is given, the default_configuration will be used.

If an optional block is given, then the block's result is returned and the connection will be closed when the block execution ends. This can be used to create an ad-hoc connection which is guaranteed to be closed.

If no block is given the connected client instance is returned. This can be used as a shorthand to create & connect a client.

Overloads:

  • .open(address, configuration = nil) {|client| ... } ⇒ Object

    Returns the block result.

    Yield Parameters:

    • client (TCPClient)

      the connected client

    Returns:

    • (Object)

      the block result

  • .open(address, configuration = nil) ⇒ TCPClient

    Returns the connected client.

    Returns:

Parameters:

See Also:



59
60
61
62
63
64
65
# File 'lib/tcp-client.rb', line 59

def self.open(address, configuration = nil)
  client = new
  client.connect(address, configuration)
  block_given? ? yield(client) : client
ensure
  client.close if block_given?
end

.with_deadline(timeout, address, configuration = nil) {|client| ... } ⇒ Object

Yields an instance which is connected to the server on the given address. It limits all #read and #write actions within the block to the given time.

It ensures to close the connection when the block execution ends and returns the block's result.

This can be used to create an ad-hoc connection which is guaranteed to be closed and which #read/#write call sequence should not last longer than the timeout seconds.

If no configuration is given, the default_configuration will be used.

Parameters:

Yield Parameters:

  • client (TCPClient)

    the connected client

Returns:

  • (Object)

    the block's result

See Also:



94
95
96
97
98
99
100
101
102
# File 'lib/tcp-client.rb', line 94

def self.with_deadline(timeout, address, configuration = nil)
  raise(NoBlockGivenError) unless block_given?
  client = new
  client.with_deadline(timeout) do
    yield(client.connect(address, configuration))
  end
ensure
  client&.close
end

Instance Method Details

#closeTCPClient

Close the current connection if connected.

Returns:



125
126
127
128
129
130
131
132
# File 'lib/tcp-client.rb', line 125

def close
  @socket&.close
  self
rescue *NETWORK_ERRORS
  self
ensure
  @socket = @deadline = nil
end

#closed?Boolean

Returns whether the connection is closed.

Returns:

  • (Boolean)

    whether the connection is closed



118
# File 'lib/tcp-client.rb', line 118

def closed? = @socket.nil? || @socket.closed?

#connect(address, configuration = nil, timeout: nil, exception: nil) ⇒ TCPClient

Establishes a new connection to a server on given address.

It accepts a connection-specific configuration or uses the default_configuration.

The optional timeout and exception parameters allow to override the connect_timeout and connect_timeout_error values.

Parameters:

  • address (Address, String, Addrinfo, Integer)

    the target address, see TCPClient::Address#initialize for valid formats

  • configuration (Configuration) (defaults to: nil)

    the Configuration to be used for this instance

  • timeout (Numeric) (defaults to: nil)

    maximum time in seconds to connect

  • exception (Class<Exception>) (defaults to: nil)

    exception class to be used when the connect timeout reached

Returns:

Raises:

See Also:



157
158
159
160
161
162
163
164
# File 'lib/tcp-client.rb', line 157

def connect(address, configuration = nil, timeout: nil, exception: nil)
  close if @socket
  @configuration = (configuration || Configuration.default).dup
  raise(NoOpenSSLError) if @configuration.ssl? && !defined?(SSLSocket)
  @address = Address.new(address)
  @socket = create_socket(timeout, exception)
  self
end

#flushTCPClient

Flushes all internal buffers (write all buffered data).

Returns:



171
172
173
174
# File 'lib/tcp-client.rb', line 171

def flush
  stem_errors { @socket&.flush }
  self
end

#read(nbytes = nil, timeout: nil, exception: nil) ⇒ String

Read the given nbytes or the next available buffer from server.

The optional timeout and exception parameters allow to override the read_timeout and read_timeout_error values of the used #configuration.

Parameters:

  • nbytes (Integer) (defaults to: nil)

    the number of bytes to read

  • timeout (Numeric) (defaults to: nil)

    maximum time in seconds to read

  • exception (Class<Exception>) (defaults to: nil)

    exception class to be used when the read timeout reached

Returns:

  • (String)

    the read buffer

Raises:

See Also:



193
194
195
196
197
198
199
200
201
# File 'lib/tcp-client.rb', line 193

def read(nbytes = nil, timeout: nil, exception: nil)
  raise(NotConnectedError) if closed?
  deadline = create_deadline(timeout, configuration.read_timeout)
  return stem_errors { @socket.read(nbytes) } unless deadline.valid?
  exception ||= configuration.read_timeout_error
  stem_errors(exception) do
    @socket.read_with_deadline(nbytes, deadline, exception)
  end
end

#readline(separator = $/, chomp: false, timeout: nil, exception: nil) ⇒ String

Reads the next line from server.

The standard record separator is used as separator.

The optional timeout and exception parameters allow to override the read_timeout and read_timeout_error values of the used #configuration.

Parameters:

  • separator (String) (defaults to: $/)

    the line separator to be used

  • timeout (Numeric) (defaults to: nil)

    maximum time in seconds to read

  • exception (Class<Exception>) (defaults to: nil)

    exception class to be used when the read timeout reached

Returns:

  • (String)

    the read line

Raises:

See Also:



222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/tcp-client.rb', line 222

def readline(separator = $/, chomp: false, timeout: nil, exception: nil)
  raise(NotConnectedError) if closed?
  deadline = create_deadline(timeout, configuration.read_timeout)
  deadline.valid? or
    return stem_errors { @socket.readline(separator, chomp: chomp) }
  exception ||= configuration.read_timeout_error
  line =
    stem_errors(exception) do
      @socket.read_to_with_deadline(separator, deadline, exception)
    end
  chomp ? line.chomp : line
end

#to_sString

Returns the currently used address as text.

Returns:

  • (String)

    the currently used address as text.

See Also:



240
# File 'lib/tcp-client.rb', line 240

def to_s = @address.to_s

#with_deadline(timeout) {|client| ... } ⇒ Object

Executes a block with a given overall time limit.

When you like to ensure that a complete #read/#write communication sequence with the server is finished before a given amount of time you use this method.

Examples:

ensure to send SMTP welcome message and receive a 4 byte answer

answer = client.with_deadline(2.5) do
  client.write('HELO')
  client.read(4)
end
# answer is EHLO when server speaks fluent SMPT

Parameters:

  • timeout (Numeric)

    maximum time in seconds for all #read and #write calls within the block

Yield Parameters:

Returns:

  • (Object)

    the block's result

Raises:



265
266
267
268
269
270
271
272
273
# File 'lib/tcp-client.rb', line 265

def with_deadline(timeout)
  previous_deadline = @deadline
  raise(NoBlockGivenError) unless block_given?
  @deadline = Deadline.new(timeout)
  raise(InvalidDeadLineError, timeout) unless @deadline.valid?
  yield(self)
ensure
  @deadline = previous_deadline
end

#write(*messages, timeout: nil, exception: nil) ⇒ Integer

Writes the given messages to the server.

The optional timeout and exception parameters allow to override the write_timeout and write_timeout_error values of the used #configuration.

Parameters:

  • messages (Array<String>)

    one or more messages to write

  • timeout (Numeric) (defaults to: nil)

    maximum time in seconds to write

  • exception (Class<Exception>) (defaults to: nil)

    exception class to be used when the write timeout reached

Returns:

  • (Integer)

    bytes written

Raises:

See Also:



293
294
295
296
297
298
299
300
301
302
303
# File 'lib/tcp-client.rb', line 293

def write(*messages, timeout: nil, exception: nil)
  raise(NotConnectedError) if closed?
  deadline = create_deadline(timeout, configuration.write_timeout)
  return stem_errors { @socket.write(*messages) } unless deadline.valid?
  exception ||= configuration.write_timeout_error
  stem_errors(exception) do
    messages.sum do |chunk|
      @socket.write_with_deadline(chunk.b, deadline, exception)
    end
  end
end