twisted.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. # Author: Ovidiu Predescu
  2. # Date: July 2011
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  5. # not use this file except in compliance with the License. You may obtain
  6. # a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. # License for the specific language governing permissions and limitations
  14. # under the License.
  15. """Bridges between the Twisted reactor and Tornado IOLoop.
  16. This module lets you run applications and libraries written for
  17. Twisted in a Tornado application. It can be used in two modes,
  18. depending on which library's underlying event loop you want to use.
  19. This module has been tested with Twisted versions 11.0.0 and newer.
  20. """
  21. import socket
  22. import sys
  23. import twisted.internet.abstract # type: ignore
  24. import twisted.internet.asyncioreactor # type: ignore
  25. from twisted.internet.defer import Deferred # type: ignore
  26. from twisted.python import failure # type: ignore
  27. import twisted.names.cache # type: ignore
  28. import twisted.names.client # type: ignore
  29. import twisted.names.hosts # type: ignore
  30. import twisted.names.resolve # type: ignore
  31. from tornado.concurrent import Future, future_set_exc_info
  32. from tornado.escape import utf8
  33. from tornado import gen
  34. from tornado.netutil import Resolver
  35. import typing
  36. if typing.TYPE_CHECKING:
  37. from typing import Generator, Any, List, Tuple # noqa: F401
  38. class TwistedResolver(Resolver):
  39. """Twisted-based asynchronous resolver.
  40. This is a non-blocking and non-threaded resolver. It is
  41. recommended only when threads cannot be used, since it has
  42. limitations compared to the standard ``getaddrinfo``-based
  43. `~tornado.netutil.Resolver` and
  44. `~tornado.netutil.DefaultExecutorResolver`. Specifically, it returns at
  45. most one result, and arguments other than ``host`` and ``family``
  46. are ignored. It may fail to resolve when ``family`` is not
  47. ``socket.AF_UNSPEC``.
  48. Requires Twisted 12.1 or newer.
  49. .. versionchanged:: 5.0
  50. The ``io_loop`` argument (deprecated since version 4.1) has been removed.
  51. """
  52. def initialize(self) -> None:
  53. # partial copy of twisted.names.client.createResolver, which doesn't
  54. # allow for a reactor to be passed in.
  55. self.reactor = twisted.internet.asyncioreactor.AsyncioSelectorReactor()
  56. host_resolver = twisted.names.hosts.Resolver("/etc/hosts")
  57. cache_resolver = twisted.names.cache.CacheResolver(reactor=self.reactor)
  58. real_resolver = twisted.names.client.Resolver(
  59. "/etc/resolv.conf", reactor=self.reactor
  60. )
  61. self.resolver = twisted.names.resolve.ResolverChain(
  62. [host_resolver, cache_resolver, real_resolver]
  63. )
  64. @gen.coroutine
  65. def resolve(
  66. self, host: str, port: int, family: int = 0
  67. ) -> "Generator[Any, Any, List[Tuple[int, Any]]]":
  68. # getHostByName doesn't accept IP addresses, so if the input
  69. # looks like an IP address just return it immediately.
  70. if twisted.internet.abstract.isIPAddress(host):
  71. resolved = host
  72. resolved_family = socket.AF_INET
  73. elif twisted.internet.abstract.isIPv6Address(host):
  74. resolved = host
  75. resolved_family = socket.AF_INET6
  76. else:
  77. deferred = self.resolver.getHostByName(utf8(host))
  78. fut = Future() # type: Future[Any]
  79. deferred.addBoth(fut.set_result)
  80. resolved = yield fut
  81. if isinstance(resolved, failure.Failure):
  82. try:
  83. resolved.raiseException()
  84. except twisted.names.error.DomainError as e:
  85. raise IOError(e)
  86. elif twisted.internet.abstract.isIPAddress(resolved):
  87. resolved_family = socket.AF_INET
  88. elif twisted.internet.abstract.isIPv6Address(resolved):
  89. resolved_family = socket.AF_INET6
  90. else:
  91. resolved_family = socket.AF_UNSPEC
  92. if family != socket.AF_UNSPEC and family != resolved_family:
  93. raise Exception(
  94. "Requested socket family %d but got %d" % (family, resolved_family)
  95. )
  96. result = [(typing.cast(int, resolved_family), (resolved, port))]
  97. return result
  98. if hasattr(gen.convert_yielded, "register"):
  99. @gen.convert_yielded.register(Deferred) # type: ignore
  100. def _(d: Deferred) -> Future:
  101. f = Future() # type: Future[Any]
  102. def errback(failure: failure.Failure) -> None:
  103. try:
  104. failure.raiseException()
  105. # Should never happen, but just in case
  106. raise Exception("errback called without error")
  107. except:
  108. future_set_exc_info(f, sys.exc_info())
  109. d.addCallbacks(f.set_result, errback)
  110. return f