aio-libs / aiohttp
1 10
import asyncio
2 10
import socket
3 10
from typing import Any, Dict, List, Type, Union
4

5 10
from .abc import AbstractResolver
6

7 10
__all__ = ("ThreadedResolver", "AsyncResolver", "DefaultResolver")
8

9 10
try:
10 10
    import aiodns
11

12
    # aiodns_default = hasattr(aiodns.DNSResolver, 'gethostbyname')
13
except ImportError:  # pragma: no cover
14
    aiodns = None
15

16 10
aiodns_default = False
17

18

19 10
class ThreadedResolver(AbstractResolver):
20
    """Use Executor for synchronous getaddrinfo() calls, which defaults to
21
    concurrent.futures.ThreadPoolExecutor.
22
    """
23

24 10
    def __init__(self) -> None:
25 10
        self._loop = asyncio.get_running_loop()
26

27 10
    async def resolve(
28
        self, hostname: str, port: int = 0, family: int = socket.AF_INET
29
    ) -> List[Dict[str, Any]]:
30 10
        infos = await self._loop.getaddrinfo(
31
            hostname,
32
            port,
33
            type=socket.SOCK_STREAM,
34
            family=family,
35
            flags=socket.AI_ADDRCONFIG,
36
        )
37

38 10
        hosts = []
39 10
        for family, _, proto, _, address in infos:
40 10
            if family == socket.AF_INET6 and address[3]:  # type: ignore[misc]
41
                # This is essential for link-local IPv6 addresses.
42
                # LL IPv6 is a VERY rare case. Strictly speaking, we should use
43
                # getnameinfo() unconditionally, but performance makes sense.
44 0
                host, _port = socket.getnameinfo(
45
                    address, socket.NI_NUMERICHOST | socket.NI_NUMERICSERV
46
                )
47 0
                port = int(_port)
48
            else:
49 10
                host, port = address[:2]
50 10
            hosts.append(
51
                {
52
                    "hostname": hostname,
53
                    "host": host,
54
                    "port": port,
55
                    "family": family,
56
                    "proto": proto,
57
                    "flags": socket.AI_NUMERICHOST | socket.AI_NUMERICSERV,
58
                }
59
            )
60

61 10
        return hosts
62

63 10
    async def close(self) -> None:
64 10
        pass
65

66

67 10
class AsyncResolver(AbstractResolver):
68
    """Use the `aiodns` package to make asynchronous DNS lookups"""
69

70 10
    def __init__(self, *args: Any, **kwargs: Any) -> None:
71 10
        if aiodns is None:
72 10
            raise RuntimeError("Resolver requires aiodns library")
73

74 7
        self._loop = asyncio.get_running_loop()
75 7
        self._resolver = aiodns.DNSResolver(*args, loop=self._loop, **kwargs)
76

77 10
    async def resolve(
78
        self, host: str, port: int = 0, family: int = socket.AF_INET
79
    ) -> List[Dict[str, Any]]:
80 7
        try:
81 7
            resp = await self._resolver.gethostbyname(host, family)
82 7
        except aiodns.error.DNSError as exc:
83 7
            msg = exc.args[1] if len(exc.args) >= 1 else "DNS lookup failed"
84 7
            raise OSError(msg) from exc
85 7
        hosts = []
86 10
        for address in resp.addresses:
87 7
            hosts.append(
88
                {
89
                    "hostname": host,
90
                    "host": address,
91
                    "port": port,
92
                    "family": family,
93
                    "proto": 0,
94
                    "flags": socket.AI_NUMERICHOST | socket.AI_NUMERICSERV,
95
                }
96
            )
97

98 10
        if not hosts:
99 7
            raise OSError("DNS lookup failed")
100

101 7
        return hosts
102

103 10
    async def close(self) -> None:
104 7
        self._resolver.cancel()
105

106

107 10
_DefaultType = Type[Union[AsyncResolver, ThreadedResolver]]
108 10
DefaultResolver: _DefaultType = AsyncResolver if aiodns_default else ThreadedResolver

Read our documentation on viewing source code .

Loading