lib/httpx/resolver/native.rb in httpx-0.19.4 vs lib/httpx/resolver/native.rb in httpx-0.19.5

- old
+ new

@@ -44,10 +44,12 @@ def initialize(_, options) super @ns_index = 0 @resolver_options = DEFAULTS.merge(@options.resolver_options) @nameserver = @resolver_options[:nameserver] + @ndots = @resolver_options[:ndots] + @search = Array(@resolver_options[:search]).map { |srch| srch.scan(/[^.]+/) } @_timeouts = Array(@resolver_options[:timeouts]) @timeouts = Hash.new { |timeouts, host| timeouts[host] = @_timeouts.dup } @connections = [] @queries = {} @read_buffer = "".b @@ -134,37 +136,36 @@ def do_retry return if @queries.empty? || !@start_timeout loop_time = Utils.elapsed_time(@start_timeout) - connections = [] - queries = {} - while (query = @queries.shift) - h, connection = query - host = connection.origin.host - timeout = (@timeouts[host][0] -= loop_time) - unless timeout.negative? - queries[h] = connection - next - end - @timeouts[host].shift - if @timeouts[host].empty? - @timeouts.delete(host) - @connections.delete(connection) - # This loop_time passed to the exception is bogus. Ideally we would pass the total - # resolve timeout, including from the previous retries. - raise ResolveTimeoutError.new(loop_time, "Timed out while resolving #{host}") - # raise NativeResolveError.new(connection, host) - else - log { "resolver: timeout after #{timeout}s, retry(#{@timeouts[host].first}) #{host}..." } - connections << connection - queries[h] = connection - end + query = @queries.first + + return unless query + + h, connection = query + host = connection.origin.host + timeout = (@timeouts[host][0] -= loop_time) + + return unless timeout.negative? + + @timeouts[host].shift + if @timeouts[host].empty? + @timeouts.delete(host) + @queries.delete(h) + + return unless @queries.empty? + + @connections.delete(connection) + # This loop_time passed to the exception is bogus. Ideally we would pass the total + # resolve timeout, including from the previous retries. + raise ResolveTimeoutError.new(loop_time, "Timed out while resolving #{connection.origin.host}") + else + log { "resolver: timeout after #{timeout}s, retry(#{@timeouts[host].first}) #{host}..." } + resolve(connection) end - @queries = queries - connections.each { |ch| resolve(ch) } end def dread(wsize = @resolver_options[:packet_size]) loop do siz = @io.read(wsize, @read_buffer) @@ -192,22 +193,24 @@ rescue Resolv::DNS::DecodeError => e hostname, connection = @queries.first @queries.delete(hostname) @timeouts.delete(hostname) @connections.delete(connection) - ex = NativeResolveError.new(connection, hostname, e.message) + ex = NativeResolveError.new(connection, connection.origin.host, e.message) ex.set_backtrace(e.backtrace) raise ex end if addresses.nil? || addresses.empty? hostname, connection = @queries.first @queries.delete(hostname) @timeouts.delete(hostname) - @connections.delete(connection) - raise NativeResolveError.new(connection, hostname) + unless @queries.value?(connection) + @connections.delete(connection) + raise NativeResolveError.new(connection, connection.origin.host) + end else address = addresses.first name = address["name"] connection = @queries.delete(name) @@ -222,10 +225,13 @@ address["name"] = name connection = @queries.delete(name) end + # eliminate other candidates + @queries.delete_if { |_, conn| connection == conn } + if address.key?("alias") # CNAME # clean up intermediate queries @timeouts.delete(name) unless connection.origin.host == name if catch(:coalesced) { early_resolve(connection, hostname: address["alias"]) } @@ -254,17 +260,34 @@ hostname ||= @queries.key(connection) if hostname.nil? hostname = connection.origin.host log { "resolver: resolve IDN #{connection.origin.non_ascii_hostname} as #{hostname}" } if connection.origin.non_ascii_hostname + + hostname = generate_candidates(hostname).each do |name| + @queries[name] = connection + end.first + else + @queries[hostname] = connection end - @queries[hostname] = connection log { "resolver: query #{@record_type.name.split("::").last} for #{hostname}" } begin @write_buffer << Resolver.encode_dns_query(hostname, type: @record_type) rescue Resolv::DNS::EncodeError => e emit_resolve_error(connection, hostname, e) end + end + + def generate_candidates(name) + return [name] if name.end_with?(".") + + candidates = [] + name_parts = name.scan(/[^.]+/) + candidates = [name] if @ndots <= name_parts.size - 1 + candidates.concat(@search.map { |domain| [*name_parts, *domain].join(".") }) + candidates << name unless candidates.include?(name) + + candidates end def build_socket return if @io