summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVictor Stinner <victor.stinner@gmail.com>2015-09-21 18:33:43 +0200
committerVictor Stinner <victor.stinner@gmail.com>2015-09-21 18:33:43 +0200
commit9865f4ffe798957c194d9b52810e063fcc9022e4 (patch)
tree43ce050b05f7f96a10c5da7ae859190b0ff920b7
parentcd68cf1aadafa4d15039ce34f6e0f3359ed13f9a (diff)
downloadcpython-9865f4ffe798957c194d9b52810e063fcc9022e4.tar.gz
Issue #23630, asyncio: host parameter of loop.create_server() can now be a
sequence of strings. Patch written by Yann Sionneau.
-rw-r--r--Doc/library/asyncio-eventloop.rst13
-rw-r--r--Lib/asyncio/base_events.py35
-rw-r--r--Lib/asyncio/events.py3
-rw-r--r--Lib/test/test_asyncio/test_events.py33
-rw-r--r--Misc/ACKS1
5 files changed, 74 insertions, 11 deletions
diff --git a/Doc/library/asyncio-eventloop.rst b/Doc/library/asyncio-eventloop.rst
index 277e4045df..78a07a00dd 100644
--- a/Doc/library/asyncio-eventloop.rst
+++ b/Doc/library/asyncio-eventloop.rst
@@ -331,9 +331,12 @@ Creating listening connections
Parameters:
- * If *host* is an empty string or ``None``, all interfaces are assumed
- and a list of multiple sockets will be returned (most likely
- one for IPv4 and another one for IPv6).
+ * The *host* parameter can be a string, in that case the TCP server is
+ bound to *host* and *port*. The *host* parameter can also be a sequence
+ of strings and in that case the TCP server is bound to all hosts of the
+ sequence. If *host* is an empty string or ``None``, all interfaces are
+ assumed and a list of multiple sockets will be returned (most likely one
+ for IPv4 and another one for IPv6).
* *family* can be set to either :data:`socket.AF_INET` or
:data:`~socket.AF_INET6` to force the socket to use IPv4 or IPv6. If not set
@@ -365,6 +368,10 @@ Creating listening connections
The function :func:`start_server` creates a (:class:`StreamReader`,
:class:`StreamWriter`) pair and calls back a function with this pair.
+ .. versionchanged:: 3.4.4
+
+ The *host* parameter can now be a sequence of strings.
+
.. coroutinemethod:: BaseEventLoop.create_unix_server(protocol_factory, path=None, \*, sock=None, backlog=100, ssl=None)
diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py
index c20544545b..a50e00522c 100644
--- a/Lib/asyncio/base_events.py
+++ b/Lib/asyncio/base_events.py
@@ -18,6 +18,7 @@ import collections
import concurrent.futures
import heapq
import inspect
+import itertools
import logging
import os
import socket
@@ -787,6 +788,15 @@ class BaseEventLoop(events.AbstractEventLoop):
return transport, protocol
@coroutine
+ def _create_server_getaddrinfo(self, host, port, family, flags):
+ infos = yield from self.getaddrinfo(host, port, family=family,
+ type=socket.SOCK_STREAM,
+ flags=flags)
+ if not infos:
+ raise OSError('getaddrinfo({!r}) returned empty list'.format(host))
+ return infos
+
+ @coroutine
def create_server(self, protocol_factory, host=None, port=None,
*,
family=socket.AF_UNSPEC,
@@ -795,7 +805,13 @@ class BaseEventLoop(events.AbstractEventLoop):
backlog=100,
ssl=None,
reuse_address=None):
- """Create a TCP server bound to host and port.
+ """Create a TCP server.
+
+ The host parameter can be a string, in that case the TCP server is bound
+ to host and port.
+
+ The host parameter can also be a sequence of strings and in that case
+ the TCP server is bound to all hosts of the sequence.
Return a Server object which can be used to stop the service.
@@ -813,13 +829,18 @@ class BaseEventLoop(events.AbstractEventLoop):
reuse_address = os.name == 'posix' and sys.platform != 'cygwin'
sockets = []
if host == '':
- host = None
+ hosts = [None]
+ elif (isinstance(host, str) or
+ not isinstance(host, collections.Iterable)):
+ hosts = [host]
+ else:
+ hosts = host
- infos = yield from self.getaddrinfo(
- host, port, family=family,
- type=socket.SOCK_STREAM, proto=0, flags=flags)
- if not infos:
- raise OSError('getaddrinfo() returned empty list')
+ fs = [self._create_server_getaddrinfo(host, port, family=family,
+ flags=flags)
+ for host in hosts]
+ infos = yield from tasks.gather(*fs, loop=self)
+ infos = itertools.chain.from_iterable(infos)
completed = False
try:
diff --git a/Lib/asyncio/events.py b/Lib/asyncio/events.py
index d5f0d45195..1e42ddd03b 100644
--- a/Lib/asyncio/events.py
+++ b/Lib/asyncio/events.py
@@ -305,7 +305,8 @@ class AbstractEventLoop:
If host is an empty string or None all interfaces are assumed
and a list of multiple sockets will be returned (most likely
- one for IPv4 and another one for IPv6).
+ one for IPv4 and another one for IPv6). The host parameter can also be a
+ sequence (e.g. list) of hosts to bind to.
family can be set to either AF_INET or AF_INET6 to force the
socket to use IPv4 or IPv6. If not set it will be determined
diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py
index ba1fa5dd0c..24c3f1e366 100644
--- a/Lib/test/test_asyncio/test_events.py
+++ b/Lib/test/test_asyncio/test_events.py
@@ -745,6 +745,39 @@ class EventLoopTestsMixin:
self.assertEqual(cm.exception.errno, errno.EADDRINUSE)
self.assertIn(str(httpd.address), cm.exception.strerror)
+ @mock.patch('asyncio.base_events.socket')
+ def create_server_multiple_hosts(self, family, hosts, mock_sock):
+ @asyncio.coroutine
+ def getaddrinfo(host, port, *args, **kw):
+ if family == socket.AF_INET:
+ return [[family, socket.SOCK_STREAM, 6, '', (host, port)]]
+ else:
+ return [[family, socket.SOCK_STREAM, 6, '', (host, port, 0, 0)]]
+
+ def getaddrinfo_task(*args, **kwds):
+ return asyncio.Task(getaddrinfo(*args, **kwds), loop=self.loop)
+
+ if family == socket.AF_INET:
+ mock_sock.socket().getsockbyname.side_effect = [(host, 80)
+ for host in hosts]
+ else:
+ mock_sock.socket().getsockbyname.side_effect = [(host, 80, 0, 0)
+ for host in hosts]
+ self.loop.getaddrinfo = getaddrinfo_task
+ self.loop._start_serving = mock.Mock()
+ f = self.loop.create_server(lambda: MyProto(self.loop), hosts, 80)
+ server = self.loop.run_until_complete(f)
+ self.addCleanup(server.close)
+ server_hosts = [sock.getsockbyname()[0] for sock in server.sockets]
+ self.assertEqual(server_hosts, hosts)
+
+ def test_create_server_multiple_hosts_ipv4(self):
+ self.create_server_multiple_hosts(socket.AF_INET,
+ ['1.2.3.4', '5.6.7.8'])
+
+ def test_create_server_multiple_hosts_ipv6(self):
+ self.create_server_multiple_hosts(socket.AF_INET6, ['::1', '::2'])
+
def test_create_server(self):
proto = MyProto(self.loop)
f = self.loop.create_server(lambda: proto, '0.0.0.0', 0)
diff --git a/Misc/ACKS b/Misc/ACKS
index 521468ca4f..d9b32b2a95 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -1294,6 +1294,7 @@ Adam Simpkins
Ravi Sinha
Janne Sinkkonen
Ng Pheng Siong
+Yann Sionneau
George Sipe
J. Sipprell
Kragen Sitaker