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.
@@ -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
- :connect_retry_count, :connect_retry_interval, :retry_count,
42
- :policy, :close_on_error, :buffered, :ssl, :buffered,
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
- @@reconnect_on_errors = [
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
- def self.reconnect_on_errors
70
- @@reconnect_on_errors
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.send('Update the database')
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
- begin
93
- connection = self.new(params)
94
- yield(connection)
95
- ensure
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 [Boolean]
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
- # :connect_retry_count [Fixnum]
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 [Fixnum]
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.send('Update the database')
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(parameters={})
246
- params = parameters.dup
247
- @read_timeout = (params.delete(:read_timeout) || 60.0).to_f
248
- @write_timeout = (params.delete(:write_timeout) || 60.0).to_f
249
- @connect_timeout = (params.delete(:connect_timeout) || 10).to_f
250
- buffered = params.delete(:buffered)
251
- @buffered = buffered.nil? ? true : buffered
252
- @connect_retry_count = params.delete(:connect_retry_count) || 10
253
- @retry_count = params.delete(:retry_count) || 3
254
- @connect_retry_interval = (params.delete(:connect_retry_interval) || 0.5).to_f
255
- @on_connect = params.delete(:on_connect)
256
- @proxy_server = params.delete(:proxy_server)
257
- @policy = params.delete(:policy) || :ordered
258
- @close_on_error = params.delete(:close_on_error)
259
- @close_on_error = true if @close_on_error.nil?
260
- if @ssl = params.delete(:ssl)
261
- @ssl = {} if @ssl == true
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
- if server = params.delete(:server)
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 => exception
316
- cause = exception.is_a?(ConnectionTimeout) ? exception : exception.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
- logger.warn "#connect Failed to connect to any of #{servers.join(',')}. Sleeping:#{connect_retry_interval}s. Retry: #{retries}" if respond_to?(:logger)
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. #{exception.class}: #{exception.message}"
325
- logger.benchmark_error(message, exception: exception, duration: (Time.now - start_time)) if respond_to?(:logger)
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
- # Send data to the server
327
+ # Write data to the server
332
328
  #
333
- # Use #with_retry to add resilience to the #send method
329
+ # Use #with_retry to add resilience to the #write method
334
330
  #
335
- # Raises Net::TCPClient::ConnectionFailure whenever the send fails
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 longers want the connection after a
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('#write', payload: payload) do
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 => exc
360
+ rescue Exception => e
365
361
  close if close_on_error
366
- raise exc
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 respnse from the connection
377
+ # to read the response from the connection
382
378
  #
383
379
  # Parameters
384
- # length [Fixnum]
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 longers want the connection after a
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('#read', payload: payload) do
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 => exc
413
+ rescue Exception => e
418
414
  close if close_on_error
419
- raise exc
415
+ raise e
420
416
  end
421
417
 
422
- # Send and/or receive data with automatic retry on connection failure
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 send and the read with #retry_on_connection_failure
432
- # since it is safe to send the same data twice to the server
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 send can be sent many times it is safe to also put the receive
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.send("GETVALUE:count\n")
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 send with #retry_on_connection_failure
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 send if the connection fails during the #read
442
+ # not retry the write if the connection fails during the #read
447
443
  #
448
444
  # value = 45
449
- # # Only the send is within the retry block since we cannot re-send once
450
- # # the send was successful since the server may have made the change
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.send("SETVALUE:#{count}\n")
448
+ # client.write("SETVALUE:#{count}\n")
453
449
  # end
454
- # # Server returns "SAVED" if the call was successfull
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 occurrs during the block invocation the block
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 => exception
469
- exc_str = exception.cause ? "#{exception.cause.class}: #{exception.cause.message}" : exception.message
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?(exception.cause.class)
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 exception
469
+ raise e
474
470
  elsif retries < @retry_count
475
471
  retries += 1
476
- logger.warn "#retry_on_connection_failure retry #{retries} due to #{exception.class}: #{exception.message}" if respond_to?(:logger)
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
- logger.error "#retry_on_connection_failure Connection failure: #{exception.class}: #{exception.message}. Giving up after #{retries} retries" if respond_to?(:logger)
481
- raise ConnectionFailure.new("After #{retries} retries to host '#{server}': #{exc_str}", server, exception.cause)
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 => exception
494
- logger.warn "IOError when attempting to close socket: #{exception.class}: #{exception.message}" if respond_to?(:logger)
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
- respond_to?(:logger) ? logger.benchmark_debug('#flush') { socket.flush } : socket.flush
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
- !socket.eof? rescue false
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 => exception
551
- last_exception = 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 = ssl ? ssl_connect(socket, address, ssl_handshake_timeout) : 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.call(self) if @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.new("Timed out after #{timeout} seconds trying to connect to #{address}")
602
- rescue SystemCallError, IOError => exception
603
- message = "#connect Connection failure connecting to '#{address.to_s}': #{exception.class}: #{exception.message}"
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, exception)
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 < 0
615
+ if timeout.negative?
612
616
  socket.write(data)
613
617
  else
614
- deadline = Time.now.utc + timeout
618
+ deadline = Time.now.utc + timeout
619
+ length = data.bytesize
620
+ total_count = 0
615
621
  non_blocking(socket, deadline) do
616
- socket.write_nonblock(data)
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.new("Timed out after #{timeout} seconds trying to write to #{address}")
622
- rescue SystemCallError, IOError => exception
623
- message = "#write Connection failure while writing to '#{address.to_s}': #{exception.class}: #{exception.message}"
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, exception)
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 < 0
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('Connection lost while reading data', address.to_s, EOFError.new('end of file reached'))
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.new("Timed out after #{timeout} seconds trying to read from #{address}")
648
- rescue SystemCallError, IOError => exception
649
- message = "#read Connection failure while reading data from '#{address.to_s}': #{exception.class}: #{exception.message}"
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, exception)
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 < 0
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.new("SSL handshake Timed out after #{timeout} seconds trying to connect to #{address.to_s}")
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 => exception
702
- message = "#connect SSL handshake failure with '#{address.to_s}': #{exception.class}: #{exception.message}"
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, exception)
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
- unless OpenSSL::SSL.verify_certificate_identity(ssl_socket.peer_cert, address.host_name)
715
- ssl_socket.close
716
- message = "#connect SSL handshake failed due to a hostname mismatch with '#{address.to_s}'"
717
- logger.error message if respond_to?(:logger)
718
- raise ConnectionFailure.new(message, address.to_s)
719
- end
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
@@ -1,5 +1,5 @@
1
1
  module Net
2
- class TCPClient #:nodoc
3
- VERSION = '2.0.1'
2
+ class TCPClient
3
+ VERSION = "2.2.1".freeze
4
4
  end
5
5
  end
@@ -1,22 +1,22 @@
1
- require 'socket'
1
+ require "socket"
2
2
  # Load SemanticLogger if available
3
3
  begin
4
- require 'semantic_logger'
4
+ require "semantic_logger"
5
5
  rescue LoadError
6
6
  end
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'
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, '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'
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
@@ -1 +1 @@
1
- require 'net/tcp_client'
1
+ require "net/tcp_client"