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