net_tcp_client 2.0.1 → 2.2.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.
- checksums.yaml +5 -5
- data/README.md +20 -20
- data/Rakefile +10 -17
- data/lib/net/tcp_client/address.rb +11 -6
- data/lib/net/tcp_client/exceptions.rb +3 -3
- data/lib/net/tcp_client/policy/base.rb +0 -1
- data/lib/net/tcp_client/policy/custom.rb +5 -3
- data/lib/net/tcp_client/policy/ordered.rb +1 -2
- data/lib/net/tcp_client/policy/random.rb +1 -2
- data/lib/net/tcp_client/tcp_client.rb +149 -122
- data/lib/net/tcp_client/version.rb +2 -2
- data/lib/net/tcp_client.rb +10 -10
- data/lib/net_tcp_client.rb +1 -1
- metadata +11 -32
- data/test/address_test.rb +0 -91
- data/test/policy/custom_policy_test.rb +0 -42
- data/test/policy/ordered_policy_test.rb +0 -36
- data/test/policy/random_policy_test.rb +0 -46
- data/test/simple_tcp_server.rb +0 -140
- data/test/ssl_files/ca.pem +0 -19
- data/test/ssl_files/localhost-server-key.pem +0 -27
- data/test/ssl_files/localhost-server.pem +0 -18
- data/test/tcp_client_test.rb +0 -245
- data/test/test_helper.rb +0 -14
@@ -38,9 +38,8 @@ module Net
|
|
38
38
|
include SemanticLogger::Loggable if defined?(SemanticLogger::Loggable)
|
39
39
|
|
40
40
|
attr_accessor :connect_timeout, :read_timeout, :write_timeout,
|
41
|
-
|
42
|
-
|
43
|
-
:proxy_server
|
41
|
+
:connect_retry_count, :connect_retry_interval, :retry_count,
|
42
|
+
:policy, :close_on_error, :buffered, :ssl, :proxy_server, :keepalive
|
44
43
|
attr_reader :servers, :address, :socket, :ssl_handshake_timeout
|
45
44
|
|
46
45
|
# Supports embedding user supplied data along with this connection
|
@@ -48,7 +47,7 @@ module Net
|
|
48
47
|
# Not used or modified by TCPClient
|
49
48
|
attr_accessor :user_data
|
50
49
|
|
51
|
-
|
50
|
+
@reconnect_on_errors = [
|
52
51
|
Errno::ECONNABORTED,
|
53
52
|
Errno::ECONNREFUSED,
|
54
53
|
Errno::ECONNRESET,
|
@@ -66,8 +65,8 @@ module Net
|
|
66
65
|
# Return the array of errors that will result in an automatic connection retry
|
67
66
|
# To add any additional errors to the standard list:
|
68
67
|
# Net::TCPClient.reconnect_on_errors << Errno::EPROTO
|
69
|
-
|
70
|
-
|
68
|
+
class << self
|
69
|
+
attr_reader :reconnect_on_errors
|
71
70
|
end
|
72
71
|
|
73
72
|
# Create a connection, call the supplied block and close the connection on
|
@@ -82,19 +81,17 @@ module Net
|
|
82
81
|
# connect_retry_count: 5
|
83
82
|
# ) do |client|
|
84
83
|
# client.retry_on_connection_failure do
|
85
|
-
# client.
|
84
|
+
# client.write('Update the database')
|
86
85
|
# end
|
87
86
|
# response = client.read(20)
|
88
87
|
# puts "Received: #{response}"
|
89
88
|
# end
|
90
89
|
#
|
91
|
-
def self.connect(params={})
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
connection.close if connection
|
97
|
-
end
|
90
|
+
def self.connect(params = {})
|
91
|
+
connection = new(params)
|
92
|
+
yield(connection)
|
93
|
+
ensure
|
94
|
+
connection&.close
|
98
95
|
end
|
99
96
|
|
100
97
|
# Create a new TCP Client connection
|
@@ -129,7 +126,7 @@ module Net
|
|
129
126
|
# Can be overridden by supplying a timeout in the write call
|
130
127
|
# Default: 60
|
131
128
|
#
|
132
|
-
# :buffered [
|
129
|
+
# :buffered [true|false]
|
133
130
|
# Whether to use Nagle's Buffering algorithm (http://en.wikipedia.org/wiki/Nagle's_algorithm)
|
134
131
|
# Recommend disabling for RPC style invocations where we don't want to wait for an
|
135
132
|
# ACK from the server before sending the last partial segment
|
@@ -139,7 +136,12 @@ module Net
|
|
139
136
|
# internal buffering.
|
140
137
|
# Default: true
|
141
138
|
#
|
142
|
-
# :
|
139
|
+
# :keepalive [true|false]
|
140
|
+
# Makes the OS check connections even when not in use, so that failed connections fail immediately
|
141
|
+
# upon use instead of possibly taking considerable time to fail.
|
142
|
+
# Default: true
|
143
|
+
#
|
144
|
+
# :connect_retry_count [Integer]
|
143
145
|
# Number of times to retry connecting when a connection fails
|
144
146
|
# Default: 10
|
145
147
|
#
|
@@ -147,7 +149,7 @@ module Net
|
|
147
149
|
# Number of seconds between connection retry attempts after the first failed attempt
|
148
150
|
# Default: 0.5
|
149
151
|
#
|
150
|
-
# :retry_count [
|
152
|
+
# :retry_count [Integer]
|
151
153
|
# Number of times to retry when calling #retry_on_connection_failure
|
152
154
|
# This is independent of :connect_retry_count which still applies with
|
153
155
|
# connection failures. This retry controls upto how many times to retry the
|
@@ -216,7 +218,7 @@ module Net
|
|
216
218
|
# )
|
217
219
|
#
|
218
220
|
# client.retry_on_connection_failure do
|
219
|
-
# client.
|
221
|
+
# client.write('Update the database')
|
220
222
|
# end
|
221
223
|
#
|
222
224
|
# # Read upto 20 characters from the server
|
@@ -242,40 +244,32 @@ module Net
|
|
242
244
|
# verify_mode: OpenSSL::SSL::VERIFY_NONE
|
243
245
|
# }
|
244
246
|
# )
|
245
|
-
def initialize(
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
@
|
252
|
-
@
|
253
|
-
@
|
254
|
-
@
|
255
|
-
@
|
256
|
-
@
|
257
|
-
@
|
258
|
-
@
|
259
|
-
@
|
260
|
-
|
261
|
-
|
247
|
+
def initialize(server: nil, servers: nil,
|
248
|
+
policy: :ordered, buffered: true, keepalive: true,
|
249
|
+
connect_timeout: 10.0, read_timeout: 60.0, write_timeout: 60.0,
|
250
|
+
connect_retry_count: 10, retry_count: 3, connect_retry_interval: 0.5, close_on_error: true,
|
251
|
+
on_connect: nil, proxy_server: nil, ssl: nil)
|
252
|
+
@read_timeout = read_timeout.to_f
|
253
|
+
@write_timeout = write_timeout.to_f
|
254
|
+
@connect_timeout = connect_timeout.to_f
|
255
|
+
@buffered = buffered
|
256
|
+
@keepalive = keepalive
|
257
|
+
@connect_retry_count = connect_retry_count
|
258
|
+
@retry_count = retry_count
|
259
|
+
@connect_retry_interval = connect_retry_interval.to_f
|
260
|
+
@on_connect = on_connect
|
261
|
+
@proxy_server = proxy_server
|
262
|
+
@policy = policy
|
263
|
+
@close_on_error = close_on_error
|
264
|
+
if ssl
|
265
|
+
@ssl = ssl == true ? {} : ssl
|
262
266
|
@ssl_handshake_timeout = (@ssl.delete(:handshake_timeout) || @connect_timeout).to_f
|
263
267
|
end
|
268
|
+
@servers = [server] if server
|
269
|
+
@servers = servers if servers
|
264
270
|
|
265
|
-
|
266
|
-
@servers = [server]
|
267
|
-
end
|
268
|
-
if servers = params.delete(:servers)
|
269
|
-
@servers = servers
|
270
|
-
end
|
271
|
-
raise(ArgumentError, 'Missing mandatory :server or :servers') unless @servers
|
272
|
-
|
273
|
-
if params.delete(:logger)
|
274
|
-
warn '[Deprecated] :logger option is no longer offered. Add semantic_logger gem to enable logging.' if $VERBOSE
|
275
|
-
end
|
276
|
-
raise(ArgumentError, "Invalid options: #{params.inspect}") if params.size > 0
|
271
|
+
raise(ArgumentError, "Missing mandatory :server or :servers") unless @servers
|
277
272
|
|
278
|
-
# Connect to the Server
|
279
273
|
connect
|
280
274
|
end
|
281
275
|
|
@@ -312,27 +306,29 @@ module Net
|
|
312
306
|
begin
|
313
307
|
connect_to_server(servers, policy)
|
314
308
|
logger.info(message: "Connected to #{address}", duration: (Time.now - start_time) * 1000) if respond_to?(:logger)
|
315
|
-
rescue ConnectionFailure, ConnectionTimeout =>
|
316
|
-
cause =
|
309
|
+
rescue ConnectionFailure, ConnectionTimeout => e
|
310
|
+
cause = e.is_a?(ConnectionTimeout) ? e : e.cause
|
317
311
|
# Retry-able?
|
318
312
|
if self.class.reconnect_on_errors.include?(cause.class) && (retries < connect_retry_count.to_i)
|
319
313
|
retries += 1
|
320
|
-
|
314
|
+
if respond_to?(:logger)
|
315
|
+
logger.warn "#connect Failed to connect to any of #{servers.join(',')}. Sleeping:#{connect_retry_interval}s. Retry: #{retries}"
|
316
|
+
end
|
321
317
|
sleep(connect_retry_interval)
|
322
318
|
retry
|
323
319
|
else
|
324
|
-
message = "#connect Failed to connect to any of #{servers.join(',')} after #{retries} retries. #{
|
325
|
-
logger.benchmark_error(message, exception:
|
320
|
+
message = "#connect Failed to connect to any of #{servers.join(',')} after #{retries} retries. #{e.class}: #{e.message}"
|
321
|
+
logger.benchmark_error(message, exception: e, duration: (Time.now - start_time)) if respond_to?(:logger)
|
326
322
|
raise ConnectionFailure.new(message, address.to_s, cause)
|
327
323
|
end
|
328
324
|
end
|
329
325
|
end
|
330
326
|
|
331
|
-
#
|
327
|
+
# Write data to the server
|
332
328
|
#
|
333
|
-
# Use #with_retry to add resilience to the #
|
329
|
+
# Use #with_retry to add resilience to the #write method
|
334
330
|
#
|
335
|
-
# Raises Net::TCPClient::ConnectionFailure whenever the
|
331
|
+
# Raises Net::TCPClient::ConnectionFailure whenever the write fails
|
336
332
|
# For a description of the errors, see Socket#write
|
337
333
|
#
|
338
334
|
# Parameters
|
@@ -345,7 +341,7 @@ module Net
|
|
345
341
|
#
|
346
342
|
# Note: After a Net::TCPClient::ReadTimeout #read can be called again on
|
347
343
|
# the same socket to read the response later.
|
348
|
-
# If the application no
|
344
|
+
# If the application no longer wants the connection after a
|
349
345
|
# Net::TCPClient::ReadTimeout, then the #close method _must_ be called
|
350
346
|
# before calling _connect_ or _retry_on_connection_failure_ to create
|
351
347
|
# a new connection
|
@@ -355,15 +351,15 @@ module Net
|
|
355
351
|
payload = {timeout: timeout}
|
356
352
|
# With trace level also log the sent data
|
357
353
|
payload[:data] = data if logger.trace?
|
358
|
-
logger.benchmark_debug(
|
354
|
+
logger.benchmark_debug("#write", payload: payload) do
|
359
355
|
payload[:bytes] = socket_write(data, timeout)
|
360
356
|
end
|
361
357
|
else
|
362
358
|
socket_write(data, timeout)
|
363
359
|
end
|
364
|
-
rescue Exception =>
|
360
|
+
rescue Exception => e
|
365
361
|
close if close_on_error
|
366
|
-
raise
|
362
|
+
raise e
|
367
363
|
end
|
368
364
|
|
369
365
|
# Returns a response from the server
|
@@ -378,10 +374,10 @@ module Net
|
|
378
374
|
# requested number of bytes from the server
|
379
375
|
# Partial data will not be returned
|
380
376
|
# Connection is _not_ closed and #read can be called again later
|
381
|
-
# to read the
|
377
|
+
# to read the response from the connection
|
382
378
|
#
|
383
379
|
# Parameters
|
384
|
-
# length [
|
380
|
+
# length [Integer]
|
385
381
|
# The number of bytes to return
|
386
382
|
# #read will not return until 'length' bytes have been received from
|
387
383
|
# the server
|
@@ -398,14 +394,14 @@ module Net
|
|
398
394
|
#
|
399
395
|
# Note: After a Net::TCPClient::ReadTimeout #read can be called again on
|
400
396
|
# the same socket to read the response later.
|
401
|
-
# If the application no
|
397
|
+
# If the application no longer wants the connection after a
|
402
398
|
# Net::TCPClient::ReadTimeout, then the #close method _must_ be called
|
403
399
|
# before calling _connect_ or _retry_on_connection_failure_ to create
|
404
400
|
# a new connection
|
405
401
|
def read(length, buffer = nil, timeout = read_timeout)
|
406
402
|
if respond_to?(:logger)
|
407
403
|
payload = {bytes: length, timeout: timeout}
|
408
|
-
logger.benchmark_debug(
|
404
|
+
logger.benchmark_debug("#read", payload: payload) do
|
409
405
|
data = socket_read(length, buffer, timeout)
|
410
406
|
# With trace level also log the received data
|
411
407
|
payload[:data] = data if logger.trace?
|
@@ -414,12 +410,12 @@ module Net
|
|
414
410
|
else
|
415
411
|
socket_read(length, buffer, timeout)
|
416
412
|
end
|
417
|
-
rescue Exception =>
|
413
|
+
rescue Exception => e
|
418
414
|
close if close_on_error
|
419
|
-
raise
|
415
|
+
raise e
|
420
416
|
end
|
421
417
|
|
422
|
-
#
|
418
|
+
# Write and/or receive data with automatic retry on connection failure
|
423
419
|
#
|
424
420
|
# On a connection failure, it will create a new connection and retry the block.
|
425
421
|
# Returns immediately on exception Net::TCPClient::ReadTimeout
|
@@ -428,34 +424,34 @@ module Net
|
|
428
424
|
# 1. Example of a resilient _readonly_ request:
|
429
425
|
#
|
430
426
|
# When reading data from a server that does not change state on the server
|
431
|
-
# Wrap both the
|
432
|
-
# since it is safe to
|
427
|
+
# Wrap both the write and the read with #retry_on_connection_failure
|
428
|
+
# since it is safe to write the same data twice to the server
|
433
429
|
#
|
434
|
-
# # Since the
|
430
|
+
# # Since the write can be sent many times it is safe to also put the receive
|
435
431
|
# # inside the retry block
|
436
432
|
# value = client.retry_on_connection_failure do
|
437
|
-
# client.
|
433
|
+
# client.write("GETVALUE:count\n")
|
438
434
|
# client.read(20).strip.to_i
|
439
435
|
# end
|
440
436
|
#
|
441
437
|
# 2. Example of a resilient request that _modifies_ data on the server:
|
442
438
|
#
|
443
439
|
# When changing state on the server, for example when updating a value
|
444
|
-
# Wrap _only_ the
|
440
|
+
# Wrap _only_ the write with #retry_on_connection_failure
|
445
441
|
# The read must be outside the #retry_on_connection_failure since we must
|
446
|
-
# not retry the
|
442
|
+
# not retry the write if the connection fails during the #read
|
447
443
|
#
|
448
444
|
# value = 45
|
449
|
-
# # Only the
|
450
|
-
# # the
|
445
|
+
# # Only the write is within the retry block since we cannot re-write once
|
446
|
+
# # the write was successful since the server may have made the change
|
451
447
|
# client.retry_on_connection_failure do
|
452
|
-
# client.
|
448
|
+
# client.write("SETVALUE:#{count}\n")
|
453
449
|
# end
|
454
|
-
# # Server returns "SAVED" if the call was
|
450
|
+
# # Server returns "SAVED" if the call was successful
|
455
451
|
# result = client.read(20).strip
|
456
452
|
#
|
457
453
|
# Error handling is implemented as follows:
|
458
|
-
# If a network failure
|
454
|
+
# If a network failure occurs during the block invocation the block
|
459
455
|
# will be called again with a new connection to the server.
|
460
456
|
# It will only be retried up to 3 times
|
461
457
|
# The re-connect will independently retry and timeout using all the
|
@@ -465,20 +461,22 @@ module Net
|
|
465
461
|
begin
|
466
462
|
connect if closed?
|
467
463
|
yield(self)
|
468
|
-
rescue ConnectionFailure =>
|
469
|
-
exc_str =
|
464
|
+
rescue ConnectionFailure => e
|
465
|
+
exc_str = e.cause ? "#{e.cause.class}: #{e.cause.message}" : e.message
|
470
466
|
# Re-raise exceptions that should not be retried
|
471
|
-
if !self.class.reconnect_on_errors.include?(
|
467
|
+
if !self.class.reconnect_on_errors.include?(e.cause.class)
|
472
468
|
logger.info "#retry_on_connection_failure not configured to retry: #{exc_str}" if respond_to?(:logger)
|
473
|
-
raise
|
469
|
+
raise e
|
474
470
|
elsif retries < @retry_count
|
475
471
|
retries += 1
|
476
|
-
logger.warn "#retry_on_connection_failure retry #{retries} due to #{
|
472
|
+
logger.warn "#retry_on_connection_failure retry #{retries} due to #{e.class}: #{e.message}" if respond_to?(:logger)
|
477
473
|
connect
|
478
474
|
retry
|
479
475
|
end
|
480
|
-
|
481
|
-
|
476
|
+
if respond_to?(:logger)
|
477
|
+
logger.error "#retry_on_connection_failure Connection failure: #{e.class}: #{e.message}. Giving up after #{retries} retries"
|
478
|
+
end
|
479
|
+
raise ConnectionFailure.new("After #{retries} retries to any of #{servers.join(',')}': #{exc_str}", servers, e.cause)
|
482
480
|
end
|
483
481
|
end
|
484
482
|
|
@@ -490,14 +488,15 @@ module Net
|
|
490
488
|
@socket = nil
|
491
489
|
@address = nil
|
492
490
|
true
|
493
|
-
rescue IOError =>
|
494
|
-
logger.warn "IOError when attempting to close socket: #{
|
491
|
+
rescue IOError => e
|
492
|
+
logger.warn "IOError when attempting to close socket: #{e.class}: #{e.message}" if respond_to?(:logger)
|
495
493
|
false
|
496
494
|
end
|
497
495
|
|
498
496
|
def flush
|
499
497
|
return unless socket
|
500
|
-
|
498
|
+
|
499
|
+
respond_to?(:logger) ? logger.benchmark_debug("#flush") { socket.flush } : socket.flush
|
501
500
|
end
|
502
501
|
|
503
502
|
def closed?
|
@@ -525,7 +524,11 @@ module Net
|
|
525
524
|
return false if socket.nil? || closed?
|
526
525
|
|
527
526
|
if IO.select([socket], nil, nil, 0)
|
528
|
-
|
527
|
+
begin
|
528
|
+
!socket.eof?
|
529
|
+
rescue StandardError
|
530
|
+
false
|
531
|
+
end
|
529
532
|
else
|
530
533
|
true
|
531
534
|
end
|
@@ -547,8 +550,8 @@ module Net
|
|
547
550
|
Policy::Base.factory(policy, servers).each do |address|
|
548
551
|
begin
|
549
552
|
return connect_to_address(address)
|
550
|
-
rescue ConnectionTimeout, ConnectionFailure =>
|
551
|
-
last_exception =
|
553
|
+
rescue ConnectionTimeout, ConnectionFailure => e
|
554
|
+
last_exception = e
|
552
555
|
end
|
553
556
|
end
|
554
557
|
|
@@ -572,14 +575,15 @@ module Net
|
|
572
575
|
socket.sync = true
|
573
576
|
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
574
577
|
end
|
578
|
+
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true) if keepalive
|
575
579
|
|
576
580
|
socket_connect(socket, address, connect_timeout)
|
577
581
|
|
578
|
-
@socket
|
582
|
+
@socket = ssl ? ssl_connect(socket, address, ssl_handshake_timeout) : socket
|
579
583
|
@address = address
|
580
584
|
|
581
585
|
# Invoke user supplied Block every time a new connection has been established
|
582
|
-
@on_connect
|
586
|
+
@on_connect&.call(self)
|
583
587
|
end
|
584
588
|
|
585
589
|
# Connect to server
|
@@ -598,36 +602,48 @@ module Net
|
|
598
602
|
rescue Errno::EISCONN
|
599
603
|
# Connection was successful.
|
600
604
|
rescue NonBlockingTimeout
|
601
|
-
raise ConnectionTimeout
|
602
|
-
rescue SystemCallError, IOError =>
|
603
|
-
message = "#connect Connection failure connecting to '#{address
|
605
|
+
raise ConnectionTimeout, "Timed out after #{timeout} seconds trying to connect to #{address}"
|
606
|
+
rescue SystemCallError, IOError => e
|
607
|
+
message = "#connect Connection failure connecting to '#{address}': #{e.class}: #{e.message}"
|
604
608
|
logger.error message if respond_to?(:logger)
|
605
|
-
raise ConnectionFailure.new(message, address.to_s,
|
609
|
+
raise ConnectionFailure.new(message, address.to_s, e)
|
606
610
|
end
|
607
611
|
end
|
608
612
|
|
609
613
|
# Write to the socket
|
610
614
|
def socket_write(data, timeout)
|
611
|
-
if timeout
|
615
|
+
if timeout.negative?
|
612
616
|
socket.write(data)
|
613
617
|
else
|
614
|
-
deadline
|
618
|
+
deadline = Time.now.utc + timeout
|
619
|
+
length = data.bytesize
|
620
|
+
total_count = 0
|
615
621
|
non_blocking(socket, deadline) do
|
616
|
-
|
622
|
+
loop do
|
623
|
+
begin
|
624
|
+
count = socket.write_nonblock(data)
|
625
|
+
rescue Errno::EWOULDBLOCK
|
626
|
+
retry
|
627
|
+
end
|
628
|
+
total_count += count
|
629
|
+
return total_count if total_count >= length
|
630
|
+
|
631
|
+
data = data.byteslice(count..-1)
|
632
|
+
end
|
617
633
|
end
|
618
634
|
end
|
619
635
|
rescue NonBlockingTimeout
|
620
636
|
logger.warn "#write Timeout after #{timeout} seconds" if respond_to?(:logger)
|
621
|
-
raise WriteTimeout
|
622
|
-
rescue SystemCallError, IOError =>
|
623
|
-
message = "#write Connection failure while writing to '#{address
|
637
|
+
raise WriteTimeout, "Timed out after #{timeout} seconds trying to write to #{address}"
|
638
|
+
rescue SystemCallError, IOError => e
|
639
|
+
message = "#write Connection failure while writing to '#{address}': #{e.class}: #{e.message}"
|
624
640
|
logger.error message if respond_to?(:logger)
|
625
|
-
raise ConnectionFailure.new(message, address.to_s,
|
641
|
+
raise ConnectionFailure.new(message, address.to_s, e)
|
626
642
|
end
|
627
643
|
|
628
644
|
def socket_read(length, buffer, timeout)
|
629
645
|
result =
|
630
|
-
if timeout
|
646
|
+
if timeout.negative?
|
631
647
|
buffer.nil? ? socket.read(length) : socket.read(length, buffer)
|
632
648
|
else
|
633
649
|
deadline = Time.now.utc + timeout
|
@@ -639,19 +655,19 @@ module Net
|
|
639
655
|
# EOF before all the data was returned
|
640
656
|
if result.nil? || (result.length < length)
|
641
657
|
logger.warn "#read server closed the connection before #{length} bytes were returned" if respond_to?(:logger)
|
642
|
-
raise ConnectionFailure.new(
|
658
|
+
raise ConnectionFailure.new("Connection lost while reading data", address.to_s, EOFError.new("end of file reached"))
|
643
659
|
end
|
644
660
|
result
|
645
661
|
rescue NonBlockingTimeout
|
646
662
|
logger.warn "#read Timeout after #{timeout} seconds" if respond_to?(:logger)
|
647
|
-
raise ReadTimeout
|
648
|
-
rescue SystemCallError, IOError =>
|
649
|
-
message = "#read Connection failure while reading data from '#{address
|
663
|
+
raise ReadTimeout, "Timed out after #{timeout} seconds trying to read from #{address}"
|
664
|
+
rescue SystemCallError, IOError => e
|
665
|
+
message = "#read Connection failure while reading data from '#{address}': #{e.class}: #{e.message}"
|
650
666
|
logger.error message if respond_to?(:logger)
|
651
|
-
raise ConnectionFailure.new(message, address.to_s,
|
667
|
+
raise ConnectionFailure.new(message, address.to_s, e)
|
652
668
|
end
|
653
669
|
|
654
|
-
class NonBlockingTimeout< ::SocketError
|
670
|
+
class NonBlockingTimeout < ::SocketError
|
655
671
|
end
|
656
672
|
|
657
673
|
def non_blocking(socket, deadline)
|
@@ -659,16 +675,19 @@ module Net
|
|
659
675
|
rescue IO::WaitReadable
|
660
676
|
time_remaining = check_time_remaining(deadline)
|
661
677
|
raise NonBlockingTimeout unless IO.select([socket], nil, nil, time_remaining)
|
678
|
+
|
662
679
|
retry
|
663
680
|
rescue IO::WaitWritable
|
664
681
|
time_remaining = check_time_remaining(deadline)
|
665
682
|
raise NonBlockingTimeout unless IO.select(nil, [socket], nil, time_remaining)
|
683
|
+
|
666
684
|
retry
|
667
685
|
end
|
668
686
|
|
669
687
|
def check_time_remaining(deadline)
|
670
688
|
time_remaining = deadline - Time.now.utc
|
671
|
-
raise NonBlockingTimeout if time_remaining
|
689
|
+
raise NonBlockingTimeout if time_remaining.negative?
|
690
|
+
|
672
691
|
time_remaining
|
673
692
|
end
|
674
693
|
|
@@ -682,6 +701,7 @@ module Net
|
|
682
701
|
ssl_context.set_params(ssl.is_a?(Hash) ? ssl : {})
|
683
702
|
|
684
703
|
ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context)
|
704
|
+
ssl_socket.hostname = address.host_name
|
685
705
|
ssl_socket.sync_close = true
|
686
706
|
|
687
707
|
begin
|
@@ -695,13 +715,13 @@ module Net
|
|
695
715
|
rescue Errno::EISCONN
|
696
716
|
# Connection was successful.
|
697
717
|
rescue NonBlockingTimeout
|
698
|
-
raise ConnectionTimeout
|
718
|
+
raise ConnectionTimeout, "SSL handshake Timed out after #{timeout} seconds trying to connect to #{address}"
|
699
719
|
end
|
700
720
|
end
|
701
|
-
rescue SystemCallError, OpenSSL::SSL::SSLError, IOError =>
|
702
|
-
message = "#connect SSL handshake failure with '#{address
|
721
|
+
rescue SystemCallError, OpenSSL::SSL::SSLError, IOError => e
|
722
|
+
message = "#connect SSL handshake failure with '#{address}': #{e.class}: #{e.message}"
|
703
723
|
logger.error message if respond_to?(:logger)
|
704
|
-
raise ConnectionFailure.new(message, address.to_s,
|
724
|
+
raise ConnectionFailure.new(message, address.to_s, e)
|
705
725
|
end
|
706
726
|
|
707
727
|
# Verify Peer certificate
|
@@ -711,13 +731,20 @@ module Net
|
|
711
731
|
|
712
732
|
# Raises Net::TCPClient::ConnectionFailure if the peer certificate does not match its hostname
|
713
733
|
def ssl_verify(ssl_socket, address)
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
734
|
+
return if OpenSSL::SSL.verify_certificate_identity(ssl_socket.peer_cert, address.host_name)
|
735
|
+
|
736
|
+
domains = extract_domains_from_cert(ssl_socket.peer_cert)
|
737
|
+
ssl_socket.close
|
738
|
+
message = "#connect SSL handshake failed due to a hostname mismatch. Request address was: '#{address}'" \
|
739
|
+
" Certificate valid for hostnames: #{domains.map { |d| "'#{d}'" }.join(',')}"
|
740
|
+
logger.error message if respond_to?(:logger)
|
741
|
+
raise ConnectionFailure.new(message, address.to_s)
|
720
742
|
end
|
721
743
|
|
744
|
+
def extract_domains_from_cert(cert)
|
745
|
+
cert.subject.to_a.each do |oid, value|
|
746
|
+
return [value] if oid == "CN"
|
747
|
+
end
|
748
|
+
end
|
722
749
|
end
|
723
750
|
end
|
data/lib/net/tcp_client.rb
CHANGED
@@ -1,22 +1,22 @@
|
|
1
|
-
require
|
1
|
+
require "socket"
|
2
2
|
# Load SemanticLogger if available
|
3
3
|
begin
|
4
|
-
require
|
4
|
+
require "semantic_logger"
|
5
5
|
rescue LoadError
|
6
6
|
end
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
7
|
+
require "net/tcp_client/version"
|
8
|
+
require "net/tcp_client/address"
|
9
|
+
require "net/tcp_client/exceptions"
|
10
|
+
require "net/tcp_client/tcp_client"
|
11
11
|
|
12
12
|
# @formatter:off
|
13
13
|
module Net
|
14
14
|
class TCPClient
|
15
15
|
module Policy
|
16
|
-
autoload :Base,
|
17
|
-
autoload :Custom,
|
18
|
-
autoload :Ordered,
|
19
|
-
autoload :Random,
|
16
|
+
autoload :Base, "net/tcp_client/policy/base.rb"
|
17
|
+
autoload :Custom, "net/tcp_client/policy/custom.rb"
|
18
|
+
autoload :Ordered, "net/tcp_client/policy/ordered.rb"
|
19
|
+
autoload :Random, "net/tcp_client/policy/random.rb"
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
data/lib/net_tcp_client.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
require
|
1
|
+
require "net/tcp_client"
|