Coverage for /pythoncovmergedfiles/medio/medio/src/aiohttp/aiohttp/resolver.py: 26%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import asyncio
2import socket
3import sys
4import weakref
5from typing import Any, Optional
7from .abc import AbstractResolver, ResolveResult
9__all__ = ("ThreadedResolver", "AsyncResolver", "DefaultResolver")
12try:
13 import aiodns
15 aiodns_default = hasattr(aiodns.DNSResolver, "getaddrinfo")
16except ImportError:
17 aiodns = None # type: ignore[assignment]
18 aiodns_default = False
21_NUMERIC_SOCKET_FLAGS = socket.AI_NUMERICHOST | socket.AI_NUMERICSERV
22_NAME_SOCKET_FLAGS = socket.NI_NUMERICHOST | socket.NI_NUMERICSERV
23_AI_ADDRCONFIG = socket.AI_ADDRCONFIG
24if hasattr(socket, "AI_MASK"):
25 _AI_ADDRCONFIG &= socket.AI_MASK
26_IS_WINDOWS = sys.platform == "win32"
29def _is_windows_localhost(host: str) -> bool:
30 return _IS_WINDOWS and host.rstrip(".").casefold() == "localhost"
33class ThreadedResolver(AbstractResolver):
34 """Threaded resolver.
36 Uses an Executor for synchronous getaddrinfo() calls.
37 concurrent.futures.ThreadPoolExecutor is used by default.
38 """
40 def __init__(self) -> None:
41 self._loop = asyncio.get_running_loop()
43 async def resolve(
44 self, host: str, port: int = 0, family: socket.AddressFamily = socket.AF_INET
45 ) -> list[ResolveResult]:
46 try:
47 infos = await self._loop.getaddrinfo(
48 host,
49 port,
50 type=socket.SOCK_STREAM,
51 family=family,
52 flags=_AI_ADDRCONFIG,
53 )
54 except socket.gaierror:
55 if not _is_windows_localhost(host):
56 raise
57 infos = await self._loop.getaddrinfo(
58 host,
59 port,
60 type=socket.SOCK_STREAM,
61 family=family,
62 flags=0,
63 )
65 hosts: list[ResolveResult] = []
66 for family, _, proto, _, address in infos:
67 if family == socket.AF_INET6:
68 if len(address) < 3:
69 # IPv6 is not supported by Python build,
70 # or IPv6 is not enabled in the host
71 continue
72 if address[3]:
73 # This is essential for link-local IPv6 addresses.
74 # LL IPv6 is a VERY rare case. Strictly speaking, we should use
75 # getnameinfo() unconditionally, but performance makes sense.
76 resolved_host, _port = await self._loop.getnameinfo(
77 address, _NAME_SOCKET_FLAGS
78 )
79 port = int(_port)
80 else:
81 resolved_host, port = address[:2]
82 else: # IPv4
83 assert family == socket.AF_INET
84 resolved_host, port = address # type: ignore[misc]
85 hosts.append(
86 ResolveResult(
87 hostname=host,
88 host=resolved_host,
89 port=port,
90 family=family,
91 proto=proto,
92 flags=_NUMERIC_SOCKET_FLAGS,
93 )
94 )
96 return hosts
98 async def close(self) -> None:
99 pass
102class AsyncResolver(AbstractResolver):
103 """Use the `aiodns` package to make asynchronous DNS lookups"""
105 def __init__(self, *args: Any, **kwargs: Any) -> None:
106 if aiodns is None:
107 raise RuntimeError("Resolver requires aiodns library")
109 self._loop = asyncio.get_running_loop()
110 self._manager: _DNSResolverManager | None = None
111 # If custom args are provided, create a dedicated resolver instance
112 # This means each AsyncResolver with custom args gets its own
113 # aiodns.DNSResolver instance
114 if args or kwargs:
115 self._resolver = aiodns.DNSResolver(*args, **kwargs)
116 return
117 # Use the shared resolver from the manager for default arguments
118 self._manager = _DNSResolverManager()
119 self._resolver = self._manager.get_resolver(self, self._loop)
121 async def resolve(
122 self, host: str, port: int = 0, family: socket.AddressFamily = socket.AF_INET
123 ) -> list[ResolveResult]:
124 try:
125 try:
126 resp = await self._resolver.getaddrinfo(
127 host,
128 port=port,
129 type=socket.SOCK_STREAM,
130 family=family,
131 flags=_AI_ADDRCONFIG,
132 )
133 except aiodns.error.DNSError:
134 if not _is_windows_localhost(host):
135 raise
136 resp = await self._resolver.getaddrinfo(
137 host,
138 port=port,
139 type=socket.SOCK_STREAM,
140 family=family,
141 flags=0,
142 )
143 except aiodns.error.DNSError as exc:
144 msg = exc.args[1] if len(exc.args) >= 1 else "DNS lookup failed"
145 raise OSError(None, msg) from exc
146 hosts: list[ResolveResult] = []
147 for node in resp.nodes:
148 address: tuple[bytes, int] | tuple[bytes, int, int, int] = node.addr
149 if node.family == socket.AF_INET6:
150 if len(address) > 3 and address[3]:
151 # This is essential for link-local IPv6 addresses.
152 # LL IPv6 is a VERY rare case. Strictly speaking, we should use
153 # getnameinfo() unconditionally, but performance makes sense.
154 result = await self._resolver.getnameinfo(
155 (address[0].decode("ascii"), *address[1:]),
156 _NAME_SOCKET_FLAGS,
157 )
158 resolved_host = result.node
159 else:
160 resolved_host = address[0].decode("ascii")
161 port = address[1]
162 else: # IPv4
163 assert node.family == socket.AF_INET
164 resolved_host = address[0].decode("ascii")
165 port = address[1]
166 hosts.append(
167 ResolveResult(
168 hostname=host,
169 host=resolved_host,
170 port=port,
171 family=node.family,
172 proto=0,
173 flags=_NUMERIC_SOCKET_FLAGS,
174 )
175 )
177 if not hosts:
178 raise OSError(None, "DNS lookup failed")
180 return hosts
182 async def close(self) -> None:
183 if self._manager:
184 # Release the resolver from the manager if using the shared resolver
185 self._manager.release_resolver(self, self._loop)
186 self._manager = None # Clear reference to manager
187 self._resolver = None # type: ignore[assignment] # Clear reference to resolver
188 return
189 # Otherwise cancel our dedicated resolver
190 if self._resolver is not None:
191 self._resolver.cancel()
192 self._resolver = None # type: ignore[assignment] # Clear reference
195class _DNSResolverManager:
196 """Manager for aiodns.DNSResolver objects.
198 This class manages shared aiodns.DNSResolver instances
199 with no custom arguments across different event loops.
200 """
202 _instance: Optional["_DNSResolverManager"] = None
204 def __new__(cls) -> "_DNSResolverManager":
205 if cls._instance is None:
206 cls._instance = super().__new__(cls)
207 cls._instance._init()
208 return cls._instance
210 def _init(self) -> None:
211 # Use WeakKeyDictionary to allow event loops to be garbage collected
212 self._loop_data: weakref.WeakKeyDictionary[
213 asyncio.AbstractEventLoop,
214 tuple[aiodns.DNSResolver, weakref.WeakSet[AsyncResolver]],
215 ] = weakref.WeakKeyDictionary()
217 def get_resolver(
218 self, client: "AsyncResolver", loop: asyncio.AbstractEventLoop
219 ) -> "aiodns.DNSResolver":
220 """Get or create the shared aiodns.DNSResolver instance for a specific event loop.
222 Args:
223 client: The AsyncResolver instance requesting the resolver.
224 This is required to track resolver usage.
225 loop: The event loop to use for the resolver.
226 """
227 # Create a new resolver and client set for this loop if it doesn't exist
228 if loop not in self._loop_data:
229 resolver = aiodns.DNSResolver(loop=loop)
230 client_set: weakref.WeakSet[AsyncResolver] = weakref.WeakSet()
231 self._loop_data[loop] = (resolver, client_set)
232 else:
233 # Get the existing resolver and client set
234 resolver, client_set = self._loop_data[loop]
236 # Register this client with the loop
237 client_set.add(client)
238 return resolver
240 def release_resolver(
241 self, client: "AsyncResolver", loop: asyncio.AbstractEventLoop
242 ) -> None:
243 """Release the resolver for an AsyncResolver client when it's closed.
245 Args:
246 client: The AsyncResolver instance to release.
247 loop: The event loop the resolver was using.
248 """
249 # Remove client from its loop's tracking
250 current_loop_data = self._loop_data.get(loop)
251 if current_loop_data is None:
252 return
253 resolver, client_set = current_loop_data
254 client_set.discard(client)
255 # If no more clients for this loop, cancel and remove its resolver
256 if not client_set:
257 if resolver is not None:
258 resolver.cancel()
259 del self._loop_data[loop]
262_DefaultType = type[AsyncResolver | ThreadedResolver]
263DefaultResolver: _DefaultType = AsyncResolver if aiodns_default else ThreadedResolver