tcp-client 0.9.4 → 0.11.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: 114da301d59fc9cf9c3d3bda508f4d23b5b4991877a5dc7504d4da97114f8e7f
4
- data.tar.gz: 9d0815501de340b485cba157aff05097597948d023a7eca6c45a8ead3a4e3d0d
3
+ metadata.gz: 91bdfe4046f3e4baeefe4a140d7a2bdb0cac02d3c069102e9c574fe02e5ca1c8
4
+ data.tar.gz: e26cc5ce187d50aba43d06403730445f3605667a4cffc5d4c7105dd549ca4c9a
5
5
  SHA512:
6
- metadata.gz: a560644d578cedccaf1e2665f518b3a6a4e225886cfc561a8b62087420d93b958ee4741d8efde3b456fa15ce24fd5ee2f6a1121b55855205ffae5abf17becf97
7
- data.tar.gz: f1f0ed0b87f8ee9c9b376b6b567c45aecb204c5b3de044c97f6ba080071c3c8e70da8bca8cca2484d5ddec06df181ee4b007e9ed826045e205d77df38eefbbbd
6
+ metadata.gz: dfc647093949536fa3c040f9140ec5039a37934e1b77ccc3150f9c16a8de824123c7e8b374faa9479c5572a84f7cb9e08a051e356fe76e79d9245e10efd61bf4
7
+ data.tar.gz: 6046435c921660e284838c0e33559540f81de5f8b4c150ab63bf37ee72fc7605bc128d5c2b79d8ac7cb6cf657267bc445a4bef36103508ffaed4d6b0961d023c
data/README.md CHANGED
@@ -27,11 +27,14 @@ cfg = TCPClient::Configuration.create(
27
27
  # - limit all network interactions to 1.5 seconds
28
28
  # - use the Configuration cfg
29
29
  # - send a simple HTTP get request
30
- # - read 12 byte: "HTTP/1.1 " + 3 byte HTTP status code
31
- TCPClient.with_deadline(1.5, 'www.google.com:443', cfg) do |client|
32
- client.write("GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n") # >= 40
33
- client.read(12) # => "HTTP/1.1 200"
34
- end
30
+ # - read the returned message and headers
31
+ response =
32
+ TCPClient.with_deadline(1.5, 'www.google.com:443', cfg) do |client|
33
+ client.write("GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n") #=> 40
34
+ client.readline("\r\n\r\n") #=> see response
35
+ end
36
+
37
+ puts(response)
35
38
  ```
36
39
 
37
40
  For more samples see [the samples dir](https://github.com/mblumtritt/tcp-client/tree/main/sample)
@@ -58,7 +61,7 @@ To install the gem globally use:
58
61
  gem install tcp-client
59
62
  ```
60
63
 
61
- After that you need only a single line of code in your project to have all tools on board:
64
+ After that you need only a single line of code in your project to have on board:
62
65
 
63
66
  ```ruby
64
67
  require 'tcp-client'
@@ -41,7 +41,7 @@ class TCPClient
41
41
  # @param addrinfo [Addrinfo] containing the addressed host and port
42
42
  #
43
43
  # @overload initialize(port)
44
- # Adresses the port on the local machine.
44
+ # Addresses the port on the local machine.
45
45
  #
46
46
  # @example create an Address for localhost on port 80
47
47
  # Address.new(80)
@@ -26,45 +26,29 @@ class TCPClient
26
26
  # @example
27
27
  # config = TCPClient::Configuration.create(buffered: false)
28
28
  #
29
- # @param options [Hash<Symbol,Object>] see {#initialize} for details
29
+ # @param options [{Symbol => Object}] see {#initialize} for details
30
30
  #
31
31
  # @return [Configuration] the initialized configuration
32
32
  #
33
- def self.create(options = {})
33
+ def self.create(options = nil)
34
34
  configuration = new(options)
35
35
  yield(configuration) if block_given?
36
36
  configuration
37
37
  end
38
38
 
39
39
  #
40
- # Intializes the instance with given options.
40
+ # Initializes and optionally configures the instance with given options.
41
41
  #
42
- # @param options [Hash<Symbol,Object>]
43
- # @option options [Boolean] :buffered, see {#buffered}
44
- # @option options [Boolean] :keep_alive, see {#keep_alive}
45
- # @option options [Boolean] :reverse_lookup, see {#reverse_lookup}
46
- # @option options [Hash<Symbol, Object>] :ssl_params, see {#ssl_params}
47
- # @option options [Numeric] :connect_timeout, see {#connect_timeout}
48
- # @option options [Class<Exception>] :connect_timeout_error, see
49
- # {#connect_timeout_error}
50
- # @option options [Numeric] :read_timeout, see {#read_timeout}
51
- # @option options [Class<Exception>] :read_timeout_error, see
52
- # {#read_timeout_error}
53
- # @option options [Numeric] :write_timeout, see {#write_timeout}
54
- # @option options [Class<Exception>] :write_timeout_error, see
55
- # {#write_timeout_error}
56
- # @option options [Boolean] :normalize_network_errors, see
57
- # {#normalize_network_errors}
42
+ # @see #configure
58
43
  #
59
- #
60
- def initialize(options = {})
44
+ def initialize(options = nil)
61
45
  @buffered = @keep_alive = @reverse_lookup = true
62
46
  self.timeout = @ssl_params = nil
63
47
  @connect_timeout_error = ConnectTimeoutError
64
48
  @read_timeout_error = ReadTimeoutError
65
49
  @write_timeout_error = WriteTimeoutError
66
50
  @normalize_network_errors = false
67
- options.each_pair { |attribute, value| set(attribute, value) }
51
+ configure(options) if options
68
52
  end
69
53
 
70
54
  # @!group Instance Attributes Socket Level
@@ -72,8 +56,8 @@ class TCPClient
72
56
  #
73
57
  # Enables/disables use of Socket-level buffering
74
58
  #
75
- # @return [Boolean] wheter the connection is allowed to use internal buffers
76
- # (default) or not
59
+ # @return [Boolean] whether the connection is allowed to use internal
60
+ # buffers (default) or not
77
61
  #
78
62
  attr_reader :buffered
79
63
 
@@ -84,7 +68,7 @@ class TCPClient
84
68
  #
85
69
  # Enables/disables use of Socket-level keep alive handling.
86
70
  #
87
- # @return [Boolean] wheter the connection is allowed to use keep alive
71
+ # @return [Boolean] whether the connection is allowed to use keep alive
88
72
  # signals (default) or not
89
73
  #
90
74
  attr_reader :keep_alive
@@ -96,7 +80,7 @@ class TCPClient
96
80
  #
97
81
  # Enables/disables address lookup.
98
82
  #
99
- # @return [Boolean] wheter the connection is allowed to lookup the address
83
+ # @return [Boolean] whether the connection is allowed to lookup the address
100
84
  # (default) or not
101
85
  #
102
86
  attr_reader :reverse_lookup
@@ -107,7 +91,7 @@ class TCPClient
107
91
 
108
92
  #
109
93
  # @!parse attr_reader :ssl?
110
- # @return [Boolean] wheter SSL is configured, see {#ssl_params}
94
+ # @return [Boolean] whether SSL is configured, see {#ssl_params}
111
95
  #
112
96
  def ssl?
113
97
  @ssl_params ? true : false
@@ -117,7 +101,7 @@ class TCPClient
117
101
  # Parameters used to initialize a SSL context. SSL/TLS will only be used if
118
102
  # this attribute is not `nil`.
119
103
  #
120
- # @return [Hash<Symbol, Object>] SSL parameters for the SSL context
104
+ # @return [{Symbol => Object}] SSL parameters for the SSL context
121
105
  # @return [nil] if no SSL should be used (default)
122
106
  #
123
107
  attr_reader :ssl_params
@@ -226,7 +210,7 @@ class TCPClient
226
210
  # @attribute [w] timeout
227
211
  # Shorthand to set maximum time in seconds for all timeout monitoring.
228
212
  #
229
- # @return [Numeric] maximum time in seconds for any actwion
213
+ # @return [Numeric] maximum time in seconds for any action
230
214
  # @return [nil] if all timeout monitoring should be disabled (default)
231
215
  #
232
216
  # @see #connect_timeout
@@ -239,7 +223,7 @@ class TCPClient
239
223
 
240
224
  #
241
225
  # @attribute [w] timeout_error
242
- # Shorthand to set the exception class wich will by raised by any reached
226
+ # Shorthand to set the exception class which will by raised by any reached
243
227
  # timeout.
244
228
  #
245
229
  # @return [Class<Exception>] exception class raised
@@ -258,6 +242,8 @@ class TCPClient
258
242
 
259
243
  # @!endgroup
260
244
 
245
+ # @!group Other Instance Attributes
246
+
261
247
  #
262
248
  # Enables/disables if network exceptions should be raised as {NetworkError}.
263
249
  #
@@ -266,7 +252,7 @@ class TCPClient
266
252
  # manner. If this option is set to true all these error cases are raised as
267
253
  # {NetworkError} and can be easily captured.
268
254
  #
269
- # @return [Boolean] wheter all network exceptions should be raised as
255
+ # @return [Boolean] whether all network exceptions should be raised as
270
256
  # {NetworkError}, or not (default)
271
257
  #
272
258
  attr_reader :normalize_network_errors
@@ -275,12 +261,12 @@ class TCPClient
275
261
  @normalize_network_errors = value ? true : false
276
262
  end
277
263
 
264
+ # @!endgroup
265
+
278
266
  #
279
- # Convert `self` to a Hash containing all attributes.
280
- #
281
- # @return [Hash<Symbol, Object>]
267
+ # @return [{Symbol => Object}] Hash containing all attributes
282
268
  #
283
- # @see #initialize
269
+ # @see #configure
284
270
  #
285
271
  def to_h
286
272
  {
@@ -298,6 +284,33 @@ class TCPClient
298
284
  }
299
285
  end
300
286
 
287
+ #
288
+ # Configures the instance with given options Hash.
289
+ #
290
+ # @param options [{Symbol => Object}]
291
+ # @option options [Boolean] :buffered, see {#buffered}
292
+ # @option options [Boolean] :keep_alive, see {#keep_alive}
293
+ # @option options [Boolean] :reverse_lookup, see {#reverse_lookup}
294
+ # @option options [{Symbol => Object}] :ssl_params, see {#ssl_params}
295
+ # @option options [Numeric] :connect_timeout, see {#connect_timeout}
296
+ # @option options [Class<Exception>] :connect_timeout_error, see
297
+ # {#connect_timeout_error}
298
+ # @option options [Numeric] :read_timeout, see {#read_timeout}
299
+ # @option options [Class<Exception>] :read_timeout_error, see
300
+ # {#read_timeout_error}
301
+ # @option options [Numeric] :write_timeout, see {#write_timeout}
302
+ # @option options [Class<Exception>] :write_timeout_error, see
303
+ # {#write_timeout_error}
304
+ # @option options [Boolean] :normalize_network_errors, see
305
+ # {#normalize_network_errors}
306
+ #
307
+ # @return [Configuration] self
308
+ #
309
+ def configure(options)
310
+ options.each_pair { |attribute, value| set(attribute, value) }
311
+ self
312
+ end
313
+
301
314
  # @!visibility private
302
315
  def freeze
303
316
  @ssl_params.freeze
@@ -25,13 +25,13 @@ class TCPClient
25
25
  # cfg.ssl_params = { min_version: :TLS1_2, max_version: :TLS1_3 }
26
26
  # end
27
27
  #
28
- # @param options [Hash] see {Configuration#initialize} for details
28
+ # @param options [Hash] see {Configuration#configure} for details
29
29
  #
30
30
  # @yieldparam cfg {Configuration} the new configuration
31
31
  #
32
32
  # @return [Configuration] the new default configuration
33
33
  #
34
- def configure(options = {}, &block)
34
+ def configure(options = nil, &block)
35
35
  @default_configuration = Configuration.create(options, &block)
36
36
  end
37
37
  end
@@ -41,7 +41,7 @@ class TCPClient
41
41
  #
42
42
  # @!parse attr_reader :default
43
43
  # @return [Configuration] used by default if no dedicated configuration
44
- # was specified
44
+ # was specified
45
45
  #
46
46
  # @see TCPClient.open
47
47
  # @see TCPClient.with_deadline
@@ -2,7 +2,7 @@
2
2
 
3
3
  class TCPClient
4
4
  #
5
- # Raised when a SSL connection should be establshed but the OpenSSL gem is
5
+ # Raised when a SSL connection should be established but the OpenSSL gem is
6
6
  # not available.
7
7
  #
8
8
  class NoOpenSSLError < RuntimeError
@@ -38,7 +38,7 @@ class TCPClient
38
38
  #
39
39
  class UnknownAttributeError < ArgumentError
40
40
  #
41
- # @param attribute [Object] the undefined atttribute
41
+ # @param attribute [Object] the undefined attribute
42
42
  #
43
43
  def initialize(attribute)
44
44
  super("unknown attribute - #{attribute}")
@@ -1,57 +1,80 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # @!visibility private
4
- module IOWithDeadlineMixin # :nodoc:
5
- def self.included(mod)
6
- methods = mod.instance_methods
7
- if methods.index(:wait_writable) && methods.index(:wait_readable)
8
- mod.include(ViaWaitMethod)
9
- elsif methods.index(:to_io)
10
- mod.include(ViaIOWaitMethod)
11
- else
12
- mod.include(ViaSelect)
3
+ class TCPClient
4
+ module IOWithDeadlineMixin
5
+ def self.included(mod)
6
+ methods = mod.instance_methods
7
+ return if methods.index(:wait_writable) && methods.index(:wait_readable)
8
+ mod.include(methods.index(:to_io) ? WaitWithIO : WaitWithSelect)
13
9
  end
14
- end
15
10
 
16
- def read_with_deadline(bytes_to_read, deadline, exception)
17
- raise(exception) unless deadline.remaining_time
18
- if bytes_to_read.nil?
19
- return(
20
- with_deadline(deadline, exception) do
21
- read_nonblock(65_536, exception: false)
22
- end
23
- )
11
+ def read_with_deadline(nbytes, deadline, exception)
12
+ raise(exception) unless deadline.remaining_time
13
+ return fetch_avail(deadline, exception) if nbytes.nil?
14
+ return ''.b if nbytes.zero?
15
+ @read_buffer ||= ''.b
16
+ while @read_buffer.bytesize < nbytes
17
+ read = fetch_next(deadline, exception) and next @read_buffer << read
18
+ close
19
+ break
20
+ end
21
+ fetch_slice(nbytes)
24
22
  end
25
- result = ''.b
26
- while result.bytesize < bytes_to_read
27
- read =
28
- with_deadline(deadline, exception) do
29
- read_nonblock(bytes_to_read - result.bytesize, exception: false)
30
- end
31
- next result += read if read
32
- close
33
- break
23
+
24
+ def read_to_with_deadline(sep, deadline, exception)
25
+ raise(exception) unless deadline.remaining_time
26
+ @read_buffer ||= ''.b
27
+ while @read_buffer.index(sep).nil?
28
+ read = fetch_next(deadline, exception) and next @read_buffer << read
29
+ close
30
+ break
31
+ end
32
+ index = @read_buffer.index(sep)
33
+ return fetch_slice(index + sep.bytesize) if index
34
+ result = @read_buffer
35
+ @read_buffer = nil
36
+ result
34
37
  end
35
- result
36
- end
37
38
 
38
- def write_with_deadline(data, deadline, exception)
39
- raise(exception) unless deadline.remaining_time
40
- return 0 if (size = data.bytesize).zero?
41
- result = 0
42
- loop do
43
- written =
44
- with_deadline(deadline, exception) do
45
- write_nonblock(data, exception: false)
46
- end
47
- result += written
48
- return result if result >= size
49
- data = data.byteslice(written, data.bytesize - written)
39
+ def write_with_deadline(data, deadline, exception)
40
+ raise(exception) unless deadline.remaining_time
41
+ return 0 if (size = data.bytesize).zero?
42
+ result = 0
43
+ loop do
44
+ written =
45
+ with_deadline(deadline, exception) do
46
+ write_nonblock(data, exception: false)
47
+ end
48
+ (result += written) >= size and return result
49
+ data = data.byteslice(written, data.bytesize - written)
50
+ end
50
51
  end
51
- end
52
52
 
53
- module ViaWaitMethod
54
- private def with_deadline(deadline, exception)
53
+ private
54
+
55
+ def fetch_avail(deadline, exception)
56
+ if (result = @read_buffer || fetch_next(deadline, exception)).nil?
57
+ close
58
+ return ''.b
59
+ end
60
+ @read_buffer = nil
61
+ result
62
+ end
63
+
64
+ def fetch_slice(size)
65
+ result = @read_buffer.byteslice(0, size)
66
+ rest = @read_buffer.bytesize - result.bytesize
67
+ @read_buffer = rest.zero? ? nil : @read_buffer.byteslice(size, rest)
68
+ result
69
+ end
70
+
71
+ def fetch_next(deadline, exception)
72
+ with_deadline(deadline, exception) do
73
+ read_nonblock(65_536, exception: false)
74
+ end
75
+ end
76
+
77
+ def with_deadline(deadline, exception)
55
78
  loop do
56
79
  case ret = yield
57
80
  when :wait_writable
@@ -67,45 +90,29 @@ module IOWithDeadlineMixin # :nodoc:
67
90
  rescue Errno::ETIMEDOUT
68
91
  raise(exception)
69
92
  end
70
- end
71
93
 
72
- module ViaIOWaitMethod
73
- private def with_deadline(deadline, exception)
74
- loop do
75
- case ret = yield
76
- when :wait_writable
77
- remaining_time = deadline.remaining_time or raise(exception)
78
- raise(exception) if to_io.wait_writable(remaining_time).nil?
79
- when :wait_readable
80
- remaining_time = deadline.remaining_time or raise(exception)
81
- raise(exception) if to_io.wait_readable(remaining_time).nil?
82
- else
83
- return ret
84
- end
94
+ module WaitWithIO
95
+ def wait_writable(remaining_time)
96
+ to_io.wait_writable(remaining_time)
97
+ end
98
+
99
+ def wait_readable(remaining_time)
100
+ to_io.wait_readable(remaining_time)
85
101
  end
86
- rescue Errno::ETIMEDOUT
87
- raise(exception)
88
102
  end
89
- end
90
103
 
91
- module ViaSelect
92
- private def with_deadline(deadline, exception)
93
- loop do
94
- case ret = yield
95
- when :wait_writable
96
- remaining_time = deadline.remaining_time or raise(exception)
97
- raise(exception) if ::IO.select(nil, [self], nil, remaining_time).nil?
98
- when :wait_readable
99
- remaining_time = deadline.remaining_time or raise(exception)
100
- raise(exception) if ::IO.select([self], nil, nil, remaining_time).nil?
101
- else
102
- return ret
103
- end
104
+ module WaitWithSelect
105
+ def wait_writable(remaining_time)
106
+ ::IO.select(nil, [self], nil, remaining_time)
107
+ end
108
+
109
+ def wait_readable(remaining_time)
110
+ ::IO.select([self], nil, nil, remaining_time)
104
111
  end
105
- rescue Errno::ETIMEDOUT
106
- raise(exception)
107
112
  end
113
+
114
+ private_constant(:WaitWithIO, :WaitWithSelect)
108
115
  end
109
116
 
110
- private_constant(:ViaWaitMethod, :ViaIOWaitMethod, :ViaSelect)
117
+ private_constant(:IOWithDeadlineMixin)
111
118
  end
@@ -30,7 +30,7 @@ class TCPClient
30
30
  ::OpenSSL::SSL::SSLContext.new.tap do |ctx|
31
31
  ctx.set_params(ssl_params)
32
32
  ctx.session_cache_mode = CONTEXT_CACHE_MODE
33
- ctx.session_new_cb = proc { |_, sess| @new_session = sess }
33
+ ctx.session_new_cb = proc { |_, session| @new_session = session }
34
34
  end
35
35
  end
36
36
 
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class TCPClient
4
- VERSION = '0.9.4'
4
+ # The current version number.
5
+ VERSION = '0.11.0'
5
6
  end
data/lib/tcp-client.rb CHANGED
@@ -13,15 +13,18 @@ require_relative 'tcp-client/version'
13
13
  # Client class to communicate with a server via TCP w/o SSL.
14
14
  #
15
15
  # All connect/read/write actions can be monitored to ensure that all actions
16
- # terminate before given time limits - or raise an exception.
16
+ # terminate before given time limit - or raise an exception.
17
17
  #
18
18
  # @example request to Google.com and limit 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"
19
+ # # create a configuration to use at least TLS 1.2
20
+ # cfg = TCPClient::Configuration.create(ssl_params: {min_version: :TLS1_2})
24
21
  #
22
+ # response =
23
+ # TCPClient.with_deadline(1.5, 'www.google.com:443', cfg) do |client|
24
+ # client.write("GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n") #=> 40
25
+ # client.readline("\r\n\r\n") #=> see response
26
+ # end
27
+ # # response contains the returned message and header
25
28
  #
26
29
  class TCPClient
27
30
  #
@@ -40,10 +43,10 @@ class TCPClient
40
43
  #
41
44
  # If an optional block is given, then the block's result is returned and the
42
45
  # connection will be closed when the block execution ends.
43
- # This can be used to create an ad-hoc connection which is garanteed to be
46
+ # This can be used to create an ad-hoc connection which is guaranteed to be
44
47
  # closed.
45
48
  #
46
- # If no block is giiven the connected client instance is returned.
49
+ # If no block is given the connected client instance is returned.
47
50
  # This can be used as a shorthand to create & connect a client.
48
51
  #
49
52
  # @param address [Address, String, Addrinfo, Integer] the target address see
@@ -55,7 +58,7 @@ class TCPClient
55
58
  #
56
59
  def self.open(address, configuration = nil)
57
60
  client = new
58
- client.connect(Address.new(address), configuration)
61
+ client.connect(address, configuration)
59
62
  block_given? ? yield(client) : client
60
63
  ensure
61
64
  client.close if block_given?
@@ -67,9 +70,9 @@ class TCPClient
67
70
  # the given time.
68
71
  #
69
72
  # It ensures to close the connection when the block execution ends and returns
70
- # the block`s result.
73
+ # the block's result.
71
74
  #
72
- # This can be used to create an ad-hoc connection which is garanteed to be
75
+ # This can be used to create an ad-hoc connection which is guaranteed to be
73
76
  # closed and which {#read}/{#write} call sequence should not last longer than
74
77
  # the `timeout` seconds.
75
78
  #
@@ -91,7 +94,6 @@ class TCPClient
91
94
  def self.with_deadline(timeout, address, configuration = nil)
92
95
  client = nil
93
96
  raise(NoBlockGivenError) unless block_given?
94
- address = Address.new(address)
95
97
  client = new
96
98
  client.with_deadline(timeout) do
97
99
  yield(client.connect(address, configuration))
@@ -112,7 +114,7 @@ class TCPClient
112
114
 
113
115
  #
114
116
  # @!parse attr_reader :closed?
115
- # @return [Boolean] wheter the connection is closed
117
+ # @return [Boolean] whether the connection is closed
116
118
  #
117
119
  def closed?
118
120
  @socket.nil? || @socket.closed?
@@ -121,7 +123,7 @@ class TCPClient
121
123
  #
122
124
  # Close the current connection if connected.
123
125
  #
124
- # @return [self]
126
+ # @return [TCPClient] itself
125
127
  #
126
128
  def close
127
129
  @socket&.close
@@ -133,7 +135,7 @@ class TCPClient
133
135
  end
134
136
 
135
137
  #
136
- # Establishes a new connection to a given `address`.
138
+ # Establishes a new connection to a server on given `address`.
137
139
  #
138
140
  # It accepts a connection-specific `configuration` or uses the
139
141
  # {.default_configuration}.
@@ -141,7 +143,7 @@ class TCPClient
141
143
  # The optional `timeout` and `exception` parameters allow to override the
142
144
  # `connect_timeout` and `connect_timeout_error` values.
143
145
  #
144
- # @param address [Address, String, Addrinfo, Integer] the target address see
146
+ # @param address [Address, String, Addrinfo, Integer] the target address, see
145
147
  # {Address#initialize} for valid formats
146
148
  # @param configuration [Configuration] the {Configuration} to be used for
147
149
  # this instance
@@ -149,7 +151,7 @@ class TCPClient
149
151
  # @param exception [Class<Exception>] exception class to be used when the
150
152
  # connect timeout reached
151
153
  #
152
- # @return [self]
154
+ # @return [TCPClient] itself
153
155
  #
154
156
  # @raise {NoOpenSSLError} if SSL should be used but OpenSSL is not avail
155
157
  #
@@ -157,17 +159,17 @@ class TCPClient
157
159
  #
158
160
  def connect(address, configuration = nil, timeout: nil, exception: nil)
159
161
  close if @socket
160
- @address = Address.new(address)
161
162
  @configuration = (configuration || Configuration.default).dup
162
163
  raise(NoOpenSSLError) if @configuration.ssl? && !defined?(SSLSocket)
164
+ @address = stem_errors { Address.new(address) }
163
165
  @socket = create_socket(timeout, exception)
164
166
  self
165
167
  end
166
168
 
167
169
  #
168
- # Flushes all internal buffers (write all through).
170
+ # Flushes all internal buffers (write all buffered data).
169
171
  #
170
- # @return [self]
172
+ # @return [TCPClient] itself
171
173
  #
172
174
  def flush
173
175
  stem_errors { @socket&.flush }
@@ -201,6 +203,39 @@ class TCPClient
201
203
  end
202
204
  end
203
205
 
206
+ #
207
+ # Reads the next line from server.
208
+ #
209
+ # The standard record separator is used as `separator`.
210
+ #
211
+ # The optional `timeout` and `exception` parameters allow to override the
212
+ # `read_timeout` and `read_timeout_error` values of the used {#configuration}.
213
+ #
214
+ # @param separator [String] the line separator to be used
215
+ # @param timeout [Numeric] maximum time in seconds to read
216
+ # @param exception [Class<Exception>] exception class to be used when the
217
+ # read timeout reached
218
+ #
219
+ # @return [String] the read line
220
+ #
221
+ # @raise [NotConnectedError] if {#connect} was not called before
222
+ #
223
+ # @see NetworkError
224
+ #
225
+ def readline(separator = $/, chomp: false, timeout: nil, exception: nil)
226
+ raise(NotConnectedError) if closed?
227
+ deadline = create_deadline(timeout, configuration.read_timeout)
228
+ unless deadline.valid?
229
+ return stem_errors { @socket.readline(separator, chomp: chomp) }
230
+ end
231
+ exception ||= configuration.read_timeout_error
232
+ line =
233
+ stem_errors(exception) do
234
+ @socket.read_to_with_deadline(separator, deadline, exception)
235
+ end
236
+ chomp ? line.chomp : line
237
+ end
238
+
204
239
  #
205
240
  # @return [String] the currently used address as text.
206
241
  #
@@ -229,7 +264,7 @@ class TCPClient
229
264
  #
230
265
  # @yieldparam client [TCPClient] self
231
266
  #
232
- # @return [Object] the block`s result
267
+ # @return [Object] the block's result
233
268
  #
234
269
  # @raise [NoBlockGivenError] if the block is missing
235
270
  #
@@ -259,6 +294,8 @@ class TCPClient
259
294
  #
260
295
  # @raise [NotConnectedError] if {#connect} was not called before
261
296
  #
297
+ # @see NetworkError
298
+ #
262
299
  def write(*messages, timeout: nil, exception: nil)
263
300
  raise(NotConnectedError) if closed?
264
301
  deadline = create_deadline(timeout, configuration.write_timeout)
@@ -307,7 +344,8 @@ class TCPClient
307
344
  IOError,
308
345
  SocketError
309
346
  ].tap do |errors|
310
- errors << ::OpenSSL::SSL::SSLError if defined?(::OpenSSL::SSL::SSLError)
311
- end.freeze
347
+ errors << ::OpenSSL::SSL::SSLError if defined?(::OpenSSL::SSL::SSLError)
348
+ end
349
+ .freeze
312
350
  private_constant(:NETWORK_ERRORS)
313
351
  end
data/rakefile.rb CHANGED
@@ -6,7 +6,11 @@ require 'rspec/core/rake_task'
6
6
  require 'yard'
7
7
 
8
8
  $stdout.sync = $stderr.sync = true
9
- CLOBBER << 'prj' << 'doc' << '.yardoc'
9
+
10
+ CLEAN << '.yardoc'
11
+ CLOBBER << 'prj' << 'doc'
12
+
10
13
  task(:default) { exec('rake --tasks') }
14
+ task(test: :spec)
11
15
  RSpec::Core::RakeTask.new { |task| task.ruby_opts = %w[-w] }
12
16
  YARD::Rake::YardocTask.new { |task| task.stats_options = %w[--list-undoc] }
data/sample/google_ssl.rb CHANGED
@@ -18,8 +18,11 @@ cfg =
18
18
  # - limit all network interactions to 1.5 seconds
19
19
  # - use the Configuration cfg
20
20
  # - send a simple HTTP get request
21
- # - read 12 byte: "HTTP/1.1 " + 3 byte HTTP status code
22
- TCPClient.with_deadline(1.5, 'www.google.com:443', cfg) do |client|
23
- p client.write("GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n")
24
- p client.read(12)
25
- end
21
+ # - read the returned message and headers
22
+ response =
23
+ TCPClient.with_deadline(1.5, 'www.google.com:443', cfg) do |client|
24
+ client.write("GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n") #=> 40
25
+ client.readline("\r\n\r\n") #=> header, see response
26
+ end
27
+
28
+ puts(response)
@@ -60,20 +60,34 @@ RSpec.describe TCPClient::Configuration do
60
60
  end
61
61
  end
62
62
 
63
- context 'with valid options' do
64
- subject(:configuration) do
65
- TCPClient::Configuration.new(
66
- buffered: false,
67
- keep_alive: false,
68
- reverse_lookup: false,
69
- normalize_network_errors: true,
70
- ssl: true,
71
- timeout: 60,
72
- timeout_error: custom_error
73
- )
63
+ context 'when options are given' do
64
+ let(:options) { double(:options) }
65
+
66
+ it 'calls #configure with given options' do
67
+ expect_any_instance_of(TCPClient::Configuration).to receive(
68
+ :configure
69
+ ).once.with(options)
70
+
71
+ TCPClient::Configuration.new(options)
74
72
  end
75
- let(:custom_error) { Class.new(StandardError) }
73
+ end
74
+ end
75
+
76
+ describe '#configure' do
77
+ subject(:configuration) do
78
+ TCPClient::Configuration.new.configure(
79
+ buffered: false,
80
+ keep_alive: false,
81
+ reverse_lookup: false,
82
+ normalize_network_errors: true,
83
+ ssl: true,
84
+ timeout: 60,
85
+ timeout_error: custom_error
86
+ )
87
+ end
88
+ let(:custom_error) { Class.new(StandardError) }
76
89
 
90
+ context 'with valid options' do
77
91
  it 'allows to configure buffering' do
78
92
  expect(configuration.buffered).to be false
79
93
  end
@@ -107,52 +121,56 @@ RSpec.describe TCPClient::Configuration do
107
121
  end
108
122
 
109
123
  it 'allows to configure dedicated timeout values' do
110
- config =
111
- TCPClient::Configuration.new(
112
- connect_timeout: 21,
113
- read_timeout: 42,
114
- write_timeout: 84
115
- )
116
- expect(config.connect_timeout).to be 21
117
- expect(config.read_timeout).to be 42
118
- expect(config.write_timeout).to be 84
124
+ configuration.configure(
125
+ connect_timeout: 21,
126
+ read_timeout: 42,
127
+ write_timeout: 84
128
+ )
129
+ expect(configuration.connect_timeout).to be 21
130
+ expect(configuration.read_timeout).to be 42
131
+ expect(configuration.write_timeout).to be 84
119
132
  end
120
133
 
121
134
  it 'allows to configure dedicated timeout errors' do
122
135
  custom_connect = Class.new(StandardError)
123
136
  custom_read = Class.new(StandardError)
124
137
  custom_write = Class.new(StandardError)
125
- config =
126
- TCPClient::Configuration.new(
127
- connect_timeout_error: custom_connect,
128
- read_timeout_error: custom_read,
129
- write_timeout_error: custom_write
130
- )
131
- expect(config.connect_timeout_error).to be custom_connect
132
- expect(config.read_timeout_error).to be custom_read
133
- expect(config.write_timeout_error).to be custom_write
138
+ configuration.configure(
139
+ connect_timeout_error: custom_connect,
140
+ read_timeout_error: custom_read,
141
+ write_timeout_error: custom_write
142
+ )
143
+ expect(configuration.connect_timeout_error).to be custom_connect
144
+ expect(configuration.read_timeout_error).to be custom_read
145
+ expect(configuration.write_timeout_error).to be custom_write
134
146
  end
147
+ end
135
148
 
136
- it 'raises when no exception class is used to configure a timeout error' do
137
- expect do
138
- TCPClient::Configuration.new(
139
- connect_timeout_error: double(:something)
140
- )
141
- end.to raise_error(TCPClient::NotAnExceptionError)
149
+ context 'when an invalid attribute is given' do
150
+ it 'raises an error' do
151
+ expect { configuration.configure(invalid: :value) }.to raise_error(
152
+ TCPClient::UnknownAttributeError
153
+ )
154
+ end
155
+ end
156
+
157
+ context 'when no exception class is used to configure a timeout error' do
158
+ it 'raises with invalid connect_timeout_error' do
142
159
  expect do
143
- TCPClient::Configuration.new(read_timeout_error: double(:something))
160
+ configuration.configure(connect_timeout_error: double(:something))
144
161
  end.to raise_error(TCPClient::NotAnExceptionError)
162
+ end
163
+
164
+ it 'raises with invalid read_timeout_error' do
145
165
  expect do
146
- TCPClient::Configuration.new(write_timeout_error: double(:something))
166
+ configuration.configure(read_timeout_error: double(:something))
147
167
  end.to raise_error(TCPClient::NotAnExceptionError)
148
168
  end
149
- end
150
169
 
151
- context 'with invalid attribute' do
152
- it 'raises an error' do
153
- expect { TCPClient::Configuration.new(invalid: :value) }.to raise_error(
154
- TCPClient::UnknownAttributeError
155
- )
170
+ it 'raises with invalid write_timeout_error' do
171
+ expect do
172
+ configuration.configure(write_timeout_error: double(:something))
173
+ end.to raise_error(TCPClient::NotAnExceptionError)
156
174
  end
157
175
  end
158
176
  end
@@ -9,8 +9,9 @@ RSpec.describe 'TCPClient.configure' do
9
9
 
10
10
  context 'called with parameters' do
11
11
  it 'creates a new configuratiion' do
12
- expect(TCPClient::Configuration).to receive(:create).once.with(a: 1, b: 2)
13
- TCPClient.configure(a: 1, b: 2)
12
+ options = double(:options)
13
+ expect(TCPClient::Configuration).to receive(:create).once.with(options)
14
+ TCPClient.configure(options)
14
15
  end
15
16
 
16
17
  it 'returns the new configuratiion' do
@@ -26,7 +26,7 @@ RSpec.describe TCPClient do
26
26
  subject(:client) { TCPClient.new }
27
27
 
28
28
  it 'is closed' do
29
- expect(client.closed?).to be true
29
+ expect(client).to be_closed
30
30
  end
31
31
 
32
32
  it 'has no address' do
@@ -64,7 +64,7 @@ RSpec.describe TCPClient do
64
64
  before { allow_any_instance_of(::Socket).to receive(:connect) }
65
65
 
66
66
  it 'is not closed' do
67
- expect(client.closed?).to be false
67
+ expect(client).not_to be_closed
68
68
  end
69
69
 
70
70
  it 'has an address' do
@@ -115,7 +115,7 @@ RSpec.describe TCPClient do
115
115
  end
116
116
 
117
117
  it 'is closed' do
118
- expect(client.closed?).to be true
118
+ expect(client).to be_closed
119
119
  end
120
120
 
121
121
  it 'has an address' do
@@ -130,11 +130,11 @@ RSpec.describe TCPClient do
130
130
  expect(client.configuration).to be configuration
131
131
  end
132
132
 
133
- it 'failes when read is called' do
133
+ it 'fails when read is called' do
134
134
  expect { client.read(42) }.to raise_error(TCPClient::NotConnectedError)
135
135
  end
136
136
 
137
- it 'failes when write is called' do
137
+ it 'fails when write is called' do
138
138
  expect { client.write('?!') }.to raise_error(TCPClient::NotConnectedError)
139
139
  end
140
140
 
@@ -149,14 +149,10 @@ RSpec.describe TCPClient do
149
149
  end
150
150
  end
151
151
 
152
- xdescribe '.open' do
153
- end
154
-
155
- xdescribe '.with_deadline' do
156
- end
157
-
158
152
  context 'when not using SSL' do
159
153
  describe '#connect' do
154
+ subject(:client) { TCPClient.new }
155
+
160
156
  it 'configures the socket' do
161
157
  expect_any_instance_of(::Socket).to receive(:sync=).once.with(true)
162
158
  expect_any_instance_of(::Socket).to receive(:setsockopt)
@@ -169,7 +165,7 @@ RSpec.describe TCPClient do
169
165
  .once
170
166
  .with(false)
171
167
  expect_any_instance_of(::Socket).to receive(:connect)
172
- TCPClient.new.connect('localhost:1234', configuration)
168
+ client.connect('localhost:1234', configuration)
173
169
  end
174
170
 
175
171
  context 'when a timeout is specified' do
@@ -177,7 +173,26 @@ RSpec.describe TCPClient do
177
173
  expect_any_instance_of(::Socket).to receive(:connect_nonblock)
178
174
  .once
179
175
  .with(kind_of(String), exception: false)
180
- TCPClient.new.connect('localhost:1234', configuration, timeout: 10)
176
+ client.connect('localhost:1234', configuration, timeout: 10)
177
+ end
178
+
179
+ it 'is returns itself' do
180
+ allow_any_instance_of(::Socket).to receive(:connect_nonblock).with(
181
+ kind_of(String),
182
+ exception: false
183
+ )
184
+ result = client.connect('localhost:1234', configuration, timeout: 10)
185
+
186
+ expect(result).to be client
187
+ end
188
+
189
+ it 'is not closed' do
190
+ allow_any_instance_of(::Socket).to receive(:connect_nonblock).with(
191
+ kind_of(String),
192
+ exception: false
193
+ )
194
+ client.connect('localhost:1234', configuration, timeout: 10)
195
+ expect(client).not_to be_closed
181
196
  end
182
197
 
183
198
  context 'when the connection can not be established in time' do
@@ -188,25 +203,29 @@ RSpec.describe TCPClient do
188
203
 
189
204
  it 'raises an exception' do
190
205
  expect do
191
- TCPClient.new.connect(
192
- 'localhost:1234',
193
- configuration,
194
- timeout: 0.25
195
- )
206
+ client.connect('localhost:1234', configuration, timeout: 0.1)
196
207
  end.to raise_error(TCPClient::ConnectTimeoutError)
197
208
  end
198
209
 
199
210
  it 'allows to raise a custom exception' do
200
211
  exception = Class.new(StandardError)
201
212
  expect do
202
- TCPClient.new.connect(
213
+ client.connect(
203
214
  'localhost:1234',
204
215
  configuration,
205
- timeout: 0.25,
216
+ timeout: 0.1,
206
217
  exception: exception
207
218
  )
208
219
  end.to raise_error(exception)
209
220
  end
221
+
222
+ it 'is still closed' do
223
+ begin
224
+ client.connect('localhost:1234', configuration, timeout: 0.1)
225
+ rescue TCPClient::ConnectTimeoutError
226
+ end
227
+ expect(client).to be_closed
228
+ end
210
229
  end
211
230
  end
212
231
 
@@ -226,7 +245,7 @@ RSpec.describe TCPClient do
226
245
  end
227
246
 
228
247
  SOCKET_ERRORS.each do |error_class|
229
- it "raises a TCPClient::NetworkError when a #{error_class} appeared" do
248
+ it "raises TCPClient::NetworkError when a #{error_class} appeared" do
230
249
  allow_any_instance_of(::Socket).to receive(:connect) {
231
250
  raise error_class
232
251
  }
@@ -270,18 +289,61 @@ RSpec.describe TCPClient do
270
289
  expect(client.read(timeout: 10)).to be data
271
290
  end
272
291
 
292
+ context 'when socket closed before any data can be read' do
293
+ it 'returns empty buffer' do
294
+ expect_any_instance_of(::Socket).to receive(:read_nonblock)
295
+ .and_return(nil)
296
+ expect(client.read(timeout: 10)).to be_empty
297
+ end
298
+
299
+ it 'is closed' do
300
+ expect_any_instance_of(::Socket).to receive(:read_nonblock)
301
+ .and_return(nil)
302
+
303
+ client.read(timeout: 10)
304
+ expect(client).to be_closed
305
+ end
306
+ end
307
+
273
308
  context 'when data can not be fetched in a single chunk' do
274
309
  it 'reads chunk by chunk' do
275
310
  expect_any_instance_of(::Socket).to receive(:read_nonblock)
276
311
  .once
277
- .with(data_size * 2, exception: false)
312
+ .with(instance_of(Integer), exception: false)
278
313
  .and_return(data)
279
314
  expect_any_instance_of(::Socket).to receive(:read_nonblock)
280
315
  .once
281
- .with(data_size, exception: false)
316
+ .with(instance_of(Integer), exception: false)
282
317
  .and_return(data)
283
318
  expect(client.read(data_size * 2, timeout: 10)).to eq data * 2
284
319
  end
320
+
321
+ context 'when socket closed before enough data is avail' do
322
+ it 'returns available data only' do
323
+ expect_any_instance_of(::Socket).to receive(:read_nonblock)
324
+ .once
325
+ .with(instance_of(Integer), exception: false)
326
+ .and_return(data)
327
+ expect_any_instance_of(::Socket).to receive(:read_nonblock)
328
+ .once
329
+ .with(instance_of(Integer), exception: false)
330
+ .and_return(nil)
331
+ expect(client.read(data_size * 2, timeout: 10)).to eq data
332
+ end
333
+
334
+ it 'is closed' do
335
+ expect_any_instance_of(::Socket).to receive(:read_nonblock)
336
+ .once
337
+ .with(instance_of(Integer), exception: false)
338
+ .and_return(data)
339
+ expect_any_instance_of(::Socket).to receive(:read_nonblock)
340
+ .once
341
+ .with(instance_of(Integer), exception: false)
342
+ .and_return(nil)
343
+ client.read(data_size * 2, timeout: 10)
344
+ expect(client).to be_closed
345
+ end
346
+ end
285
347
  end
286
348
 
287
349
  context 'when the data can not be read in time' do
@@ -329,6 +391,146 @@ RSpec.describe TCPClient do
329
391
  end
330
392
  end
331
393
 
394
+ describe '#readline' do
395
+ before { allow_any_instance_of(::Socket).to receive(:connect) }
396
+
397
+ it 'reads from socket' do
398
+ expect_any_instance_of(::Socket).to receive(:readline)
399
+ .once
400
+ .with($/, chomp: false)
401
+ .and_return("Hello World\n")
402
+ expect(client.readline).to eq "Hello World\n"
403
+ end
404
+
405
+ context 'when a separator is specified' do
406
+ it 'forwards the separator' do
407
+ expect_any_instance_of(::Socket).to receive(:readline)
408
+ .once
409
+ .with('/', chomp: false)
410
+ .and_return('Hello/')
411
+ expect(client.readline('/')).to eq 'Hello/'
412
+ end
413
+ end
414
+
415
+ context 'when chomp is true' do
416
+ it 'forwards the flag' do
417
+ expect_any_instance_of(::Socket).to receive(:readline)
418
+ .once
419
+ .with($/, chomp: true)
420
+ .and_return('Hello World')
421
+ expect(client.readline(chomp: true)).to eq 'Hello World'
422
+ end
423
+ end
424
+
425
+ context 'when a timeout is specified' do
426
+ it 'checks the time' do
427
+ expect_any_instance_of(::Socket).to receive(:read_nonblock)
428
+ .and_return("Hello World\nHello World\n")
429
+ expect(client.readline(timeout: 10)).to eq "Hello World\n"
430
+ end
431
+
432
+ it 'optional chomps the line' do
433
+ expect_any_instance_of(::Socket).to receive(:read_nonblock)
434
+ .and_return("Hello World\nHello World\n")
435
+ expect(client.readline(chomp: true, timeout: 10)).to eq 'Hello World'
436
+ end
437
+
438
+ it 'uses the given separator' do
439
+ expect_any_instance_of(::Socket).to receive(:read_nonblock)
440
+ .and_return("Hello/World\n")
441
+ expect(client.readline('/', timeout: 10)).to eq 'Hello/'
442
+ end
443
+
444
+ context 'when data can not be fetched in a single chunk' do
445
+ it 'reads chunk by chunk' do
446
+ expect_any_instance_of(::Socket).to receive(:read_nonblock)
447
+ .once
448
+ .with(instance_of(Integer), exception: false)
449
+ .and_return('Hello ')
450
+ expect_any_instance_of(::Socket).to receive(:read_nonblock)
451
+ .once
452
+ .with(instance_of(Integer), exception: false)
453
+ .and_return('World')
454
+ expect_any_instance_of(::Socket).to receive(:read_nonblock)
455
+ .once
456
+ .with(instance_of(Integer), exception: false)
457
+ .and_return("\nAnd so...")
458
+ expect(client.readline(timeout: 10)).to eq "Hello World\n"
459
+ end
460
+
461
+ context 'when socket closed before enough data is avail' do
462
+ it 'returns available data only' do
463
+ expect_any_instance_of(::Socket).to receive(:read_nonblock)
464
+ .once
465
+ .with(instance_of(Integer), exception: false)
466
+ .and_return('Hello ')
467
+ expect_any_instance_of(::Socket).to receive(:read_nonblock)
468
+ .once
469
+ .with(instance_of(Integer), exception: false)
470
+ .and_return(nil)
471
+ expect(client.readline(timeout: 10)).to eq 'Hello '
472
+ end
473
+
474
+ it 'is closed' do
475
+ expect_any_instance_of(::Socket).to receive(:read_nonblock)
476
+ .once
477
+ .with(instance_of(Integer), exception: false)
478
+ .and_return('Hello ')
479
+ expect_any_instance_of(::Socket).to receive(:read_nonblock)
480
+ .once
481
+ .with(instance_of(Integer), exception: false)
482
+ .and_return(nil)
483
+ client.readline(timeout: 10)
484
+ expect(client).to be_closed
485
+ end
486
+ end
487
+ end
488
+
489
+ context 'when the data can not be read in time' do
490
+ before do
491
+ allow_any_instance_of(::Socket).to receive(:read_nonblock)
492
+ .and_return(:wait_readable)
493
+ end
494
+ it 'raises an exception' do
495
+ expect { client.readline(timeout: 0.25) }.to raise_error(
496
+ TCPClient::ReadTimeoutError
497
+ )
498
+ end
499
+
500
+ it 'allows to raise a custom exception' do
501
+ exception = Class.new(StandardError)
502
+ expect do
503
+ client.read(timeout: 0.25, exception: exception)
504
+ end.to raise_error(exception)
505
+ end
506
+ end
507
+ end
508
+
509
+ context 'when a SocketError appears' do
510
+ it 'does not handle it' do
511
+ allow_any_instance_of(::Socket).to receive(:read) {
512
+ raise SocketError
513
+ }
514
+ expect { client.read(10) }.to raise_error(SocketError)
515
+ end
516
+
517
+ context 'when normalize_network_errors is configured' do
518
+ let(:configuration) do
519
+ TCPClient::Configuration.create(normalize_network_errors: true)
520
+ end
521
+
522
+ SOCKET_ERRORS.each do |error_class|
523
+ it "raises a TCPClient::NetworkError when a #{error_class} appeared" do
524
+ allow_any_instance_of(::Socket).to receive(:read) {
525
+ raise error_class
526
+ }
527
+ expect { client.read(12) }.to raise_error(TCPClient::NetworkError)
528
+ end
529
+ end
530
+ end
531
+ end
532
+ end
533
+
332
534
  describe '#write' do
333
535
  let(:data) { 'some bytes' }
334
536
  let(:data_size) { data.bytesize }
@@ -463,20 +665,16 @@ RSpec.describe TCPClient do
463
665
  .with(kind_of(String), exception: false)
464
666
  expect_any_instance_of(::Socket).to receive(:read_nonblock)
465
667
  .once
466
- .with(12, exception: false)
467
- .and_return('123456789012')
668
+ .with(instance_of(Integer), exception: false)
669
+ .and_return('123456789012abcdefgAB')
468
670
  expect_any_instance_of(::Socket).to receive(:write_nonblock)
469
671
  .once
470
672
  .with('123456', exception: false)
471
673
  .and_return(6)
472
674
  expect_any_instance_of(::Socket).to receive(:read_nonblock)
473
675
  .once
474
- .with(7, exception: false)
475
- .and_return('abcdefg')
476
- expect_any_instance_of(::Socket).to receive(:read_nonblock)
477
- .once
478
- .with(7, exception: false)
479
- .and_return('ABCDEFG')
676
+ .with(instance_of(Integer), exception: false)
677
+ .and_return('CDEFG')
480
678
  expect_any_instance_of(::Socket).to receive(:write_nonblock)
481
679
  .once
482
680
  .with('abc', exception: false)
@@ -575,7 +773,7 @@ RSpec.describe TCPClient do
575
773
  # :set_params
576
774
  # )
577
775
  # .once
578
- # .with(ssl_version: :TLSv1_2)
776
+ # .with(max_version: :TLS1_3, min_version: :TLS1_2)
579
777
  # .and_call_original
580
778
  expect_any_instance_of(::OpenSSL::SSL::SSLSocket).to receive(
581
779
  :sync_close=
data/tcp-client.gemspec CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
9
9
 
10
10
  spec.author = 'Mike Blumtritt'
11
11
  spec.summary = 'A TCP client implementation with working timeout support.'
12
- spec.description = <<~description
12
+ spec.description = <<~DESCRIPTION
13
13
  This Gem implements a TCP client with (optional) SSL support.
14
14
  It is an easy to use, versatile configurable client that can correctly
15
15
  handle time limits.
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
17
17
  predefined/configurable time limits for each method
18
18
  (`connect`, `read`, `write`). Deadlines for a sequence of read/write
19
19
  actions can also be monitored.
20
- description
20
+ DESCRIPTION
21
21
 
22
22
  spec.homepage = 'https://github.com/mblumtritt/tcp-client'
23
23
  spec.license = 'BSD-3-Clause'
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.9.4
4
+ version: 0.11.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-12-16 00:00:00.000000000 Z
11
+ date: 2022-07-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -129,7 +129,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
129
129
  - !ruby/object:Gem::Version
130
130
  version: '0'
131
131
  requirements: []
132
- rubygems_version: 3.2.32
132
+ rubygems_version: 3.3.7
133
133
  signing_key:
134
134
  specification_version: 4
135
135
  summary: A TCP client implementation with working timeout support.