Module: HTTPX::Resolver

Defined in:
lib/httpx/resolver.rb,
lib/httpx/resolver/entry.rb

Defined Under Namespace

Classes: Entry, HTTPS, Multi, Native, Resolver, System

Constant Summary collapse

RESOLVE_TIMEOUT =
[2, 3].freeze

Class Method Summary collapse

Class Method Details

.cached_lookup(hostname) ⇒ Object



73
74
75
76
77
78
# File 'lib/httpx/resolver.rb', line 73

def cached_lookup(hostname)
  now = Utils.now
  lookup_synchronize do |lookups|
    lookup(hostname, lookups, now)
  end
end

.cached_lookup_evict(hostname, ip) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
# File 'lib/httpx/resolver.rb', line 101

def cached_lookup_evict(hostname, ip)
  ip = ip.to_s

  lookup_synchronize do |lookups|
    entries = lookups[hostname]

    return unless entries

    lookups.delete_if { |entry| entry["data"] == ip }
  end
end

.cached_lookup_set(hostname, family, entries) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/httpx/resolver.rb', line 80

def cached_lookup_set(hostname, family, entries)
  lookup_synchronize do |lookups|
    case family
    when Socket::AF_INET6
      lookups[hostname].concat(entries)
    when Socket::AF_INET
      lookups[hostname].unshift(*entries)
    end
    entries.each do |entry|
      next unless entry["name"] != hostname

      case family
      when Socket::AF_INET6
        lookups[entry["name"]] << entry
      when Socket::AF_INET
        lookups[entry["name"]].unshift(entry)
      end
    end
  end
end

.decode_dns_answer(payload) ⇒ Object



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/httpx/resolver.rb', line 143

def decode_dns_answer(payload)
  begin
    message = Resolv::DNS::Message.decode(payload)
  rescue Resolv::DNS::DecodeError => e
    return :decode_error, e
  end

  # no domain was found
  return :no_domain_found if message.rcode == Resolv::DNS::RCode::NXDomain

  return :message_truncated if message.tc == 1

  return :dns_error, message.rcode if message.rcode != Resolv::DNS::RCode::NoError

  addresses = []

  now = Utils.now
  message.each_answer do |question, _, value|
    case value
    when Resolv::DNS::Resource::IN::CNAME
      addresses << {
        "name" => question.to_s,
        "TTL" => (now + value.ttl),
        "alias" => value.name.to_s,
      }
    when Resolv::DNS::Resource::IN::A,
         Resolv::DNS::Resource::IN::AAAA
      addresses << {
        "name" => question.to_s,
        "TTL" => (now + value.ttl),
        "data" => value.address.to_s,
      }
    end
  end

  [:ok, addresses]
end

.encode_dns_query(hostname, type: Resolv::DNS::Resource::IN::A, message_id: generate_id) ⇒ Object



136
137
138
139
140
141
# File 'lib/httpx/resolver.rb', line 136

def encode_dns_query(hostname, type: Resolv::DNS::Resource::IN::A, message_id: generate_id)
  Resolv::DNS::Message.new(message_id).tap do |query|
    query.rd = 1
    query.add_question(hostname, type)
  end.encode
end

.generate_idObject



132
133
134
# File 'lib/httpx/resolver.rb', line 132

def generate_id
  id_synchronize { @identifier = (@identifier + 1) & 0xFFFF }
end

.hosts_resolve(hostname) ⇒ Object

matches hostname to entries in the hosts file, returns <tt>nil</nil> if none is found, or there is no hosts file.



65
66
67
68
69
70
71
# File 'lib/httpx/resolver.rb', line 65

def hosts_resolve(hostname)
  ips = @hosts_resolver.getaddresses(hostname)
  return if ips.empty?

  ips.map { |ip| Entry.new(ip) }
rescue IOError
end

.id_synchronize(&block) ⇒ Object



185
186
187
# File 'lib/httpx/resolver.rb', line 185

def id_synchronize(&block)
  @identifier_mutex.synchronize(&block)
end

.ip_resolve(hostname) ⇒ Object

tries to convert hostname into an IPAddr, returns nil otherwise.



58
59
60
61
# File 'lib/httpx/resolver.rb', line 58

def ip_resolve(hostname)
  [Entry.new(hostname)]
rescue ArgumentError
end

.lookup(hostname, lookups, ttl) ⇒ Object

do not use directly!



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/httpx/resolver.rb', line 114

def lookup(hostname, lookups, ttl)
  return unless lookups.key?(hostname)

  entries = lookups[hostname] = lookups[hostname].select do |address|
    address["TTL"] > ttl
  end

  ips = entries.flat_map do |address|
    if (als = address["alias"])
      lookup(als, lookups, ttl)
    else
      Entry.new(address["data"], address["TTL"])
    end
  end.compact

  ips unless ips.empty?
end

.lookup_synchronizeObject



181
182
183
# File 'lib/httpx/resolver.rb', line 181

def lookup_synchronize
  @lookup_mutex.synchronize { yield(@lookups) }
end

.nolookup_resolve(hostname) ⇒ Object



53
54
55
# File 'lib/httpx/resolver.rb', line 53

def nolookup_resolve(hostname)
  ip_resolve(hostname) || cached_lookup(hostname) || hosts_resolve(hostname)
end

.resolver_for(resolver_type, options) ⇒ Object

Raises:



40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/httpx/resolver.rb', line 40

def resolver_for(resolver_type, options)
  case resolver_type
  when Symbol
    meth = :"resolver_#{resolver_type}_class"

    return options.__send__(meth) if options.respond_to?(meth)
  when Class
    return resolver_type if resolver_type < Resolver
  end

  raise Error, "unsupported resolver type (#{resolver_type})"
end

.supported_ip_familiesObject



26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/httpx/resolver.rb', line 26

def supported_ip_families
  @supported_ip_families ||= begin
    # https://github.com/ruby/resolv/blob/095f1c003f6073730500f02acbdbc55f83d70987/lib/resolv.rb#L408
    list = Socket.ip_address_list
    if list.any? { |a| a.ipv6? && !a.ipv6_loopback? && !a.ipv6_linklocal? }
      [Socket::AF_INET6, Socket::AF_INET]
    else
      [Socket::AF_INET]
    end
  rescue NotImplementedError
    [Socket::AF_INET]
  end.freeze
end