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