tcp-client 0.9.3 → 0.10.1

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