Skip to content

Commit 5178c33

Browse files
committed
Merge 4362-multiresolve: add IHostnameResolver
Author: glyph Reviewer: hawkowl Fixes: twisted#4362 Added a new interface, twisted.internet.interfaces.IHostnameResolver, which is an improvement to twisted.internet.interfaces.IResolverSimple that supports resolving multiple addresses as well as resolving IPv6 addresses. This is a native, asynchronous, Twisted analogue to getaddrinfo.
2 parents d12bb01 + 6625d4b commit 5178c33

File tree

6 files changed

+1020
-2
lines changed

6 files changed

+1020
-2
lines changed

src/twisted/internet/_resolver.py

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
# -*- test-case-name: twisted.internet.test.test_resolver -*-
2+
# Copyright (c) Twisted Matrix Laboratories.
3+
# See LICENSE for details.
4+
5+
"""
6+
IPv6-aware hostname resolution.
7+
8+
@see: L{IHostnameResolver}
9+
"""
10+
11+
from __future__ import division, absolute_import
12+
13+
__metaclass__ = type
14+
15+
from socket import (getaddrinfo, AF_INET, AF_INET6, AF_UNSPEC, SOCK_STREAM,
16+
SOCK_DGRAM, gaierror)
17+
18+
from zope.interface import implementer
19+
20+
from twisted.internet.interfaces import (IHostnameResolver, IHostResolution,
21+
IResolverSimple, IResolutionReceiver)
22+
from twisted.internet.error import DNSLookupError
23+
from twisted.internet.defer import Deferred
24+
from twisted.internet.threads import deferToThreadPool
25+
from twisted.internet.address import IPv4Address, IPv6Address
26+
from twisted.logger import Logger
27+
28+
29+
@implementer(IHostResolution)
30+
class HostResolution(object):
31+
"""
32+
The in-progress resolution of a given hostname.
33+
"""
34+
35+
def __init__(self, name):
36+
"""
37+
Create a L{HostResolution} with the given name.
38+
"""
39+
self.name = name
40+
41+
42+
43+
_any = frozenset([IPv4Address, IPv6Address])
44+
45+
_typesToAF = {
46+
frozenset([IPv4Address]): AF_INET,
47+
frozenset([IPv6Address]): AF_INET6,
48+
_any: AF_UNSPEC,
49+
}
50+
51+
_afToType = {
52+
AF_INET: IPv4Address,
53+
AF_INET6: IPv6Address,
54+
}
55+
56+
_transportToSocket = {
57+
'TCP': SOCK_STREAM,
58+
'UDP': SOCK_DGRAM,
59+
}
60+
61+
_socktypeToType = {
62+
SOCK_STREAM: 'TCP',
63+
SOCK_DGRAM: 'UDP',
64+
}
65+
66+
67+
68+
@implementer(IHostnameResolver)
69+
class GAIResolver(object):
70+
"""
71+
L{IHostnameResolver} implementation that resolves hostnames by calling
72+
L{getaddrinfo} in a thread.
73+
"""
74+
75+
def __init__(self, reactor, getThreadPool=None, getaddrinfo=getaddrinfo):
76+
"""
77+
Create a L{GAIResolver}.
78+
79+
@param reactor: the reactor to schedule result-delivery on
80+
@type reactor: L{IReactorThreads}
81+
82+
@param getThreadPool: a function to retrieve the thread pool to use for
83+
scheduling name resolutions. If not supplied, the use the given
84+
C{reactor}'s thread pool.
85+
@type getThreadPool: 0-argument callable returning a
86+
L{twisted.python.threadpool.ThreadPool}
87+
88+
@param getaddrinfo: a reference to the L{getaddrinfo} to use - mainly
89+
parameterized for testing.
90+
@type getaddrinfo: callable with the same signature as L{getaddrinfo}
91+
"""
92+
self._reactor = reactor
93+
self._getThreadPool = (reactor.getThreadPool if getThreadPool is None
94+
else getThreadPool)
95+
self._getaddrinfo = getaddrinfo
96+
97+
98+
def resolveHostName(self, resolutionReceiver, hostName, portNumber=0,
99+
addressTypes=None, transportSemantics='TCP'):
100+
"""
101+
See L{IHostnameResolver.resolveHostName}
102+
103+
@param resolutionReceiver: see interface
104+
105+
@param hostName: see interface
106+
107+
@param portNumber: see interface
108+
109+
@param addressTypes: see interface
110+
111+
@param transportSemantics: see interface
112+
113+
@return: see interface
114+
"""
115+
pool = self._getThreadPool()
116+
addressFamily = _typesToAF[_any if addressTypes is None
117+
else frozenset(addressTypes)]
118+
socketType = _transportToSocket[transportSemantics]
119+
def get():
120+
try:
121+
return self._getaddrinfo(hostName, portNumber, addressFamily,
122+
socketType)
123+
except gaierror:
124+
return []
125+
d = deferToThreadPool(self._reactor, pool, get)
126+
resolution = HostResolution(hostName)
127+
resolutionReceiver.resolutionBegan(resolution)
128+
@d.addCallback
129+
def deliverResults(result):
130+
for family, socktype, proto, cannoname, sockaddr in result:
131+
addrType = _afToType[family]
132+
resolutionReceiver.addressResolved(
133+
addrType(_socktypeToType.get(socktype, 'TCP'), *sockaddr)
134+
)
135+
resolutionReceiver.resolutionComplete()
136+
return resolution
137+
138+
139+
140+
@implementer(IHostnameResolver)
141+
class SimpleResolverComplexifier(object):
142+
"""
143+
A converter from L{IResolverSimple} to L{IHostnameResolver}.
144+
"""
145+
146+
_log = Logger()
147+
148+
def __init__(self, simpleResolver):
149+
"""
150+
Construct a L{SimpleResolverComplexifier} with an L{IResolverSimple}.
151+
"""
152+
self._simpleResolver = simpleResolver
153+
154+
155+
def resolveHostName(self, resolutionReceiver, hostName, portNumber=0,
156+
addressTypes=None, transportSemantics='TCP'):
157+
"""
158+
See L{IHostnameResolver.resolveHostName}
159+
160+
@param resolutionReceiver: see interface
161+
162+
@param hostName: see interface
163+
164+
@param portNumber: see interface
165+
166+
@param addressTypes: see interface
167+
168+
@param transportSemantics: see interface
169+
170+
@return: see interface
171+
"""
172+
resolution = HostResolution(hostName)
173+
resolutionReceiver.resolutionBegan(resolution)
174+
onAddress = self._simpleResolver.getHostByName(hostName)
175+
def addressReceived(address):
176+
resolutionReceiver.addressResolved(IPv4Address('TCP', address, 0))
177+
def errorReceived(error):
178+
if not error.check(DNSLookupError):
179+
self._log.failure("while looking up {name} with {resolver}",
180+
error, name=hostName,
181+
resolver=self._simpleResolver)
182+
onAddress.addCallbacks(addressReceived, errorReceived)
183+
def finish(result):
184+
resolutionReceiver.resolutionComplete()
185+
onAddress.addCallback(finish)
186+
return resolution
187+
188+
189+
190+
@implementer(IResolutionReceiver)
191+
class FirstOneWins(object):
192+
"""
193+
An L{IResolutionReceiver} which fires a L{Deferred} with its first result.
194+
"""
195+
196+
def __init__(self, deferred):
197+
"""
198+
@param deferred: The L{Deferred} to fire when the first resolution
199+
result arrives.
200+
"""
201+
self._deferred = deferred
202+
self._resolved = False
203+
204+
205+
def resolutionBegan(self, resolution):
206+
"""
207+
See L{IResolutionReceiver.resolutionBegan}
208+
209+
@param resolution: See L{IResolutionReceiver.resolutionBegan}
210+
"""
211+
self._resolution = resolution
212+
213+
214+
def addressResolved(self, address):
215+
"""
216+
See L{IResolutionReceiver.addressResolved}
217+
218+
@param address: See L{IResolutionReceiver.addressResolved}
219+
"""
220+
if self._resolved:
221+
return
222+
self._resolved = True
223+
self._deferred.callback(address.host)
224+
225+
226+
def resolutionComplete(self):
227+
"""
228+
See L{IResolutionReceiver.resolutionComplete}
229+
"""
230+
if self._resolved:
231+
return
232+
self._deferred.errback(DNSLookupError(self._resolution.name))
233+
234+
235+
236+
@implementer(IResolverSimple)
237+
class ComplexResolverSimplifier(object):
238+
"""
239+
A converter from L{IHostnameResolver} to L{IResolverSimple}
240+
"""
241+
def __init__(self, nameResolver):
242+
"""
243+
Create a L{ComplexResolverSimplifier} with an L{IHostnameResolver}.
244+
245+
@param nameResolver: The L{IHostnameResolver} to use.
246+
"""
247+
self._nameResolver = nameResolver
248+
249+
250+
def getHostByName(self, name, timeouts=()):
251+
"""
252+
See L{IResolverSimple.getHostByName}
253+
254+
@param name: see L{IResolverSimple.getHostByName}
255+
256+
@param timeouts: see L{IResolverSimple.getHostByName}
257+
258+
@return: see L{IResolverSimple.getHostByName}
259+
"""
260+
result = Deferred()
261+
self._nameResolver.resolveHostName(FirstOneWins(result), name, 0,
262+
[IPv4Address])
263+
return result

src/twisted/internet/address.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,23 @@ class IPv6Address(_IPAddress):
7979
@ivar host: A string containing a colon-separated, hexadecimal formatted
8080
IPv6 address; for example, "::1".
8181
@type host: C{str}
82+
83+
@ivar flowInfo: the IPv6 flow label. This can be used by QoS routers to
84+
identify flows of traffic; you may generally safely ignore it.
85+
@type flowInfo: L{int}
86+
87+
@ivar scopeID: the IPv6 scope identifier - roughly analagous to what
88+
interface traffic destined for this address must be transmitted over.
89+
@type scopeID: L{int}
8290
"""
8391

92+
compareAttributes = ('type', 'host', 'port', 'flowInfo', 'scopeID')
93+
94+
def __init__(self, type, host, port, flowInfo=0, scopeID=0):
95+
super(IPv6Address, self).__init__(type, host, port)
96+
self.flowInfo = flowInfo
97+
self.scopeID = scopeID
98+
8499

85100

86101
@implementer(IAddress)

src/twisted/internet/base.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@
2121
from twisted.internet.interfaces import IResolverSimple, IReactorPluggableResolver
2222
from twisted.internet.interfaces import IConnector, IDelayedCall
2323
from twisted.internet import fdesc, main, error, abstract, defer, threads
24+
from twisted.internet._resolver import (
25+
GAIResolver as _GAIResolver,
26+
ComplexResolverSimplifier as _ComplexResolverSimplifier,
27+
SimpleResolverComplexifier as _SimpleResolverComplexifier,
28+
)
2429
from twisted.python import log, failure, reflect
2530
from twisted.python.compat import unicode, iteritems
2631
from twisted.python.runtime import seconds as runtimeSeconds, platform
@@ -487,6 +492,7 @@ def __init__(self):
487492
self._startedBefore = False
488493
# reactor internal readers, e.g. the waker.
489494
self._internalReaders = set()
495+
self._nameResolver = None
490496
self.waker = None
491497

492498
# Arrange for the running attribute to change to True at the right time
@@ -509,12 +515,45 @@ def installWaker(self):
509515
raise NotImplementedError(
510516
reflect.qual(self.__class__) + " did not implement installWaker")
511517

518+
512519
def installResolver(self, resolver):
520+
"""
521+
See L{IReactorPluggableResolver}.
522+
523+
@param resolver: see L{IReactorPluggableResolver}.
524+
525+
@return: see L{IReactorPluggableResolver}.
526+
"""
513527
assert IResolverSimple.providedBy(resolver)
514528
oldResolver = self.resolver
515529
self.resolver = resolver
530+
self._nameResolver = _SimpleResolverComplexifier(resolver)
516531
return oldResolver
517532

533+
534+
def installNameResolver(self, resolver):
535+
"""
536+
See L{IReactorPluggableNameResolver}.
537+
538+
@param resolver: See L{IReactorPluggableNameResolver}.
539+
540+
@return: see L{IReactorPluggableNameResolver}.
541+
"""
542+
previousNameResolver = self._nameResolver
543+
self._nameResolver = resolver
544+
self.resolver = _ComplexResolverSimplifier(resolver)
545+
return previousNameResolver
546+
547+
548+
@property
549+
def nameResolver(self):
550+
"""
551+
Implementation of read-only
552+
L{IReactorPluggableNameResolver.nameResolver}.
553+
"""
554+
return self._nameResolver
555+
556+
518557
def wakeUp(self):
519558
"""
520559
Wake up the event loop.
@@ -938,8 +977,9 @@ def argChecker(arg):
938977
threadpoolShutdownID = None
939978

940979
def _initThreads(self):
980+
self.installNameResolver(_GAIResolver(self, self.getThreadPool))
941981
self.usingThreads = True
942-
self.resolver = ThreadedResolver(self)
982+
943983

944984
def callFromThread(self, f, *args, **kw):
945985
"""

0 commit comments

Comments
 (0)