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 ...@@ -331,9 +331,12 @@ Creating listening connections
Parameters: Parameters:
* If *host* is an empty string or ``None``, all interfaces are assumed * The *host* parameter can be a string, in that case the TCP server is
and a list of multiple sockets will be returned (most likely bound to *host* and *port*. The *host* parameter can also be a sequence
one for IPv4 and another one for IPv6). 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 * *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 :data:`~socket.AF_INET6` to force the socket to use IPv4 or IPv6. If not set
...@@ -365,6 +368,10 @@ Creating listening connections ...@@ -365,6 +368,10 @@ Creating listening connections
The function :func:`start_server` creates a (:class:`StreamReader`, The function :func:`start_server` creates a (:class:`StreamReader`,
:class:`StreamWriter`) pair and calls back a function with this pair. :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) .. coroutinemethod:: BaseEventLoop.create_unix_server(protocol_factory, path=None, \*, sock=None, backlog=100, ssl=None)
......
...@@ -18,6 +18,7 @@ import collections ...@@ -18,6 +18,7 @@ import collections
import concurrent.futures import concurrent.futures
import heapq import heapq
import inspect import inspect
import itertools
import logging import logging
import os import os
import socket import socket
...@@ -786,6 +787,15 @@ class BaseEventLoop(events.AbstractEventLoop): ...@@ -786,6 +787,15 @@ class BaseEventLoop(events.AbstractEventLoop):
return transport, protocol 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 @coroutine
def create_server(self, protocol_factory, host=None, port=None, def create_server(self, protocol_factory, host=None, port=None,
*, *,
...@@ -795,7 +805,13 @@ class BaseEventLoop(events.AbstractEventLoop): ...@@ -795,7 +805,13 @@ class BaseEventLoop(events.AbstractEventLoop):
backlog=100, backlog=100,
ssl=None, ssl=None,
reuse_address=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. Return a Server object which can be used to stop the service.
...@@ -813,13 +829,18 @@ class BaseEventLoop(events.AbstractEventLoop): ...@@ -813,13 +829,18 @@ class BaseEventLoop(events.AbstractEventLoop):
reuse_address = os.name == 'posix' and sys.platform != 'cygwin' reuse_address = os.name == 'posix' and sys.platform != 'cygwin'
sockets = [] sockets = []
if host == '': 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( fs = [self._create_server_getaddrinfo(host, port, family=family,
host, port, family=family, flags=flags)
type=socket.SOCK_STREAM, proto=0, flags=flags) for host in hosts]
if not infos: infos = yield from tasks.gather(*fs, loop=self)
raise OSError('getaddrinfo() returned empty list') infos = itertools.chain.from_iterable(infos)
completed = False completed = False
try: try:
......
...@@ -305,7 +305,8 @@ class AbstractEventLoop: ...@@ -305,7 +305,8 @@ class AbstractEventLoop:
If host is an empty string or None all interfaces are assumed If host is an empty string or None all interfaces are assumed
and a list of multiple sockets will be returned (most likely 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 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 socket to use IPv4 or IPv6. If not set it will be determined
......
...@@ -745,6 +745,39 @@ class EventLoopTestsMixin: ...@@ -745,6 +745,39 @@ class EventLoopTestsMixin:
self.assertEqual(cm.exception.errno, errno.EADDRINUSE) self.assertEqual(cm.exception.errno, errno.EADDRINUSE)
self.assertIn(str(httpd.address), cm.exception.strerror) 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): def test_create_server(self):
proto = MyProto(self.loop) proto = MyProto(self.loop)
f = self.loop.create_server(lambda: proto, '0.0.0.0', 0) f = self.loop.create_server(lambda: proto, '0.0.0.0', 0)
......
...@@ -1294,6 +1294,7 @@ Adam Simpkins ...@@ -1294,6 +1294,7 @@ Adam Simpkins
Ravi Sinha Ravi Sinha
Janne Sinkkonen Janne Sinkkonen
Ng Pheng Siong Ng Pheng Siong
Yann Sionneau
George Sipe George Sipe
J. Sipprell J. Sipprell
Kragen Sitaker 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