Skip to content

Commit a72c912

Browse files
committed
Merge pull request #678 from gagoman/feature/socket-binding
local socket binding for TCPConnector
2 parents 6571ecb + 3e3bf3f commit a72c912

File tree

3 files changed

+35
-3
lines changed

3 files changed

+35
-3
lines changed

aiohttp/connector.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -405,13 +405,14 @@ class TCPConnector(BaseConnector):
405405
https://en.wikipedia.org/wiki/Transport_Layer_Security#Certificate_pinning
406406
:param bool resolve: Set to True to do DNS lookup for host name.
407407
:param family: socket address family
408+
:param local_addr: local :class:`tuple` of (host, port) to bind socket to
408409
:param args: see :class:`BaseConnector`
409410
:param kwargs: see :class:`BaseConnector`
410411
"""
411412

412413
def __init__(self, *, verify_ssl=True, fingerprint=None,
413414
resolve=_marker, use_dns_cache=_marker,
414-
family=0, ssl_context=None,
415+
family=0, ssl_context=None, local_addr=None,
415416
**kwargs):
416417
super().__init__(**kwargs)
417418

@@ -450,6 +451,7 @@ def __init__(self, *, verify_ssl=True, fingerprint=None,
450451
self._cached_hosts = {}
451452
self._ssl_context = ssl_context
452453
self._family = family
454+
self._local_addr = local_addr
453455

454456
@property
455457
def verify_ssl(self):
@@ -575,7 +577,8 @@ def _create_connection(self, req):
575577
self._factory, host, port,
576578
ssl=sslcontext, family=hinfo['family'],
577579
proto=hinfo['proto'], flags=hinfo['flags'],
578-
server_hostname=hinfo['hostname'] if sslcontext else None)
580+
server_hostname=hinfo['hostname'] if sslcontext else None,
581+
local_addr=self._local_addr)
579582
has_cert = transp.get_extra_info('sslcontext')
580583
if has_cert and self._fingerprint:
581584
sock = transp.get_extra_info('socket')

docs/client_reference.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -752,7 +752,7 @@ TCPConnector
752752
family=0, \
753753
ssl_context=None, conn_timeout=None, \
754754
keepalive_timeout=30, limit=None, share_cookies=False, \
755-
force_close=False, loop=None)
755+
force_close=False, loop=None, local_addr=None)
756756

757757
Connector for working with *HTTP* and *HTTPS* via *TCP* sockets.
758758

@@ -806,6 +806,9 @@ TCPConnector
806806
*ssl_context* may be used for configuring certification
807807
authority channel, supported SSL options etc.
808808

809+
:param tuple local_addr: tuple of ``(local_host, local_port)`` used to bind
810+
socket locally.
811+
809812
.. attribute:: verify_ssl
810813

811814
Check *ssl certifications* if ``True``.

tests/test_connector.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -752,6 +752,32 @@ def handler(request):
752752
r.close()
753753
conn.close()
754754

755+
def test_tcp_connector_uses_provided_local_addr(self):
756+
@asyncio.coroutine
757+
def handler(request):
758+
return web.HTTPOk()
759+
760+
app, srv, url = self.loop.run_until_complete(
761+
self.create_server('get', '/', handler)
762+
)
763+
764+
port = self.find_unused_port()
765+
conn = aiohttp.TCPConnector(loop=self.loop,
766+
local_addr=('127.0.0.1', port))
767+
768+
r = self.loop.run_until_complete(
769+
aiohttp.request(
770+
'get', url,
771+
connector=conn
772+
))
773+
774+
self.loop.run_until_complete(r.release())
775+
first_conn = next(iter(conn._conns.values()))[0][0]
776+
self.assertEqual(first_conn._sock.getsockname(), ('127.0.0.1', port))
777+
r.close()
778+
779+
conn.close()
780+
755781
@unittest.skipUnless(hasattr(socket, 'AF_UNIX'), 'requires unix')
756782
def test_unix_connector(self):
757783
@asyncio.coroutine

0 commit comments

Comments
 (0)