caresresolver.py 3.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. import pycares # type: ignore
  2. import socket
  3. from tornado.concurrent import Future
  4. from tornado import gen
  5. from tornado.ioloop import IOLoop
  6. from tornado.netutil import Resolver, is_valid_ip
  7. import typing
  8. if typing.TYPE_CHECKING:
  9. from typing import Generator, Any, List, Tuple, Dict # noqa: F401
  10. class CaresResolver(Resolver):
  11. """Name resolver based on the c-ares library.
  12. This is a non-blocking and non-threaded resolver. It may not produce the
  13. same results as the system resolver, but can be used for non-blocking
  14. resolution when threads cannot be used.
  15. ``pycares`` will not return a mix of ``AF_INET`` and ``AF_INET6`` when
  16. ``family`` is ``AF_UNSPEC``, so it is only recommended for use in
  17. ``AF_INET`` (i.e. IPv4). This is the default for
  18. ``tornado.simple_httpclient``, but other libraries may default to
  19. ``AF_UNSPEC``.
  20. .. versionchanged:: 5.0
  21. The ``io_loop`` argument (deprecated since version 4.1) has been removed.
  22. .. deprecated:: 6.2
  23. This class is deprecated and will be removed in Tornado 7.0. Use the default
  24. thread-based resolver instead.
  25. """
  26. def initialize(self) -> None:
  27. self.io_loop = IOLoop.current()
  28. self.channel = pycares.Channel(sock_state_cb=self._sock_state_cb)
  29. self.fds = {} # type: Dict[int, int]
  30. def _sock_state_cb(self, fd: int, readable: bool, writable: bool) -> None:
  31. state = (IOLoop.READ if readable else 0) | (IOLoop.WRITE if writable else 0)
  32. if not state:
  33. self.io_loop.remove_handler(fd)
  34. del self.fds[fd]
  35. elif fd in self.fds:
  36. self.io_loop.update_handler(fd, state)
  37. self.fds[fd] = state
  38. else:
  39. self.io_loop.add_handler(fd, self._handle_events, state)
  40. self.fds[fd] = state
  41. def _handle_events(self, fd: int, events: int) -> None:
  42. read_fd = pycares.ARES_SOCKET_BAD
  43. write_fd = pycares.ARES_SOCKET_BAD
  44. if events & IOLoop.READ:
  45. read_fd = fd
  46. if events & IOLoop.WRITE:
  47. write_fd = fd
  48. self.channel.process_fd(read_fd, write_fd)
  49. @gen.coroutine
  50. def resolve(
  51. self, host: str, port: int, family: int = 0
  52. ) -> "Generator[Any, Any, List[Tuple[int, Any]]]":
  53. if is_valid_ip(host):
  54. addresses = [host]
  55. else:
  56. # gethostbyname doesn't take callback as a kwarg
  57. fut = Future() # type: Future[Tuple[Any, Any]]
  58. self.channel.gethostbyname(
  59. host, family, lambda result, error: fut.set_result((result, error))
  60. )
  61. result, error = yield fut
  62. if error:
  63. raise OSError(
  64. "C-Ares returned error %s: %s while resolving %s"
  65. % (error, pycares.errno.strerror(error), host)
  66. )
  67. addresses = result.addresses
  68. addrinfo = []
  69. for address in addresses:
  70. if "." in address:
  71. address_family = socket.AF_INET
  72. elif ":" in address:
  73. address_family = socket.AF_INET6
  74. else:
  75. address_family = socket.AF_UNSPEC
  76. if family != socket.AF_UNSPEC and family != address_family:
  77. raise OSError(
  78. "Requested socket family %d but got %d" % (family, address_family)
  79. )
  80. addrinfo.append((typing.cast(int, address_family), (address, port)))
  81. return addrinfo