tcp-client 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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