Merge branch 'gh-52' into 'master'

native resolver: moved timeouts reset out of idle transition, retry alias

See merge request os85/httpx!342
This commit is contained in:
HoneyryderChuck 2024-06-11 16:24:52 +00:00
commit 7062b3c49b
6 changed files with 82 additions and 23 deletions

View File

@ -65,6 +65,7 @@ module HTTPX
if nameserver && @ns_index < nameserver.size if nameserver && @ns_index < nameserver.size
log { "resolver: failed resolving on nameserver #{@nameserver[@ns_index - 1]} (#{e.message})" } log { "resolver: failed resolving on nameserver #{@nameserver[@ns_index - 1]} (#{e.message})" }
transition(:idle) transition(:idle)
@timeouts.clear
else else
handle_error(e) handle_error(e)
end end
@ -143,13 +144,16 @@ module HTTPX
if !@timeouts[host].empty? if !@timeouts[host].empty?
log { "resolver: timeout after #{timeout}s, retry(#{@timeouts[host].first}) #{host}..." } log { "resolver: timeout after #{timeout}s, retry(#{@timeouts[host].first}) #{host}..." }
resolve(connection) # must downgrade to tcp AND retry on same host as last
downgrade_socket
resolve(connection, h)
elsif @ns_index + 1 < @nameserver.size elsif @ns_index + 1 < @nameserver.size
# try on the next nameserver # try on the next nameserver
@ns_index += 1 @ns_index += 1
log { "resolver: failed resolving #{host} on nameserver #{@nameserver[@ns_index - 1]} (timeout error)" } log { "resolver: failed resolving #{host} on nameserver #{@nameserver[@ns_index - 1]} (timeout error)" }
transition(:idle) transition(:idle)
resolve(connection) @timeouts.clear
resolve(connection, h)
else else
@timeouts.delete(host) @timeouts.delete(host)
@ -187,10 +191,9 @@ module HTTPX
next unless @large_packet.full? next unless @large_packet.full?
parse(@large_packet.to_s) parse(@large_packet.to_s)
@socket_type = @resolver_options.fetch(:socket_type, :udp)
@large_packet = nil @large_packet = nil
transition(:idle) # downgrade to udp again
transition(:open) downgrade_socket
return return
else else
size = @read_buffer[0, 2].unpack1("n") size = @read_buffer[0, 2].unpack1("n")
@ -304,13 +307,21 @@ module HTTPX
end end
if address.key?("alias") # CNAME if address.key?("alias") # CNAME
hostname_alias = address["alias"]
# clean up intermediate queries # clean up intermediate queries
@timeouts.delete(name) unless connection.origin.host == name @timeouts.delete(name) unless connection.origin.host == name
if catch(:coalesced) { early_resolve(connection, hostname: address["alias"]) } if catch(:coalesced) { early_resolve(connection, hostname: hostname_alias) }
@connections.delete(connection) @connections.delete(connection)
else else
resolve(connection, address["alias"]) if @socket_type == :tcp
# must downgrade to udp if tcp
@socket_type = @resolver_options.fetch(:socket_type, :udp)
transition(:idle)
transition(:open)
end
log { "resolver: ALIAS #{hostname_alias} for #{name}" }
resolve(connection, hostname_alias)
return return
end end
else else
@ -386,6 +397,14 @@ module HTTPX
end end
end end
def downgrade_socket
return unless @socket_type == :tcp
@socket_type = @resolver_options.fetch(:socket_type, :udp)
transition(:idle)
transition(:open)
end
def transition(nextstate) def transition(nextstate)
case nextstate case nextstate
when :idle when :idle
@ -393,7 +412,6 @@ module HTTPX
@io.close @io.close
@io = nil @io = nil
end end
@timeouts.clear
when :open when :open
return unless @state == :idle return unless @state == :idle

View File

@ -59,6 +59,8 @@ module HTTPX
def build_socket: () -> (UDP | TCP) def build_socket: () -> (UDP | TCP)
def downgrade_socket: () -> void
def transition: (Symbol nextstate) -> void def transition: (Symbol nextstate) -> void
def handle_error: (NativeResolveError | StandardError) -> void def handle_error: (NativeResolveError | StandardError) -> void

View File

@ -99,8 +99,8 @@ module ResponseHelpers
File.join("test", "support", "fixtures", fixture_file_name) File.join("test", "support", "fixtures", fixture_file_name)
end end
def start_test_servlet(servlet_class, *args) def start_test_servlet(servlet_class, *args, **kwargs)
server = servlet_class.new(*args) server = servlet_class.new(*args, **kwargs)
th = Thread.new { server.start } th = Thread.new { server.start }
begin begin
yield server yield server

View File

@ -131,7 +131,7 @@ module Requests
uri = build_uri("/get") uri = build_uri("/get")
before_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :second) before_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :second)
response = session.get(uri, resolver_class: resolver_type, resolver_options: resolver_opts) response = session.get(uri, resolver_class: resolver_type, resolver_options: options.merge(resolver_opts))
after_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :second) after_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :second)
total_time = after_time - before_time total_time = after_time - before_time
@ -160,7 +160,7 @@ module Requests
HTTPX.plugin(SessionWithPool).wrap do |session| HTTPX.plugin(SessionWithPool).wrap do |session|
uri = build_uri("/get") uri = build_uri("/get")
response = session.get(uri, resolver_class: resolver_type, resolver_options: resolver_opts) response = session.get(uri, resolver_class: resolver_type, resolver_options: options.merge(resolver_opts))
verify_status(response, 200) verify_status(response, 200)
resolver = session.pool.resolver.resolvers[0] resolver = session.pool.resolver.resolvers[0]
@ -180,7 +180,7 @@ module Requests
HTTPX.plugin(SessionWithPool).wrap do |session| HTTPX.plugin(SessionWithPool).wrap do |session|
uri = build_uri("/get") uri = build_uri("/get")
response = session.get(uri, resolver_class: resolver_type, resolver_options: resolver_opts) response = session.get(uri, resolver_class: resolver_type, resolver_options: options.merge(resolver_opts))
verify_error_response(response, /unknown DNS error/) verify_error_response(response, /unknown DNS error/)
end end
end end
@ -231,6 +231,8 @@ module Requests
attr_reader :ios attr_reader :ios
end end
private
def build_socket def build_socket
io = super io = super
self.class.ios << io self.class.ios << io

View File

@ -6,15 +6,30 @@ require_relative "test"
# from https://gist.github.com/peterc/1425383 # from https://gist.github.com/peterc/1425383
class SlowDNSServer < TestDNSResolver class SlowDNSServer < TestDNSResolver
def initialize(timeout) def initialize(timeout, *args, hostname: nil, als: nil)
@timeout = timeout @timeout = timeout
super() @hostname = hostname
@alias = als
super(*args)
end end
private private
def dns_response(*) def dns_response(query)
if @alias
domain = extract_domain(query)
sleep(@timeout) if domain == @alias
else
sleep(@timeout) sleep(@timeout)
end
super super
end end
def resolve(domain)
if domain == "#{@hostname}."
@alias
else
super
end
end
end end

View File

@ -116,11 +116,12 @@ end
class TestDNSResolver class TestDNSResolver
attr_reader :queries, :answers attr_reader :queries, :answers
def initialize def initialize(port = next_available_port, socket_type = :udp)
@port = next_available_port @port = port
@can_log = ENV.key?("HTTPX_DEBUG") @can_log = ENV.key?("HTTPX_DEBUG")
@queries = 0 @queries = 0
@answers = 0 @answers = 0
@socket_type = socket_type
end end
def nameserver def nameserver
@ -128,11 +129,32 @@ class TestDNSResolver
end end
def start def start
if @socket_type == :udp
Socket.udp_server_loop(@port) do |query, src| Socket.udp_server_loop(@port) do |query, src|
puts "bang bang"
@queries += 1 @queries += 1
src.reply(dns_response(query)) src.reply(dns_response(query))
@answers += 1 @answers += 1
end end
elsif @socket_type == :tcp
Socket.tcp_server_loop(@port) do |sock, _addrinfo|
begin
loop do
query = sock.readpartial(2048)
size = query[0, 2].unpack1("n")
query = query.byteslice(2..-1)
query << sock.readpartial(size - query.size) while query.size < size
@queries += 1
answer = dns_response(query)
answer.prepend([answer.size].pack("n"))
sock.write(answer)
@answers += 1
end
rescue EOFError
end
end
end
end end
private private