Source code for uvhttp.dns

import aiodns
import asyncio
import random
import socket
import time
import uvhttp.utils

class DNSError(Exception):
    pass

[docs]class Resolver: """ Caching DNS resolver wrapper for aiodns. """ def __init__(self, loop, ipv6=True, nameservers=None): """ If ``ipv6`` is true, the resolver will prefer IPv6. """ self.loop = loop self.resolver = aiodns.DNSResolver(loop=self.loop, nameservers=nameservers) self.cached = {} self.ipv6 = ipv6
[docs] def add_to_cache(self, host, host_port, ip, ttl, port=80, overwrite=True): """ Add the address pair ``host`` and ``host_port`` to the DNS cache pointing to ``ip`` and ``port``. The result will be cached for ``ttl`` (or forever if ``ttl`` is 0). If ``overwrite`` is true, the result will overwrite any previous entries, otherwise it will be appended. """ addr_pair = (host, host_port) if ttl: expires = time.time() + ttl else: expires = 9999999999999 if overwrite or addr_pair not in self.cached: self.cached[addr_pair] = [(ip, port, expires)] else: self.cached[addr_pair].append((ip, port, expires))
[docs] def fetch_from_cache(self, host, host_port): """ Retrieve the cached entry for the ``host`` and ``host_port`` address pair. Returns ``None`` if there are no cached entries. """ addr_pair = (host, host_port) if addr_pair not in self.cached: return self.filter_expired(addr_pair) if self.cached[addr_pair]: return random.choice(self.cached[addr_pair])
[docs] def filter_expired(self, addr_pair): """ Remove expired entries from a cached address pair. """ now = time.time() self.cached[addr_pair] = list(filter(lambda c: c[2] > now, self.cached[addr_pair]))
[docs] async def resolve(self, host, port): """ Resolve ``host`` and ``port`` to its IP and port. """ if uvhttp.utils.is_ip(host): return (host, port) cached = self.fetch_from_cache(host, port) if cached: return cached if self.ipv6: query_types = ['AAAA', 'A'] else: query_types = ['A'] for query_type in query_types: try: responses = await self.resolver.query(host, query_type) except aiodns.error.DNSError as e: pass else: # sometimes the resolver returns an empty list and I don't know why. if not responses: continue for response in responses: self.add_to_cache(host, port, response.host, response.ttl, port=port) break response = self.fetch_from_cache(host, port) if not response: raise DNSError() else: return response