Module: HTTPX::Resolver

Extended by:
Resolver
Included in:
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
MAX_CACHE_SIZE =
512

Instance Method Summary collapse

Instance Method Details

#cached_lookup(hostname) ⇒ Object



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

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

#cached_lookup_evict(hostname, ip) ⇒ Object



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/httpx/resolver.rb', line 110

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

  lookup_synchronize do |lookups, hostnames|
    entries = lookups[hostname]

    return unless entries

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

    if entries.empty?
      lookups.delete(hostname)
      hostnames.delete(hostname)
    end
  end
end

#cached_lookup_set(hostname, family, entries) ⇒ Object



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/httpx/resolver.rb', line 81

def cached_lookup_set(hostname, family, entries)
  lookup_synchronize do |lookups, hostnames|
    # lru cleanup
    while lookups.size >= MAX_CACHE_SIZE
      hs = hostnames.shift
      lookups.delete(hs)
    end
    hostnames << hostname

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

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

#decode_dns_answer(payload) ⇒ Object



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/httpx/resolver.rb', line 169

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

  if message.rcode != Resolv::DNS::RCode::NoError
    case message.rcode
    when Resolv::DNS::RCode::ServFail
      return :retriable_error, message.rcode
    else
      return :dns_error, message.rcode
    end
  end

  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



162
163
164
165
166
167
# File 'lib/httpx/resolver.rb', line 162

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



153
154
155
156
157
158
159
160
# File 'lib/httpx/resolver.rb', line 153

def generate_id
  if in_ractor?
    identifier = Ractor.store_if_absent(:httpx_resolver_identifier) { -1 }
    Ractor.current[:httpx_resolver_identifier] = (identifier + 1) & 0xFFFF
  else
    id_synchronize { @identifier = (@identifier + 1) & 0xFFFF }
  end
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.



61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/httpx/resolver.rb', line 61

def hosts_resolve(hostname)
  ips = if in_ractor?
    Ractor.store_if_absent(:httpx_hosts_resolver) { Resolv::Hosts.new }
  else
    @hosts_resolver
  end.getaddresses(hostname)

  return if ips.empty?

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

#ip_resolve(hostname) ⇒ Object

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



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

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

#lookup(hostname, lookups, hostnames, ttl) ⇒ Object

do not use directly!



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/httpx/resolver.rb', line 128

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

  entries = lookups[hostname]

  entries.delete_if do |address|
    address["TTL"] < ttl
  end

  if entries.empty?
    lookups.delete(hostname)
    hostnames.delete(hostname)
  end

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

  ips unless ips.empty?
end

#nolookup_resolve(hostname) ⇒ Object



49
50
51
# File 'lib/httpx/resolver.rb', line 49

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

#resolver_for(resolver_type, options) ⇒ Object

Raises:



36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/httpx/resolver.rb', line 36

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



28
29
30
31
32
33
34
# File 'lib/httpx/resolver.rb', line 28

def supported_ip_families
  if in_ractor?
    Ractor.store_if_absent(:httpx_supported_ip_families) { find_supported_ip_families }
  else
    @supported_ip_families ||= find_supported_ip_families
  end
end