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.
@@ -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"