Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/aiohttp/resolver.py: 31%

68 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-08 06:40 +0000

1import asyncio 

2import socket 

3from typing import Any, Dict, List, Optional, Type, Union 

4 

5from .abc import AbstractResolver 

6from .helpers import get_running_loop 

7 

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

9 

10try: 

11 import aiodns 

12 

13 # aiodns_default = hasattr(aiodns.DNSResolver, 'gethostbyname') 

14except ImportError: # pragma: no cover 

15 aiodns = None 

16 

17aiodns_default = False 

18 

19 

20class ThreadedResolver(AbstractResolver): 

21 """Threaded resolver. 

22 

23 Uses an Executor for synchronous getaddrinfo() calls. 

24 concurrent.futures.ThreadPoolExecutor is used by default. 

25 """ 

26 

27 def __init__(self, loop: Optional[asyncio.AbstractEventLoop] = None) -> None: 

28 self._loop = get_running_loop(loop) 

29 

30 async def resolve( 

31 self, hostname: str, port: int = 0, family: int = socket.AF_INET 

32 ) -> List[Dict[str, Any]]: 

33 infos = await self._loop.getaddrinfo( 

34 hostname, 

35 port, 

36 type=socket.SOCK_STREAM, 

37 family=family, 

38 flags=socket.AI_ADDRCONFIG, 

39 ) 

40 

41 hosts = [] 

42 for family, _, proto, _, address in infos: 

43 if family == socket.AF_INET6: 

44 if len(address) < 3: 

45 # IPv6 is not supported by Python build, 

46 # or IPv6 is not enabled in the host 

47 continue 

48 if address[3]: 

49 # This is essential for link-local IPv6 addresses. 

50 # LL IPv6 is a VERY rare case. Strictly speaking, we should use 

51 # getnameinfo() unconditionally, but performance makes sense. 

52 host, _port = socket.getnameinfo( 

53 address, socket.NI_NUMERICHOST | socket.NI_NUMERICSERV 

54 ) 

55 port = int(_port) 

56 else: 

57 host, port = address[:2] 

58 else: # IPv4 

59 assert family == socket.AF_INET 

60 host, port = address # type: ignore[misc] 

61 hosts.append( 

62 { 

63 "hostname": hostname, 

64 "host": host, 

65 "port": port, 

66 "family": family, 

67 "proto": proto, 

68 "flags": socket.AI_NUMERICHOST | socket.AI_NUMERICSERV, 

69 } 

70 ) 

71 

72 return hosts 

73 

74 async def close(self) -> None: 

75 pass 

76 

77 

78class AsyncResolver(AbstractResolver): 

79 """Use the `aiodns` package to make asynchronous DNS lookups""" 

80 

81 def __init__( 

82 self, 

83 loop: Optional[asyncio.AbstractEventLoop] = None, 

84 *args: Any, 

85 **kwargs: Any 

86 ) -> None: 

87 if aiodns is None: 

88 raise RuntimeError("Resolver requires aiodns library") 

89 

90 self._loop = get_running_loop(loop) 

91 self._resolver = aiodns.DNSResolver(*args, loop=loop, **kwargs) 

92 

93 if not hasattr(self._resolver, "gethostbyname"): 

94 # aiodns 1.1 is not available, fallback to DNSResolver.query 

95 self.resolve = self._resolve_with_query # type: ignore 

96 

97 async def resolve( 

98 self, host: str, port: int = 0, family: int = socket.AF_INET 

99 ) -> List[Dict[str, Any]]: 

100 try: 

101 resp = await self._resolver.gethostbyname(host, family) 

102 except aiodns.error.DNSError as exc: 

103 msg = exc.args[1] if len(exc.args) >= 1 else "DNS lookup failed" 

104 raise OSError(msg) from exc 

105 hosts = [] 

106 for address in resp.addresses: 

107 hosts.append( 

108 { 

109 "hostname": host, 

110 "host": address, 

111 "port": port, 

112 "family": family, 

113 "proto": 0, 

114 "flags": socket.AI_NUMERICHOST | socket.AI_NUMERICSERV, 

115 } 

116 ) 

117 

118 if not hosts: 

119 raise OSError("DNS lookup failed") 

120 

121 return hosts 

122 

123 async def _resolve_with_query( 

124 self, host: str, port: int = 0, family: int = socket.AF_INET 

125 ) -> List[Dict[str, Any]]: 

126 if family == socket.AF_INET6: 

127 qtype = "AAAA" 

128 else: 

129 qtype = "A" 

130 

131 try: 

132 resp = await self._resolver.query(host, qtype) 

133 except aiodns.error.DNSError as exc: 

134 msg = exc.args[1] if len(exc.args) >= 1 else "DNS lookup failed" 

135 raise OSError(msg) from exc 

136 

137 hosts = [] 

138 for rr in resp: 

139 hosts.append( 

140 { 

141 "hostname": host, 

142 "host": rr.host, 

143 "port": port, 

144 "family": family, 

145 "proto": 0, 

146 "flags": socket.AI_NUMERICHOST, 

147 } 

148 ) 

149 

150 if not hosts: 

151 raise OSError("DNS lookup failed") 

152 

153 return hosts 

154 

155 async def close(self) -> None: 

156 self._resolver.cancel() 

157 

158 

159_DefaultType = Type[Union[AsyncResolver, ThreadedResolver]] 

160DefaultResolver: _DefaultType = AsyncResolver if aiodns_default else ThreadedResolver