tcp-client 0.9.3 → 0.10.1

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.
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
  #
@@ -30,57 +33,59 @@ class TCPClient
30
33
  #
31
34
  # If no `configuration` is given, the {.default_configuration} will be used.
32
35
  #
36
+ # @overload open(address, configuration = nil)
37
+ # @yieldparam client [TCPClient] the connected client
38
+ #
39
+ # @return [Object] the block result
40
+ #
41
+ # @overload open(address, configuration = nil)
42
+ # @return [TCPClient] the connected client
43
+ #
33
44
  # If an optional block is given, then the block's result is returned and the
34
45
  # connection will be closed when the block execution ends.
35
- # 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
36
47
  # closed.
37
48
  #
38
- # If no block is giiven the connected client instance is returned.
49
+ # If no block is given the connected client instance is returned.
39
50
  # This can be used as a shorthand to create & connect a client.
40
51
  #
41
- # @param address [Address, String, Addrinfo, Integer] the address to connect
42
- # to, see {Address#initialize} for valid formats
52
+ # @param address [Address, String, Addrinfo, Integer] the target address see
53
+ # {Address#initialize} for valid formats
43
54
  # @param configuration [Configuration] the {Configuration} to be used for
44
- # this instance
45
- #
46
- # @yieldparam client [TCPClient] the connected client
47
- # @yieldreturn [Object] any result
48
- #
49
- # @return [Object, TCPClient] the block result or the connected client
55
+ # the new instance
50
56
  #
51
57
  # @see #connect
52
58
  #
53
59
  def self.open(address, configuration = nil)
54
60
  client = new
55
- client.connect(Address.new(address), configuration)
61
+ client.connect(address, configuration)
56
62
  block_given? ? yield(client) : client
57
63
  ensure
58
64
  client.close if block_given?
59
65
  end
60
66
 
61
67
  #
62
- # Yields a new instance which is connected to the server on the given
63
- # `address`.It limits all {#read} and {#write} actions within the block to
68
+ # Yields an instance which is connected to the server on the given
69
+ # `address`. It limits all {#read} and {#write} actions within the block to
64
70
  # the given time.
65
71
  #
66
72
  # It ensures to close the connection when the block execution ends and returns
67
- # the block`s result.
73
+ # the block's result.
68
74
  #
69
- # 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
70
76
  # closed and which {#read}/{#write} call sequence should not last longer than
71
- # the `timeout`.
77
+ # the `timeout` seconds.
72
78
  #
73
79
  # If no `configuration` is given, the {.default_configuration} will be used.
74
80
  #
75
81
  # @param timeout [Numeric] maximum time in seconds for all {#read} and
76
82
  # {#write} calls within the block
77
- # @param address [Address, String, Addrinfo, Integer] the address to connect
78
- # to, see {Address#initialize} for valid formats
83
+ # @param address [Address, String, Addrinfo, Integer] the target address see
84
+ # {Address#initialize} for valid formats
79
85
  # @param configuration [Configuration] the {Configuration} to be used for
80
- # this instance
86
+ # the instance
81
87
  #
82
88
  # @yieldparam client [TCPClient] the connected client
83
- # @yieldreturn [Object] any result
84
89
  #
85
90
  # @return [Object] the block's result
86
91
  #
@@ -89,7 +94,6 @@ class TCPClient
89
94
  def self.with_deadline(timeout, address, configuration = nil)
90
95
  client = nil
91
96
  raise(NoBlockGivenError) unless block_given?
92
- address = Address.new(address)
93
97
  client = new
94
98
  client.with_deadline(timeout) do
95
99
  yield(client.connect(address, configuration))
@@ -110,7 +114,7 @@ class TCPClient
110
114
 
111
115
  #
112
116
  # @!parse attr_reader :closed?
113
- # @return [Boolean] true when the connection is closed, false when connected
117
+ # @return [Boolean] whether the connection is closed
114
118
  #
115
119
  def closed?
116
120
  @socket.nil? || @socket.closed?
@@ -119,7 +123,7 @@ class TCPClient
119
123
  #
120
124
  # Close the current connection if connected.
121
125
  #
122
- # @return [self]
126
+ # @return [TCPClient] itself
123
127
  #
124
128
  def close
125
129
  @socket&.close
@@ -131,25 +135,23 @@ class TCPClient
131
135
  end
132
136
 
133
137
  #
134
- # Establishes a new connection to a given `address`.
138
+ # Establishes a new connection to a server on given `address`.
135
139
  #
136
- # It accepts a connection-specific configuration or uses the
137
- # {.default_configuration}. The {#configuration} used by this instance will
138
- # be a copy of the configuration used for this method call. This allows to
139
- # configure the behavior per connection.
140
+ # It accepts a connection-specific `configuration` or uses the
141
+ # {.default_configuration}.
140
142
  #
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 address to connect
145
- # to, see {Address#initialize} for valid formats
146
+ # @param address [Address, String, Addrinfo, Integer] the target address, see
147
+ # {Address#initialize} for valid formats
146
148
  # @param configuration [Configuration] the {Configuration} to be used for
147
149
  # this instance
148
150
  # @param timeout [Numeric] maximum time in seconds to connect
149
- # @param exception [Class] exception class to be used when the connect timeout
150
- # reached
151
+ # @param exception [Class<Exception>] exception class to be used when the
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
- # Flush 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 }
@@ -182,8 +184,8 @@ class TCPClient
182
184
  #
183
185
  # @param nbytes [Integer] the number of bytes to read
184
186
  # @param timeout [Numeric] maximum time in seconds to read
185
- # @param exception [Class] exception class to be used when the read timeout
186
- # reached
187
+ # @param exception [Class<Exception>] exception class to be used when the
188
+ # read timeout reached
187
189
  #
188
190
  # @return [String] the read buffer
189
191
  #
@@ -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.readto_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
  #
@@ -211,7 +246,7 @@ class TCPClient
211
246
  end
212
247
 
213
248
  #
214
- # Execute a block with a given overall time limit.
249
+ # Executes a block with a given overall time limit.
215
250
  #
216
251
  # When you like to ensure that a complete {#read}/{#write} communication
217
252
  # sequence with the server is finished before a given amount of time you use
@@ -228,9 +263,8 @@ class TCPClient
228
263
  # {#write} calls within the block
229
264
  #
230
265
  # @yieldparam client [TCPClient] self
231
- # @yieldreturn [Object] any result
232
266
  #
233
- # @return [Object] the block`s result
267
+ # @return [Object] the block's result
234
268
  #
235
269
  # @raise [NoBlockGivenError] if the block is missing
236
270
  #
@@ -245,21 +279,23 @@ class TCPClient
245
279
  end
246
280
 
247
281
  #
248
- # Write the given `messages` to the server.
282
+ # Writes the given `messages` to the server.
249
283
  #
250
284
  # The optional `timeout` and `exception` parameters allow to override the
251
285
  # `write_timeout` and `write_timeout_error` values of the used
252
286
  # {#configuration}.
253
287
  #
254
- # @param messages [String] one or more messages to write
288
+ # @param messages [Array<String>] one or more messages to write
255
289
  # @param timeout [Numeric] maximum time in seconds to write
256
- # @param exception [Class] exception class to be used when the write timeout
257
- # reached
290
+ # @param exception [Class<Exception>] exception class to be used when the
291
+ # write timeout reached
258
292
  #
259
293
  # @return [Integer] bytes written
260
294
  #
261
295
  # @raise [NotConnectedError] if {#connect} was not called before
262
296
  #
297
+ # @see NetworkError
298
+ #
263
299
  def write(*messages, timeout: nil, exception: nil)
264
300
  raise(NotConnectedError) if closed?
265
301
  deadline = create_deadline(timeout, configuration.write_timeout)
data/rakefile.rb CHANGED
@@ -6,7 +6,13 @@ 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 << 'prj' << 'doc'
11
+
12
+ CLOBBER << '.yardoc'
13
+
10
14
  task(:default) { exec('rake --tasks') }
15
+
11
16
  RSpec::Core::RakeTask.new { |task| task.ruby_opts = %w[-w] }
17
+
12
18
  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") #=> see response
26
+ end
27
+
28
+ puts(response)
@@ -9,8 +9,8 @@ RSpec.describe TCPClient::Address do
9
9
 
10
10
  it 'points to the given port on localhost' do
11
11
  expect(address.hostname).to eq 'localhost'
12
+ expect(address.port).to be 42
12
13
  expect(address.to_s).to eq 'localhost:42'
13
- expect(address.addrinfo.ip_port).to be 42
14
14
  end
15
15
 
16
16
  it 'uses IPv6' do
@@ -29,8 +29,9 @@ RSpec.describe TCPClient::Address do
29
29
  end
30
30
 
31
31
  it 'points to the given host and port' do
32
- expect(address.hostname).to eq addrinfo.getnameinfo[0]
33
- expect(address.addrinfo.ip_port).to be 42
32
+ expect(address.hostname).to eq 'localhost'
33
+ expect(address.port).to be 42
34
+ expect(address.to_s).to eq 'localhost:42'
34
35
  end
35
36
 
36
37
  it 'uses IPv6' do
@@ -46,30 +47,21 @@ RSpec.describe TCPClient::Address do
46
47
 
47
48
  it 'points to the given host and port' do
48
49
  expect(address.hostname).to eq 'localhost'
50
+ expect(address.port).to be 42
49
51
  expect(address.to_s).to eq 'localhost:42'
50
- expect(address.addrinfo.ip_port).to be 42
51
- end
52
-
53
- it 'uses IPv6' do
54
52
  expect(address.addrinfo.ip?).to be true
55
- expect(address.addrinfo.ipv6?).to be true
56
- expect(address.addrinfo.ipv4?).to be false
57
53
  end
54
+
58
55
  end
59
56
 
60
57
  context 'when only a port is provided' do
61
- subject(:address) { TCPClient::Address.new(':21') }
58
+ subject(:address) { TCPClient::Address.new(':42') }
62
59
 
63
60
  it 'points to the given port on localhost' do
64
- expect(address.hostname).to eq ''
65
- expect(address.to_s).to eq ':21'
66
- expect(address.addrinfo.ip_port).to be 21
67
- end
68
-
69
- it 'uses IPv4' do
61
+ expect(address.hostname).to eq 'localhost'
62
+ expect(address.port).to be 42
63
+ expect(address.to_s).to eq 'localhost:42'
70
64
  expect(address.addrinfo.ip?).to be true
71
- expect(address.addrinfo.ipv6?).to be false
72
- expect(address.addrinfo.ipv4?).to be true
73
65
  end
74
66
  end
75
67
 
@@ -78,14 +70,9 @@ RSpec.describe TCPClient::Address do
78
70
 
79
71
  it 'points to the given port on localhost' do
80
72
  expect(address.hostname).to eq '::1'
73
+ expect(address.port).to be 42
81
74
  expect(address.to_s).to eq '[::1]:42'
82
- expect(address.addrinfo.ip_port).to be 42
83
- end
84
-
85
- it 'uses IPv6' do
86
75
  expect(address.addrinfo.ip?).to be true
87
- expect(address.addrinfo.ipv6?).to be true
88
- expect(address.addrinfo.ipv4?).to be false
89
76
  end
90
77
  end
91
78
  end
@@ -108,13 +95,13 @@ RSpec.describe TCPClient::Address do
108
95
  expect(address_a).to eq address_b
109
96
  end
110
97
 
111
- context 'using the == opperator' do
98
+ context 'using the == operator' do
112
99
  it 'compares to equal' do
113
100
  expect(address_a == address_b).to be true
114
101
  end
115
102
  end
116
103
 
117
- context 'using the === opperator' do
104
+ context 'using the === operator' do
118
105
  it 'compares to equal' do
119
106
  expect(address_a === address_b).to be true
120
107
  end
@@ -129,13 +116,13 @@ RSpec.describe TCPClient::Address do
129
116
  expect(address_a).not_to eq address_b
130
117
  end
131
118
 
132
- context 'using the == opperator' do
119
+ context 'using the == operator' do
133
120
  it 'compares not to equal' do
134
121
  expect(address_a == address_b).to be false
135
122
  end
136
123
  end
137
124
 
138
- context 'using the === opperator' do
125
+ context 'using the === operator' do
139
126
  it 'compares not to equal' do
140
127
  expect(address_a === address_b).to be false
141
128
  end
@@ -233,13 +233,13 @@ RSpec.describe TCPClient::Configuration do
233
233
  expect(config_a).to eq config_b
234
234
  end
235
235
 
236
- context 'using the == opperator' do
236
+ context 'using the == operator' do
237
237
  it 'compares to equal' do
238
238
  expect(config_a == config_b).to be true
239
239
  end
240
240
  end
241
241
 
242
- context 'using the === opperator' do
242
+ context 'using the === operator' do
243
243
  it 'compares to equal' do
244
244
  expect(config_a === config_b).to be true
245
245
  end
@@ -254,13 +254,13 @@ RSpec.describe TCPClient::Configuration do
254
254
  expect(config_a).not_to eq config_b
255
255
  end
256
256
 
257
- context 'using the == opperator' do
257
+ context 'using the == operator' do
258
258
  it 'compares not to equal' do
259
259
  expect(config_a == config_b).to be false
260
260
  end
261
261
  end
262
262
 
263
- context 'using the === opperator' do
263
+ context 'using the === operator' do
264
264
  it 'compares not to equal' do
265
265
  expect(config_a === config_b).to be false
266
266
  end