]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Add TwistedResolver for non-threaded async DNS resolution.
authorBen Darnell <ben@bendarnell.com>
Sat, 23 Feb 2013 18:15:04 +0000 (13:15 -0500)
committerBen Darnell <ben@bendarnell.com>
Sat, 23 Feb 2013 20:19:04 +0000 (15:19 -0500)
tornado/platform/twisted.py
tox.ini

index 34e108d79866664b4f4520412266cc2d976ae121..d0d12aede20b3e8cd59a42b67947bf779ed6f0e0 100644 (file)
@@ -66,20 +66,29 @@ This module has been tested with Twisted versions 11.0.0 and newer.
 
 from __future__ import absolute_import, division, print_function, with_statement
 
-import functools
 import datetime
+import functools
+import socket
 
+import twisted.internet.abstract
 from twisted.internet.posixbase import PosixReactorBase
 from twisted.internet.interfaces import \
     IReactorFDSet, IDelayedCall, IReactorTime, IReadDescriptor, IWriteDescriptor
 from twisted.python import failure, log
 from twisted.internet import error
+import twisted.names.cache
+import twisted.names.client
+import twisted.names.hosts
+import twisted.names.resolve
 
 from zope.interface import implementer
 
-import tornado
+from tornado.concurrent import return_future
+from tornado.escape import utf8
+from tornado import gen
 import tornado.ioloop
 from tornado.log import app_log
+from tornado.netutil import Resolver
 from tornado.stack_context import NullContext, wrap
 from tornado.ioloop import IOLoop
 
@@ -470,3 +479,58 @@ class TwistedIOLoop(tornado.ioloop.IOLoop):
 
     def add_callback_from_signal(self, callback, *args, **kwargs):
         self.add_callback(callback, *args, **kwargs)
+
+
+class TwistedResolver(Resolver):
+    """Twisted-based asynchronous resolver.
+
+    This is a non-blocking and non-threaded resolver.  It is
+    recommended only when threads cannot be used, since it has
+    limitations compared to the standard ``getaddrinfo``-based
+    `~tornado.netutil.Resolver` and
+    `~tornado.netutil.ThreadedResolver`.  Specifically, it returns at
+    most one result, and arguments other than ``host`` and ``family``
+    are ignored.  It may fail to resolve when ``family`` is not
+    ``socket.AF_UNSPEC``.
+    """
+    def initialize(self, io_loop):
+        self.io_loop = io_loop
+        # partial copy of twisted.names.client.createResolver, which doesn't
+        # allow for a reactor to be passed in.
+        self.reactor = tornado.platform.twisted.TornadoReactor(io_loop)
+
+        host_resolver = twisted.names.hosts.Resolver('/etc/hosts')
+        cache_resolver = twisted.names.cache.CacheResolver(reactor=self.reactor)
+        real_resolver = twisted.names.client.Resolver('/etc/resolv.conf',
+                                                      reactor=self.reactor)
+        self.resolver = twisted.names.resolve.ResolverChain(
+            [host_resolver, cache_resolver, real_resolver])
+
+    @return_future
+    @gen.engine
+    def getaddrinfo(self, host, port, family=0, socktype=0, proto=0,
+                    flags=0, callback=None):
+        # getHostByName doesn't accept IP addresses, so if the input
+        # looks like an IP address just return it immediately.
+        if twisted.internet.abstract.isIPAddress(host):
+            resolved = host
+            resolved_family = socket.AF_INET
+        elif twisted.internet.abstract.isIPv6Address(host):
+            resolved = host
+            resolved_family = socket.AF_INET6
+        else:
+            deferred = self.resolver.getHostByName(utf8(host))
+            resolved = yield gen.Task(deferred.addCallback)
+            if twisted.internet.abstract.isIPAddress(resolved):
+                resolved_family = socket.AF_INET
+            elif twisted.internet.abstract.isIPv6Address(resolved):
+                resolved_family = socket.AF_INET6
+            else:
+                resolved_family = socket.AF_UNSPEC
+        if family != socket.AF_UNSPEC and family != resolved_family:
+            raise Exception('Requested socket family %d but got %d' %
+                            (family, resolved_family))
+        result = [
+            (resolved_family, socktype, proto, '', (resolved, port)),
+            ]
+        self.io_loop.add_callback(callback, result)
diff --git a/tox.ini b/tox.ini
index bc0f73a674ce9454000c804221b80511c043a6e7..f9dcae19b548848fa072c03135007decae3265f5 100644 (file)
--- a/tox.ini
+++ b/tox.ini
@@ -11,7 +11,7 @@
 [tox]
 # "-full" variants include optional dependencies, to ensure
 # that things work both in a bare install and with all the extras.
-envlist = py27-full, py27-curl, py32-full, pypy, py26, py26-full, py27, py32, py32-utf8, py33, py27-opt, py32-opt, pypy-full, py27-select, py27-monotonic, py33-monotonic, py27-twisted, py27-threadedresolver
+envlist = py27-full, py27-curl, py32-full, pypy, py26, py26-full, py27, py32, py32-utf8, py33, py27-opt, py32-opt, pypy-full, py27-select, py27-monotonic, py33-monotonic, py27-twisted, py27-threadedresolver, py27-twistedresolver
 [testenv]
 commands = python -m tornado.test.runtests {posargs:}
 
@@ -100,6 +100,15 @@ deps =
      twisted
 commands = python -m tornado.test.runtests --resolver=tornado.netutil.ThreadedResolver
 
+[testenv:py27-twistedresolver]
+basepython = python2.7
+deps =
+     futures
+     mock
+     pycurl
+     twisted
+commands = python -m tornado.test.runtests --resolver=tornado.platform.twisted.TwistedResolver
+
 [testenv:pypy-full]
 # This configuration works with pypy 1.9.  pycurl installs ok but
 # curl_httpclient doesn't work.  Twisted works most of the time, but