caresresolver.py 3.0 KB

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