[Python-checkins] cpython (3.4): Issue #23630, asyncio: host parameter of loop.create_server() can now be a

victor.stinner python-checkins at python.org
Mon Sep 21 18:42:35 CEST 2015


https://hg.python.org/cpython/rev/682623e3e793
changeset:   98138:682623e3e793
branch:      3.4
parent:      98133:d7859e7e7071
user:        Victor Stinner <victor.stinner at gmail.com>
date:        Mon Sep 21 18:33:43 2015 +0200
summary:
  Issue #23630, asyncio: host parameter of loop.create_server() can now be a
sequence of strings. Patch written by Yann Sionneau.

files:
  Doc/library/asyncio-eventloop.rst    |  13 ++++-
  Lib/asyncio/base_events.py           |  35 ++++++++++++---
  Lib/asyncio/events.py                |   3 +-
  Lib/test/test_asyncio/test_events.py |  33 +++++++++++++++
  Misc/ACKS                            |   1 +
  5 files changed, 74 insertions(+), 11 deletions(-)


diff --git a/Doc/library/asyncio-eventloop.rst b/Doc/library/asyncio-eventloop.rst
--- a/Doc/library/asyncio-eventloop.rst
+++ b/Doc/library/asyncio-eventloop.rst
@@ -331,9 +331,12 @@
 
    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 @@
       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
--- a/Lib/asyncio/base_events.py
+++ b/Lib/asyncio/base_events.py
@@ -18,6 +18,7 @@
 import concurrent.futures
 import heapq
 import inspect
+import itertools
 import logging
 import os
 import socket
@@ -787,6 +788,15 @@
         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 @@
                       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 @@
                 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
--- a/Lib/asyncio/events.py
+++ b/Lib/asyncio/events.py
@@ -305,7 +305,8 @@
 
         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
--- a/Lib/test/test_asyncio/test_events.py
+++ b/Lib/test/test_asyncio/test_events.py
@@ -745,6 +745,39 @@
             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
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -1294,6 +1294,7 @@
 Ravi Sinha
 Janne Sinkkonen
 Ng Pheng Siong
+Yann Sionneau
 George Sipe
 J. Sipprell
 Kragen Sitaker

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list