diff --git a/lib/final_destination.rb b/lib/final_destination.rb index 893b38e9c5..2f90c6c099 100644 --- a/lib/final_destination.rb +++ b/lib/final_destination.rb @@ -10,6 +10,7 @@ require "url_helper" class FinalDestination MAX_REQUEST_TIME_SECONDS = 10 MAX_REQUEST_SIZE_BYTES = 5_242_880 # 1024 * 1024 * 5 + DEFAULT_CONNECTION_ATTEMPT_TIMEOUT = 5 def self.clear_https_cache!(domain) key = redis_https_key(domain) @@ -33,7 +34,7 @@ class FinalDestination DEFAULT_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Safari/605.1.15" - attr_reader :status, :cookie, :status_code, :content_type, :ignored + attr_reader :status, :cookie, :status_code, :content_type, :ignored, :connection_attempt_timeout def initialize(url, opts = nil) @url = url @@ -69,6 +70,8 @@ class FinalDestination @limited_ips = [] @verbose = @opts[:verbose] || false @timeout = @opts[:timeout] || nil + @connection_attempt_timeout = + @opts[:connection_attempt_timeout] || DEFAULT_CONNECTION_ATTEMPT_TIMEOUT @preserve_fragment_url = @preserve_fragment_url_hosts.any? { |host| hostname_matches?(host) } @validate_uri = @opts.fetch(:validate_uri) { true } @user_agent = @@ -123,13 +126,7 @@ class FinalDestination status_code, response_headers = nil catch(:done) do - FinalDestination::HTTP.start( - @uri.host, - @uri.port, - use_ssl: @uri.is_a?(URI::HTTPS), - open_timeout: timeout, - ) do |http| - http.read_timeout = timeout + safe_session(@uri) do |http| http.request_get(@uri.request_uri, request_headers) do |resp| status_code = resp.code.to_i response_headers = resp.to_hash @@ -513,6 +510,7 @@ class FinalDestination uri.port, use_ssl: (uri.scheme == "https"), open_timeout: timeout, + connection_attempt_timeout: connection_attempt_timeout, ) do |http| http.read_timeout = timeout yield http diff --git a/lib/final_destination/http.rb b/lib/final_destination/http.rb index ffceff99de..0aa3d994b9 100644 --- a/lib/final_destination/http.rb +++ b/lib/final_destination/http.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class FinalDestination::HTTP < Net::HTTP + attr_accessor :connection_attempt_timeout + def connect original_open_timeout = @open_timeout return super if @ipaddr @@ -21,7 +23,7 @@ class FinalDestination::HTTP < Net::HTTP raise Net::OpenTimeout.new("Operation timed out - FinalDestination::HTTP") end - @open_timeout = remaining_time + @open_timeout = [remaining_time, connection_attempt_timeout].compact.min return super rescue SystemCallError, Net::OpenTimeout => e debug "[FinalDestination] Error connecting to #{ip}... #{e.message}" diff --git a/spec/lib/final_destination/http_spec.rb b/spec/lib/final_destination/http_spec.rb index 3388a2c835..54106628e6 100644 --- a/spec/lib/final_destination/http_spec.rb +++ b/spec/lib/final_destination/http_spec.rb @@ -95,18 +95,4 @@ describe FinalDestination::HTTP do FinalDestination::HTTP.get(URI("https://blocked-ip.example.com")) end end - - it "stops iterating over DNS records once timeout reached" do - stub_ip_lookup("example.com", %w[1.1.1.1 2.2.2.2 3.3.3.3 4.4.4.4]) - TCPSocket.stubs(:open).with { |addr| addr == "1.1.1.1" }.raises(Errno::ECONNREFUSED) - TCPSocket.stubs(:open).with { |addr| addr == "2.2.2.2" }.raises(Errno::ECONNREFUSED) - TCPSocket - .stubs(:open) - .with { |*args, **kwargs| kwargs[:open_timeout] == 0 } - .raises(Errno::ETIMEDOUT) - FinalDestination::HTTP.any_instance.stubs(:current_time).returns(0, 1, 5) - expect do - FinalDestination::HTTP.start("example.com", 80, open_timeout: 5) {} - end.to raise_error(Net::OpenTimeout) - end end