tcp-client 0.8.0 → 0.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 14f787ad4e8e06910bfebf67755ae7142183df4c4fb090edce84aae7d497a2b6
4
- data.tar.gz: 6349bea2eac6bac053c724e0c6a162b1fe99502e94cf1c6734854d97f194f37a
3
+ metadata.gz: 20df105b07989653ac9e9c877672318e1da4eb6a5171b088b137422ff03d22db
4
+ data.tar.gz: 393c2bf3f983f1da7dc8e3517b0cd7b170c2cdede3c546b603def84512e93806
5
5
  SHA512:
6
- metadata.gz: b53fafbf091a67a329832663c058b4e8243b36a779f8bc52cc7f37de556ad0aa016245e0a059489b972e6afaccfea7d26a63a735686d5f50d88036a2f4ecaca1
7
- data.tar.gz: 4a6ff368e221073c5addfab51e31df1b094ee3f69e09ebe3f38d6d552ebc761b14c552a1a2cd90dcfd148b1ba34ebb1b2e3abfcea9622ded8413870f21a60285
6
+ metadata.gz: c94f0a7c51cd5b33edf0de9fb02d454e539f95b79891ad81c81adb4832829059fc34b8c3d4e141594dd0b95752807f1c32dbc9c5192914a5ec02345bb056075c
7
+ data.tar.gz: 1ab3ef36b1534cf697c911c5d4ce01137dc9860598daa34765521cbec48fe6698294402cf0a0b768f2c460212cb3ce9378adb12ba7e65c76f1531e7bd66a1688
data/.gitignore CHANGED
@@ -1,4 +1,6 @@
1
- local/
2
1
  tmp/
3
2
  pkg/
3
+ local/
4
+ doc/
5
+ .yardoc/
4
6
  gems.locked
data/README.md CHANGED
@@ -30,7 +30,7 @@ TCPClient.with_deadline(1.5, 'www.google.com:443', cfg) do |client|
30
30
  end
31
31
  ```
32
32
 
33
- ### Installation
33
+ ## Installation
34
34
 
35
35
  Use [Bundler](http://gembundler.com/) to use TCPClient in your own project:
36
36
 
@@ -3,9 +3,44 @@
3
3
  require 'socket'
4
4
 
5
5
  class TCPClient
6
+ #
7
+ # The address used by a TCPClient
8
+ #
6
9
  class Address
7
- attr_reader :hostname, :addrinfo
10
+ #
11
+ # @return [String] the host name
12
+ #
13
+ attr_reader :hostname
8
14
 
15
+ #
16
+ # @return [Addrinfo] the address info
17
+ #
18
+ attr_reader :addrinfo
19
+
20
+ #
21
+ # Initializes an address
22
+ # @overload initialize(addr)
23
+ # The addr can be specified as
24
+ # - a valid named address containing the port like +my.host.test:80+
25
+ # - a valid TCPv4 address like +142.250.181.206:80+
26
+ # - a valid TCPv6 address like +[2001:16b8:5093:3500:ad77:abe6:eb88:47b6]:80+
27
+ #
28
+ # @param addr [String] address string
29
+ #
30
+ # @overload initialize(address)
31
+ # Used to create a copy
32
+ #
33
+ # @param address [Address]
34
+ #
35
+ # @overload initialize(addrinfo)
36
+ #
37
+ # @param addrinfo [Addrinfo] containing the addressed host and port
38
+ #
39
+ # @overload initialize(port)
40
+ # Adresses the port on the local machine.
41
+ #
42
+ # @param port [Integer] the addressed port
43
+ #
9
44
  def initialize(addr)
10
45
  case addr
11
46
  when self.class
@@ -20,24 +55,28 @@ class TCPClient
20
55
  @addrinfo.freeze
21
56
  end
22
57
 
58
+ #
59
+ # @return [String] text representation of self as "<host>:<port>"
60
+ #
23
61
  def to_s
24
62
  return "[#{@hostname}]:#{@addrinfo.ip_port}" if @hostname.index(':') # IP6
25
63
  "#{@hostname}:#{@addrinfo.ip_port}"
26
64
  end
27
65
 
28
- def to_hash
66
+ #
67
+ # @return [Hash] containing the host and port
68
+ #
69
+ def to_h
29
70
  { host: @hostname, port: @addrinfo.ip_port }
30
71
  end
31
72
 
32
- def to_h(*args)
33
- args.empty? ? to_hash : to_hash.slice(*args)
34
- end
35
-
73
+ # @!visibility private
36
74
  def ==(other)
37
- to_hash == other.to_hash
75
+ to_h == other.to_h
38
76
  end
39
77
  alias eql? ==
40
78
 
79
+ # @!visibility private
41
80
  def equal?(other)
42
81
  self.class == other.class && self == other
43
82
  end
@@ -3,25 +3,59 @@
3
3
  require_relative 'errors'
4
4
 
5
5
  class TCPClient
6
+ #
7
+ # A Configuration is used to configure the behavior of a {TCPClient} instance.
8
+ #
9
+ # It allows to specify to monitor timeout, how to handle exceptions, if SSL
10
+ # should be used and to setup the underlying Socket.
11
+ #
6
12
  class Configuration
13
+ #
14
+ # @overload create(options)
15
+ # Shorthand to create a new configuration with given options.
16
+ #
17
+ # @example
18
+ # config = TCPClient::Configuration.create(buffered: false)
19
+ #
20
+ # @param options [Hash] see {#initialize} for details
21
+ #
22
+ # @return [Configuration] the initialized configuration
23
+ #
24
+ # @overload create(&block)
25
+ # Shorthand to initialize a new configuration.
26
+ #
27
+ # @example
28
+ # config = TCPClient::Configuration.create do |cfg|
29
+ # cfg.buffered = false
30
+ # cfg.ssl_params = { min_version: :TLS1_2, max_version: :TLS1_3 }
31
+ # end
32
+ #
33
+ # @yieldparam cfg {Configuration}
34
+ #
35
+ # @return [Configuration] the initialized configuration
36
+ #
7
37
  def self.create(options = {})
8
- ret = new(options)
9
- yield(ret) if block_given?
10
- ret
11
- end
12
-
13
- attr_reader :buffered,
14
- :keep_alive,
15
- :reverse_lookup,
16
- :normalize_network_errors,
17
- :connect_timeout,
18
- :read_timeout,
19
- :write_timeout,
20
- :connect_timeout_error,
21
- :read_timeout_error,
22
- :write_timeout_error
23
- attr_accessor :ssl_params
38
+ cfg = new(options)
39
+ yield(cfg) if block_given?
40
+ cfg
41
+ end
24
42
 
43
+ #
44
+ # Intializes the instance with given options.
45
+ #
46
+ # @param options [Hash]
47
+ # @option options [Boolean] :buffered, see {#buffered}
48
+ # @option options [Boolean] :keep_alive, see {#keep_alive}
49
+ # @option options [Boolean] :reverse_lookup, see {#reverse_lookup}
50
+ # @option options [Boolean] :normalize_network_errors, see {#normalize_network_errors}
51
+ # @option options [Numeric] :connect_timeout, see {#connect_timeout}
52
+ # @option options [Exception] :connect_timeout_error, see {#connect_timeout_error}
53
+ # @option options [Numeric] :read_timeout, see {#read_timeout}
54
+ # @option options [Exception] :read_timeout_error, see {#read_timeout_error}
55
+ # @option options [Numeric] :write_timeout, see {#write_timeout}
56
+ # @option options [Exception] :write_timeout_error, see {#write_timeout_error}
57
+ # @option options [Hash<Symbol, Object>] :ssl_params, see {#ssl_params}
58
+ #
25
59
  def initialize(options = {})
26
60
  @buffered = @keep_alive = @reverse_lookup = true
27
61
  self.timeout = @ssl_params = nil
@@ -32,84 +66,188 @@ class TCPClient
32
66
  options.each_pair { |attribute, value| set(attribute, value) }
33
67
  end
34
68
 
35
- def freeze
36
- @ssl_params.freeze
37
- super
38
- end
39
-
40
- def initialize_copy(_org)
41
- super
42
- @ssl_params = Hash[@ssl_params] if @ssl_params
43
- self
44
- end
45
-
46
- def ssl?
47
- @ssl_params ? true : false
48
- end
49
-
50
- def ssl=(value)
51
- @ssl_params =
52
- if value.respond_to?(:to_hash)
53
- Hash[value.to_hash]
54
- else
55
- value ? {} : nil
56
- end
57
- end
69
+ #
70
+ # Enables/disables use of Socket-level buffers.
71
+ #
72
+ # @return [true] if the connection is allowed to use internal buffers (default)
73
+ # @return [false] if buffering is not allowed
74
+ #
75
+ attr_reader :buffered
58
76
 
59
77
  def buffered=(value)
60
78
  @buffered = value ? true : false
61
79
  end
62
80
 
81
+ #
82
+ # Enables/disables use of Socket-level keep alive handling.
83
+ #
84
+ # @return [true] if the connection is allowed to use keep alive signals (default)
85
+ # @return [false] if the connection should not check keep alive
86
+ #
87
+ attr_reader :keep_alive
88
+
63
89
  def keep_alive=(value)
64
90
  @keep_alive = value ? true : false
65
91
  end
66
92
 
93
+ #
94
+ # Enables/disables address lookup.
95
+ #
96
+ # @return [true] if the connection is allowed to lookup the address (default)
97
+ # @return [false] if the address lookup is not required
98
+ #
99
+ attr_reader :reverse_lookup
100
+
67
101
  def reverse_lookup=(value)
68
102
  @reverse_lookup = value ? true : false
69
103
  end
70
104
 
105
+ #
106
+ # Enables/disables if network exceptions should be raised as {NetworkError}.
107
+ #
108
+ # @return [true] if all network exceptions should be raised as {NetworkError}
109
+ # @return [false] if socket/system errors should not be normalzed (default)
110
+ #
111
+ attr_reader :normalize_network_errors
112
+
71
113
  def normalize_network_errors=(value)
72
114
  @normalize_network_errors = value ? true : false
73
115
  end
74
116
 
75
- def timeout=(seconds)
76
- @connect_timeout = @write_timeout = @read_timeout = seconds(seconds)
117
+ #
118
+ # @attribute [w] timeout
119
+ # Shorthand to set timeout value for connect, read and write at once or to disable any timeout monitoring
120
+ #
121
+ # @return [Numeric] maximum time in seconds for any action
122
+ # @return [nil] if all timeout monitoring should be disabled (default)
123
+ #
124
+ # @see #connect_timeout
125
+ # @see #read_timeout
126
+ # @see #write_timeout
127
+ #
128
+ def timeout=(value)
129
+ @connect_timeout = @write_timeout = @read_timeout = seconds(value)
130
+ end
131
+
132
+ #
133
+ # @attribute [w] timeout_error
134
+ # Shorthand to configure exception class raised when connect, read or write exceeded the configured timeout
135
+ #
136
+ # @return [Class] exception class raised
137
+ #
138
+ # @raise [NotAnExceptionError] if given argument is not an Exception class
139
+ #
140
+ # @see #connect_timeout_error
141
+ # @see #read_timeout_error
142
+ # @see #write_timeout_error
143
+ #
144
+ def timeout_error=(value)
145
+ raise(NotAnExceptionError, value) unless exception_class?(value)
146
+ @connect_timeout_error =
147
+ @read_timeout_error = @write_timeout_error = value
77
148
  end
78
149
 
79
- def connect_timeout=(seconds)
80
- @connect_timeout = seconds(seconds)
150
+ #
151
+ # Configures maximum time in seconds to establish a connection.
152
+ #
153
+ # @return [Numeric] maximum time in seconds to establish a connection
154
+ # @return [nil] if the connect time should not be checked (default)
155
+ #
156
+ attr_reader :connect_timeout
157
+
158
+ def connect_timeout=(value)
159
+ @connect_timeout = seconds(value)
81
160
  end
82
161
 
83
- def read_timeout=(seconds)
84
- @read_timeout = seconds(seconds)
162
+ #
163
+ # @return [Class] exception class raised if a {TCPClient#connect} timed out
164
+ # @raise [NotAnExceptionError] if given argument is not an Exception class
165
+ #
166
+ attr_reader :connect_timeout_error
167
+
168
+ def connect_timeout_error=(value)
169
+ raise(NotAnExceptionError, value) unless exception_class?(value)
170
+ @connect_timeout_error = value
85
171
  end
86
172
 
87
- def write_timeout=(seconds)
88
- @write_timeout = seconds(seconds)
173
+ #
174
+ # Configures maximum time in seconds to finish a {TCPClient#read}.
175
+ #
176
+ # @return [Numeric] maximum time in seconds to finish a {TCPClient#read} request
177
+ # @return [nil] if the read time should not be checked (default)
178
+ #
179
+ attr_reader :read_timeout
180
+
181
+ def read_timeout=(value)
182
+ @read_timeout = seconds(value)
89
183
  end
90
184
 
91
- def timeout_error=(exception)
92
- raise(NotAnExceptionError, exception) unless exception_class?(exception)
93
- @connect_timeout_error =
94
- @read_timeout_error = @write_timeout_error = exception
185
+ #
186
+ # @return [Class] exception class raised if a {TCPClient#read} timed out
187
+ # @raise [NotAnExceptionError] if given argument is not an Exception class
188
+ #
189
+ attr_reader :read_timeout_error
190
+
191
+ def read_timeout_error=(value)
192
+ raise(NotAnExceptionError, value) unless exception_class?(value)
193
+ @read_timeout_error = value
194
+ end
195
+
196
+ #
197
+ # Configures maximum time in seconds to finish a {TCPClient#write}.
198
+ #
199
+ # @return [Numeric] maximum time in seconds to finish a {TCPClient#write} request
200
+ # @return [nil] if the write time should not be checked (default)
201
+ #
202
+ attr_reader :write_timeout
203
+
204
+ def write_timeout=(value)
205
+ @write_timeout = seconds(value)
95
206
  end
96
207
 
97
- def connect_timeout_error=(exception)
98
- raise(NotAnExceptionError, exception) unless exception_class?(exception)
99
- @connect_timeout_error = exception
208
+ #
209
+ # @return [Class] exception class raised if a {TCPClient#write} timed out
210
+ # @raise [NotAnExceptionError] if given argument is not an Exception class
211
+ #
212
+ attr_reader :write_timeout_error
213
+
214
+ def write_timeout_error=(value)
215
+ raise(NotAnExceptionError, value) unless exception_class?(value)
216
+ @write_timeout_error = value
100
217
  end
101
218
 
102
- def read_timeout_error=(exception)
103
- raise(NotAnExceptionError, exception) unless exception_class?(exception)
104
- @read_timeout_error = exception
219
+ #
220
+ # @attribute ssl?
221
+ # @return [Boolean] wheter SSL is configured, see {#ssl_params}
222
+ #
223
+ def ssl?
224
+ @ssl_params ? true : false
105
225
  end
106
226
 
107
- def write_timeout_error=(exception)
108
- raise(NotAnExceptionError, exception) unless exception_class?(exception)
109
- @write_timeout_error = exception
227
+ #
228
+ # Configures the SSL parameters used to initialize a SSL context.
229
+ #
230
+ # @return [Hash<Symbol, Object>] SSL parameters for the SSL context
231
+ # @return [nil] if no SSL should be used (default)
232
+ #
233
+ attr_reader :ssl_params
234
+
235
+ def ssl_params=(value)
236
+ @ssl_params =
237
+ if value.respond_to?(:to_hash)
238
+ Hash[value.to_hash]
239
+ elsif value.respond_to?(:to_h)
240
+ Hash[value.to_h]
241
+ else
242
+ value ? {} : nil
243
+ end
110
244
  end
245
+ alias ssl= ssl_params=
111
246
 
112
- def to_hash
247
+ #
248
+ # @return [Hash] configuration as a Hash
249
+ #
250
+ def to_h
113
251
  {
114
252
  buffered: @buffered,
115
253
  keep_alive: @keep_alive,
@@ -124,15 +262,26 @@ class TCPClient
124
262
  }
125
263
  end
126
264
 
127
- def to_h(*args)
128
- args.empty? ? to_hash : to_hash.slice(*args)
265
+ # @!visibility private
266
+ def freeze
267
+ @ssl_params.freeze
268
+ super
269
+ end
270
+
271
+ # @!visibility private
272
+ def initialize_copy(_org)
273
+ super
274
+ @ssl_params = Hash[@ssl_params] if @ssl_params
275
+ self
129
276
  end
130
277
 
278
+ # @!visibility private
131
279
  def ==(other)
132
- to_hash == other.to_hash
280
+ to_h == other.to_h
133
281
  end
134
282
  alias eql? ==
135
283
 
284
+ # @!visibility private
136
285
  def equal?(other)
137
286
  self.class == other.class && self == other
138
287
  end
@@ -6,14 +6,36 @@ class TCPClient
6
6
  @default_configuration = Configuration.new
7
7
 
8
8
  class << self
9
+ #
10
+ # @return [Configuration] used by default if no dedicated configuration was specified
11
+ #
9
12
  attr_reader :default_configuration
10
13
 
14
+ #
15
+ # Configure the default configuration which is used if no dedicated
16
+ # configuration was specified.
17
+ #
18
+ # @example
19
+ # TCPClient.configure do |cfg|
20
+ # cfg.buffered = false
21
+ # cfg.ssl_params = { min_version: :TLS1_2, max_version: :TLS1_3 }
22
+ # end
23
+ #
24
+ # @param options [Hash] see {Configuration#initialize} for details
25
+ #
26
+ # @yieldparam cfg {Configuration} the new configuration
27
+ #
28
+ # @return [Configuration] the new default configuration
29
+ #
11
30
  def configure(options = {}, &block)
12
31
  @default_configuration = Configuration.create(options, &block)
13
32
  end
14
33
  end
15
34
 
16
35
  class Configuration
36
+ #
37
+ # @return [Configuration] used by default if no dedicated configuration was specified
38
+ #
17
39
  def self.default
18
40
  TCPClient.default_configuration
19
41
  end
@@ -1,78 +1,139 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class TCPClient
4
+ #
5
+ # Raised when a SSL connection should be establshed but the OpenSSL gem is not available.
6
+ #
4
7
  class NoOpenSSLError < RuntimeError
5
8
  def initialize
6
9
  super('OpenSSL is not available')
7
10
  end
8
11
  end
9
12
 
13
+ #
14
+ # Raised when a method requires a callback block but no such block is specified.
15
+ #
10
16
  class NoBlockGivenError < ArgumentError
11
17
  def initialize
12
18
  super('no block given')
13
19
  end
14
20
  end
15
21
 
22
+ #
23
+ # Raised when a an invalid timeout value was specified.
24
+ #
16
25
  class InvalidDeadLineError < ArgumentError
17
26
  def initialize(timeout)
18
27
  super("invalid deadline - #{timeout}")
19
28
  end
20
29
  end
21
30
 
31
+ #
32
+ # Raised by {Configuration} when an undefined attribute should be set.
33
+ #
22
34
  class UnknownAttributeError < ArgumentError
23
35
  def initialize(attribute)
24
36
  super("unknown attribute - #{attribute}")
25
37
  end
26
38
  end
27
39
 
40
+ #
41
+ # Raised when a given timeout exception parameter is not an exception class.
42
+ #
28
43
  class NotAnExceptionError < TypeError
29
44
  def initialize(object)
30
45
  super("exception class required - #{object.inspect}")
31
46
  end
32
47
  end
33
48
 
34
- NetworkError = Class.new(StandardError)
49
+ #
50
+ # Base exception class for all network related errors.
51
+ #
52
+ # Will be raised for any system level network error when {Configuration.normalize_network_errors} is configured.
53
+ #
54
+ # You should catch this exception class when you like to handle any relevant {TCPClient} error.
55
+ #
56
+ class NetworkError < StandardError
57
+ end
35
58
 
59
+ #
60
+ # Raised when a {TCPClient} instance should read/write from/to the network but is not connected.
61
+ #
36
62
  class NotConnectedError < NetworkError
37
63
  def initialize
38
64
  super('client not connected')
39
65
  end
40
66
  end
41
67
 
68
+ #
69
+ # Base exception class for a detected timeout.
70
+ #
71
+ # You should catch this exception class when you like to handle any timeout error.
72
+ #
42
73
  class TimeoutError < NetworkError
74
+ #
75
+ # Initializes the instance with an optional message.
76
+ #
77
+ # the message will be generated from {#action} when not specified.
78
+ # @overload initialize
79
+ # @overload initialize(message)
80
+ #
81
+ # @param message [String, #to_s] the error message
82
+ #
43
83
  def initialize(message = nil)
44
84
  super(message || "unable to #{action} in time")
45
85
  end
46
86
 
87
+ #
88
+ # @return [Symbol] the action which timed out
89
+ #
47
90
  def action
48
91
  :process
49
92
  end
50
93
  end
51
94
 
95
+ #
96
+ # Raised by default whenever a {TCPClient.connect} timed out.
97
+ #
52
98
  class ConnectTimeoutError < TimeoutError
99
+ #
100
+ # @return [Symbol] the action which timed out: +:connect+
101
+ #
53
102
  def action
54
103
  :connect
55
104
  end
56
105
  end
57
106
 
107
+ #
108
+ # Raised by default whenever a {TCPClient.read} timed out.
109
+ #
58
110
  class ReadTimeoutError < TimeoutError
111
+ #
112
+ # @return [Symbol] the action which timed out: +:read+
113
+ #
59
114
  def action
60
115
  :read
61
116
  end
62
117
  end
63
118
 
119
+ #
120
+ # Raised by default whenever a {TCPClient.write} timed out.
121
+ #
64
122
  class WriteTimeoutError < TimeoutError
123
+ #
124
+ # @return [Symbol] the action which timed out: +:write+
125
+ #
65
126
  def action
66
127
  :write
67
128
  end
68
129
  end
69
130
 
70
- NoOpenSSL = NoOpenSSLError
71
- NoBlockGiven = NoBlockGivenError
72
- InvalidDeadLine = InvalidDeadLineError
73
- UnknownAttribute = UnknownAttributeError
74
- NotAnException = NotAnExceptionError
75
- NotConnected = NotConnectedError
131
+ NoOpenSSL = NoOpenSSLError # @!visibility private
132
+ NoBlockGiven = NoBlockGivenError # @!visibility private
133
+ InvalidDeadLine = InvalidDeadLineError # @!visibility private
134
+ UnknownAttribute = UnknownAttributeError # @!visibility private
135
+ NotAnException = NotAnExceptionError # @!visibility private
136
+ NotConnected = NotConnectedError # @!visibility private
76
137
  deprecate_constant(
77
138
  :NoOpenSSL,
78
139
  :NoBlockGiven,
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module IOWithDeadlineMixin
3
+ # @!visibility private
4
+ module IOWithDeadlineMixin # :nodoc:
4
5
  def self.included(mod)
5
6
  methods = mod.instance_methods
6
7
  if methods.index(:wait_writable) && methods.index(:wait_readable)
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class TCPClient
4
- VERSION = '0.8.0'
4
+ #
5
+ # The current gem version.
6
+ #
7
+ VERSION = '0.9.0'
5
8
  end
data/lib/tcp-client.rb CHANGED
@@ -9,7 +9,43 @@ require_relative 'tcp-client/configuration'
9
9
  require_relative 'tcp-client/default_configuration'
10
10
  require_relative 'tcp-client/version'
11
11
 
12
+ #
13
+ # Client class to communicate with a server via TCP w/o SSL.
14
+ #
15
+ # All connect/read/write actions can be monitored to ensure that all actions
16
+ # terminate before given time limits - or raise an exception.
17
+ #
18
+ # @example - request to Google.com and limit all network interactions to 1.5 seconds
19
+ # TCPClient.with_deadline(1.5, 'www.google.com:443') do |client|
20
+ # client.write("GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n")
21
+ # client.read(12)
22
+ # end
23
+ # # => "HTTP/1.1 200"
24
+ #
25
+ #
12
26
  class TCPClient
27
+ #
28
+ # Creates a new instance which is connected to the server on the given
29
+ # address and uses the given or the {.default_configuration}.
30
+ #
31
+ # If an optional block is given, then the block's result is returned and the
32
+ # connection will be closed when the block execution ends.
33
+ # This can be used to create an ad-hoc connection which is garanteed to be
34
+ # closed.
35
+ #
36
+ # If no block is giiven the connected client instance is returned.
37
+ # This can be used as a shorthand to create & connect a client.
38
+ #
39
+ # @param address [Address, String, Addrinfo, Integer] the address to connect to, see {Address#initialize} for valid formats
40
+ # @param configuration [Configuration] the {Configuration} to be used for this instance
41
+ #
42
+ # @yieldparam client [TCPClient] the connected client
43
+ # @yieldreturn [Object] any result
44
+ #
45
+ # @return [Object, TCPClient] the block result or the connected client
46
+ #
47
+ # @see #connect
48
+ #
13
49
  def self.open(address, configuration = nil)
14
50
  client = new
15
51
  client.connect(Address.new(address), configuration)
@@ -18,6 +54,28 @@ class TCPClient
18
54
  client.close if block_given?
19
55
  end
20
56
 
57
+ #
58
+ # Yields a new instance which is connected to the server on the given
59
+ # address and uses the given or the {.default_configuration}.
60
+ # It ensures to close the connection when the block execution ends.
61
+ # It also limits all {#read} and {#write} actions within the block to a given
62
+ # time.
63
+ #
64
+ # This can be used to create an ad-hoc connection which is garanteed to be
65
+ # closed and which read/write calls should not last longer than the timeout
66
+ # limit.
67
+ #
68
+ # @param timeout [Numeric] maximum time in seconds for all {#read} and {#write} calls within the block
69
+ # @param address [Address, String, Addrinfo, Integer] the address to connect to, see {Address#initialize} for valid formats
70
+ # @param configuration [Configuration] the {Configuration} to be used for this instance
71
+ #
72
+ # @yieldparam client [TCPClient] the connected client
73
+ # @yieldreturn [Object] any result
74
+ #
75
+ # @return [Object] the block result
76
+ #
77
+ # @see #with_deadline
78
+ #
21
79
  def self.with_deadline(timeout, address, configuration = nil)
22
80
  client = nil
23
81
  raise(NoBlockGivenError) unless block_given?
@@ -30,12 +88,49 @@ class TCPClient
30
88
  client&.close
31
89
  end
32
90
 
33
- attr_reader :address, :configuration
91
+ #
92
+ # @return [Address] the address used for this client
93
+ #
94
+ attr_reader :address
34
95
 
96
+ #
97
+ # @return [Configuration] the configuration used by this client.
98
+ #
99
+ attr_reader :configuration
100
+
101
+ #
102
+ # @attribute [r] closed?
103
+ # @return [Boolean] true when the connection is closed, false when connected
104
+ #
105
+ def closed?
106
+ @socket.nil? || @socket.closed?
107
+ end
108
+
109
+ #
110
+ # @return [String] the currently used address as text.
111
+ #
112
+ # @see Address#to_s
113
+ #
35
114
  def to_s
36
115
  @address&.to_s || ''
37
116
  end
38
117
 
118
+ #
119
+ # Establishes a new connection to a given address.
120
+ #
121
+ # It accepts a connection-specific configuration or uses the global {.default_configuration}. The {#configuration} used by this instance will
122
+ # be a copy of the configuration used for this method call. This allows to
123
+ # configure the behavior per connection.
124
+ #
125
+ # @param address [Address, String, Addrinfo, Integer] the address to connect to, see {Address#initialize} for valid formats
126
+ # @param configuration [Configuration] the {Configuration} to be used for this instance
127
+ # @param timeout [Numeric] maximum time in seconds to read; used to override the configuration's +connect_timeout+.
128
+ # @param exception [Class] exception class to be used when the read timeout reached; used to override the configuration's +connect_timeout_error+.
129
+ #
130
+ # @return [self]
131
+ #
132
+ # @raise {NoOpenSSLError} if SSL should be used but OpenSSL is not avail
133
+ #
39
134
  def connect(address, configuration = nil, timeout: nil, exception: nil)
40
135
  close if @socket
41
136
  raise(NoOpenSSLError) if configuration.ssl? && !defined?(SSLSocket)
@@ -45,6 +140,11 @@ class TCPClient
45
140
  self
46
141
  end
47
142
 
143
+ #
144
+ # Close the current connection.
145
+ #
146
+ # @return [self]
147
+ #
48
148
  def close
49
149
  @socket&.close
50
150
  self
@@ -54,10 +154,27 @@ class TCPClient
54
154
  @socket = @deadline = nil
55
155
  end
56
156
 
57
- def closed?
58
- @socket.nil? || @socket.closed?
59
- end
60
-
157
+ #
158
+ # Executes a block with a given overall timeout.
159
+ #
160
+ # When you like to ensure that a complete read/write communication sequence
161
+ # with the server is finished before a given amount of time you can use this
162
+ # method to define such a deadline.
163
+ #
164
+ # @example - ensure to send a welcome message and receive a 64 byte answer from server
165
+ # answer = client.with_deadline(2.5) do
166
+ # client.write('Helo')
167
+ # client.read(64)
168
+ # end
169
+ #
170
+ # @param timeout [Numeric] maximum time in seconds for all {#read} and {#write} calls within the block
171
+ #
172
+ # @yieldparam client [TCPClient] self
173
+ #
174
+ # @return [Object] result of the given block
175
+ #
176
+ # @raise [NoBlockGivenError] if the block is missing
177
+ #
61
178
  def with_deadline(timeout)
62
179
  previous_deadline = @deadline
63
180
  raise(NoBlockGivenError) unless block_given?
@@ -68,6 +185,17 @@ class TCPClient
68
185
  @deadline = previous_deadline
69
186
  end
70
187
 
188
+ #
189
+ # Read the given nbytes or the next available buffer from server.
190
+ #
191
+ # @param nbytes [Integer] the number of bytes to read
192
+ # @param timeout [Numeric] maximum time in seconds to read; used to override the configuration's +read_timeout+.
193
+ # @param exception [Class] exception class to be used when the read timeout reached; used to override the configuration's +read_timeout_error+.
194
+ #
195
+ # @return [String] buffer read
196
+ #
197
+ # @raise [NotConnectedError] if {#connect} was not called before
198
+ #
71
199
  def read(nbytes = nil, timeout: nil, exception: nil)
72
200
  raise(NotConnectedError) if closed?
73
201
  deadline = create_deadline(timeout, configuration.read_timeout)
@@ -78,18 +206,34 @@ class TCPClient
78
206
  end
79
207
  end
80
208
 
81
- def write(*msg, timeout: nil, exception: nil)
209
+ #
210
+ # Write the given messages to the server.
211
+ #
212
+ # @param messages [Array<String>] messages to write
213
+ # @param timeout [Numeric] maximum time in seconds to read; used to override the configuration's +write_timeout+.
214
+ # @param exception [Class] exception class to be used when the read timeout reached; used to override the configuration's +write_timeout_error+.
215
+ #
216
+ # @return [Integer] bytes written
217
+ #
218
+ # @raise [NotConnectedError] if {#connect} was not called before
219
+ #
220
+ def write(*messages, timeout: nil, exception: nil)
82
221
  raise(NotConnectedError) if closed?
83
222
  deadline = create_deadline(timeout, configuration.write_timeout)
84
- return stem_errors { @socket.write(*msg) } unless deadline.valid?
223
+ return stem_errors { @socket.write(*messages) } unless deadline.valid?
85
224
  exception ||= configuration.write_timeout_error
86
225
  stem_errors(exception) do
87
- msg.sum do |chunk|
226
+ messages.sum do |chunk|
88
227
  @socket.write_with_deadline(chunk.b, deadline, exception)
89
228
  end
90
229
  end
91
230
  end
92
231
 
232
+ #
233
+ # Flush all internal buffers (write all through).
234
+ #
235
+ # @return [self]
236
+ #
93
237
  def flush
94
238
  stem_errors { @socket&.flush }
95
239
  self
data/rakefile.rb CHANGED
@@ -3,11 +3,11 @@
3
3
  require 'rake/clean'
4
4
  require 'bundler/gem_tasks'
5
5
  require 'rspec/core/rake_task'
6
+ require 'yard'
6
7
 
7
8
  $stdout.sync = $stderr.sync = true
8
9
 
9
- CLOBBER << 'prj'
10
-
10
+ CLOBBER << 'prj' << 'doc'
11
11
  task(:default) { exec('rake --tasks') }
12
-
13
12
  RSpec::Core::RakeTask.new { |task| task.ruby_opts = %w[-w] }
13
+ YARD::Rake::YardocTask.new { |task| task.stats_options = %w[--list-undoc] }
@@ -91,25 +91,12 @@ RSpec.describe TCPClient::Address do
91
91
  end
92
92
  end
93
93
 
94
- describe '#to_hash' do
95
- subject(:address) { TCPClient::Address.new('localhost:42') }
96
-
97
- it 'returns itself as an Hash' do
98
- expect(address.to_hash).to eq(host: 'localhost', port: 42)
99
- end
100
- end
101
-
102
94
  describe '#to_h' do
103
95
  subject(:address) { TCPClient::Address.new('localhost:42') }
104
96
 
105
97
  it 'returns itself as an Hash' do
106
98
  expect(address.to_h).to eq(host: 'localhost', port: 42)
107
99
  end
108
-
109
- it 'allows to specify the keys the result should contain' do
110
- expect(address.to_h(:port)).to eq(port: 42)
111
- expect(address.to_h(:host)).to eq(host: 'localhost')
112
- end
113
100
  end
114
101
 
115
102
  describe 'comparison' do
@@ -157,39 +157,6 @@ RSpec.describe TCPClient::Configuration do
157
157
  end
158
158
  end
159
159
 
160
- describe '#to_hash' do
161
- subject(:configuration) do
162
- TCPClient::Configuration.new(
163
- buffered: false,
164
- connect_timeout: 1,
165
- read_timeout: 2,
166
- write_timeout: 3,
167
- ssl: {
168
- min_version: :TLS1_2,
169
- max_version: :TLS1_3
170
- }
171
- )
172
- end
173
-
174
- it 'returns itself as an Hash' do
175
- expect(configuration.to_hash).to eq(
176
- buffered: false,
177
- keep_alive: true,
178
- reverse_lookup: true,
179
- connect_timeout: 1,
180
- connect_timeout_error: TCPClient::ConnectTimeoutError,
181
- read_timeout: 2,
182
- read_timeout_error: TCPClient::ReadTimeoutError,
183
- write_timeout: 3,
184
- write_timeout_error: TCPClient::WriteTimeoutError,
185
- ssl_params: {
186
- min_version: :TLS1_2,
187
- max_version: :TLS1_3
188
- }
189
- )
190
- end
191
- end
192
-
193
160
  describe '#to_h' do
194
161
  subject(:configuration) do
195
162
  TCPClient::Configuration.new(
@@ -221,12 +188,6 @@ RSpec.describe TCPClient::Configuration do
221
188
  }
222
189
  )
223
190
  end
224
-
225
- it 'allows to specify the keys the result should contain' do
226
- expect(
227
- configuration.to_h(:keep_alive, :read_timeout, :write_timeout)
228
- ).to eq(keep_alive: true, read_timeout: 2, write_timeout: 3)
229
- end
230
191
  end
231
192
 
232
193
  describe '#dup' do
data/tcp-client.gemspec CHANGED
@@ -29,6 +29,7 @@ Gem::Specification.new do |spec|
29
29
  spec.add_development_dependency 'bundler'
30
30
  spec.add_development_dependency 'rake'
31
31
  spec.add_development_dependency 'rspec'
32
+ spec.add_development_dependency 'yard'
32
33
 
33
34
  all_files = Dir.chdir(__dir__) { `git ls-files -z`.split(0.chr) }
34
35
  spec.test_files = all_files.grep(%r{^spec/})
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tcp-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Blumtritt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-11-12 00:00:00.000000000 Z
11
+ date: 2021-12-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: yard
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  description: |
56
70
  This Gem implements a TCP client with (optional) SSL support.
57
71
  It is an easy to use, versatile configurable client that can correctly