diff --git a/tornado/platform/twisted.py b/tornado/platform/twisted.py index 34e108d7..d0d12aed 100644 --- a/tornado/platform/twisted.py +++ b/tornado/platform/twisted.py @@ -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 bc0f73a6..f9dcae19 100644 --- 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