Commit 5e4a7d8d authored by Victor Stinner's avatar Victor Stinner

Issue #23630, asyncio: host parameter of loop.create_server() can now be a

sequence of strings. Patch written by Yann Sionneau.
parent f7dc7fb7
......@@ -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)
......
......@@ -18,6 +18,7 @@ import collections
import concurrent.futures
import heapq
import inspect
import itertools
import logging
import os
import socket
......@@ -786,6 +787,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,
*,
......@@ -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:
......
......@@ -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
......
......@@ -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)
......
......@@ -1294,6 +1294,7 @@ Adam Simpkins
Ravi Sinha
Janne Sinkkonen
Ng Pheng Siong
Yann Sionneau
George Sipe
J. Sipprell
Kragen Sitaker
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment