mirror of
https://github.com/python/cpython.git
synced 2025-07-24 11:44:31 +00:00
bpo-32662: Implement Server.start_serving() and Server.serve_forever() (#5312)
* bpo-32662: Implement Server.start_serving() and Server.serve_forever() New methods: * Server.start_serving(), * Server.serve_forever(), and * Server.is_serving(). Add 'start_serving' keyword parameter to loop.create_server() and loop.create_unix_server().
This commit is contained in:
parent
1aa094f740
commit
c9070d03f5
6 changed files with 337 additions and 38 deletions
|
@ -157,38 +157,32 @@ def _run_until_complete_cb(fut):
|
|||
|
||||
class Server(events.AbstractServer):
|
||||
|
||||
def __init__(self, loop, sockets):
|
||||
def __init__(self, loop, sockets, protocol_factory, ssl_context, backlog,
|
||||
ssl_handshake_timeout):
|
||||
self._loop = loop
|
||||
self.sockets = sockets
|
||||
self._sockets = sockets
|
||||
self._active_count = 0
|
||||
self._waiters = []
|
||||
self._protocol_factory = protocol_factory
|
||||
self._backlog = backlog
|
||||
self._ssl_context = ssl_context
|
||||
self._ssl_handshake_timeout = ssl_handshake_timeout
|
||||
self._serving = False
|
||||
self._serving_forever_fut = None
|
||||
|
||||
def __repr__(self):
|
||||
return f'<{self.__class__.__name__} sockets={self.sockets!r}>'
|
||||
|
||||
def _attach(self):
|
||||
assert self.sockets is not None
|
||||
assert self._sockets is not None
|
||||
self._active_count += 1
|
||||
|
||||
def _detach(self):
|
||||
assert self._active_count > 0
|
||||
self._active_count -= 1
|
||||
if self._active_count == 0 and self.sockets is None:
|
||||
if self._active_count == 0 and self._sockets is None:
|
||||
self._wakeup()
|
||||
|
||||
def close(self):
|
||||
sockets = self.sockets
|
||||
if sockets is None:
|
||||
return
|
||||
self.sockets = None
|
||||
for sock in sockets:
|
||||
self._loop._stop_serving(sock)
|
||||
if self._active_count == 0:
|
||||
self._wakeup()
|
||||
|
||||
def get_loop(self):
|
||||
return self._loop
|
||||
|
||||
def _wakeup(self):
|
||||
waiters = self._waiters
|
||||
self._waiters = None
|
||||
|
@ -196,8 +190,73 @@ class Server(events.AbstractServer):
|
|||
if not waiter.done():
|
||||
waiter.set_result(waiter)
|
||||
|
||||
def _start_serving(self):
|
||||
if self._serving:
|
||||
return
|
||||
self._serving = True
|
||||
for sock in self._sockets:
|
||||
sock.listen(self._backlog)
|
||||
self._loop._start_serving(
|
||||
self._protocol_factory, sock, self._ssl_context,
|
||||
self, self._backlog, self._ssl_handshake_timeout)
|
||||
|
||||
def get_loop(self):
|
||||
return self._loop
|
||||
|
||||
def is_serving(self):
|
||||
return self._serving
|
||||
|
||||
@property
|
||||
def sockets(self):
|
||||
if self._sockets is None:
|
||||
return []
|
||||
return list(self._sockets)
|
||||
|
||||
def close(self):
|
||||
sockets = self._sockets
|
||||
if sockets is None:
|
||||
return
|
||||
self._sockets = None
|
||||
|
||||
for sock in sockets:
|
||||
self._loop._stop_serving(sock)
|
||||
|
||||
self._serving = False
|
||||
|
||||
if (self._serving_forever_fut is not None and
|
||||
not self._serving_forever_fut.done()):
|
||||
self._serving_forever_fut.cancel()
|
||||
self._serving_forever_fut = None
|
||||
|
||||
if self._active_count == 0:
|
||||
self._wakeup()
|
||||
|
||||
async def start_serving(self):
|
||||
self._start_serving()
|
||||
|
||||
async def serve_forever(self):
|
||||
if self._serving_forever_fut is not None:
|
||||
raise RuntimeError(
|
||||
f'server {self!r} is already being awaited on serve_forever()')
|
||||
if self._sockets is None:
|
||||
raise RuntimeError(f'server {self!r} is closed')
|
||||
|
||||
self._start_serving()
|
||||
self._serving_forever_fut = self._loop.create_future()
|
||||
|
||||
try:
|
||||
await self._serving_forever_fut
|
||||
except futures.CancelledError:
|
||||
try:
|
||||
self.close()
|
||||
await self.wait_closed()
|
||||
finally:
|
||||
raise
|
||||
finally:
|
||||
self._serving_forever_fut = None
|
||||
|
||||
async def wait_closed(self):
|
||||
if self.sockets is None or self._waiters is None:
|
||||
if self._sockets is None or self._waiters is None:
|
||||
return
|
||||
waiter = self._loop.create_future()
|
||||
self._waiters.append(waiter)
|
||||
|
@ -1059,7 +1118,8 @@ class BaseEventLoop(events.AbstractEventLoop):
|
|||
ssl=None,
|
||||
reuse_address=None,
|
||||
reuse_port=None,
|
||||
ssl_handshake_timeout=None):
|
||||
ssl_handshake_timeout=None,
|
||||
start_serving=True):
|
||||
"""Create a TCP server.
|
||||
|
||||
The host parameter can be a string, in that case the TCP server is
|
||||
|
@ -1149,12 +1209,14 @@ class BaseEventLoop(events.AbstractEventLoop):
|
|||
raise ValueError(f'A Stream Socket was expected, got {sock!r}')
|
||||
sockets = [sock]
|
||||
|
||||
server = Server(self, sockets)
|
||||
for sock in sockets:
|
||||
sock.listen(backlog)
|
||||
sock.setblocking(False)
|
||||
self._start_serving(protocol_factory, sock, ssl, server, backlog,
|
||||
ssl_handshake_timeout)
|
||||
|
||||
server = Server(self, sockets, protocol_factory,
|
||||
ssl, backlog, ssl_handshake_timeout)
|
||||
if start_serving:
|
||||
server._start_serving()
|
||||
|
||||
if self._debug:
|
||||
logger.info("%r is serving", server)
|
||||
return server
|
||||
|
|
|
@ -164,13 +164,39 @@ class AbstractServer:
|
|||
"""Stop serving. This leaves existing connections open."""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_loop(self):
|
||||
"""Get the event loop the Server object is attached to."""
|
||||
raise NotImplementedError
|
||||
|
||||
def is_serving(self):
|
||||
"""Return True if the server is accepting connections."""
|
||||
raise NotImplementedError
|
||||
|
||||
async def start_serving(self):
|
||||
"""Start accepting connections.
|
||||
|
||||
This method is idempotent, so it can be called when
|
||||
the server is already being serving.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
async def serve_forever(self):
|
||||
"""Start accepting connections until the coroutine is cancelled.
|
||||
|
||||
The server is closed when the coroutine is cancelled.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
async def wait_closed(self):
|
||||
"""Coroutine to wait until service is closed."""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_loop(self):
|
||||
""" Get the event loop the Server object is attached to."""
|
||||
raise NotImplementedError
|
||||
async def __aenter__(self):
|
||||
return self
|
||||
|
||||
async def __aexit__(self, *exc):
|
||||
self.close()
|
||||
await self.wait_closed()
|
||||
|
||||
|
||||
class AbstractEventLoop:
|
||||
|
@ -279,7 +305,8 @@ class AbstractEventLoop:
|
|||
*, family=socket.AF_UNSPEC,
|
||||
flags=socket.AI_PASSIVE, sock=None, backlog=100,
|
||||
ssl=None, reuse_address=None, reuse_port=None,
|
||||
ssl_handshake_timeout=None):
|
||||
ssl_handshake_timeout=None,
|
||||
start_serving=True):
|
||||
"""A coroutine which creates a TCP server bound to host and port.
|
||||
|
||||
The return value is a Server object which can be used to stop
|
||||
|
@ -319,6 +346,11 @@ class AbstractEventLoop:
|
|||
will wait for completion of the SSL handshake before aborting the
|
||||
connection. Default is 10s, longer timeouts may increase vulnerability
|
||||
to DoS attacks (see https://support.f5.com/csp/article/K13834)
|
||||
|
||||
start_serving set to True (default) causes the created server
|
||||
to start accepting connections immediately. When set to False,
|
||||
the user should await Server.start_serving() or Server.serve_forever()
|
||||
to make the server to start accepting connections.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
@ -343,7 +375,8 @@ class AbstractEventLoop:
|
|||
async def create_unix_server(
|
||||
self, protocol_factory, path=None, *,
|
||||
sock=None, backlog=100, ssl=None,
|
||||
ssl_handshake_timeout=None):
|
||||
ssl_handshake_timeout=None,
|
||||
start_serving=True):
|
||||
"""A coroutine which creates a UNIX Domain Socket server.
|
||||
|
||||
The return value is a Server object, which can be used to stop
|
||||
|
@ -363,6 +396,11 @@ class AbstractEventLoop:
|
|||
|
||||
ssl_handshake_timeout is the time in seconds that an SSL server
|
||||
will wait for the SSL handshake to complete (defaults to 10s).
|
||||
|
||||
start_serving set to True (default) causes the created server
|
||||
to start accepting connections immediately. When set to False,
|
||||
the user should await Server.start_serving() or Server.serve_forever()
|
||||
to make the server to start accepting connections.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
|
|
@ -250,7 +250,8 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
|
|||
async def create_unix_server(
|
||||
self, protocol_factory, path=None, *,
|
||||
sock=None, backlog=100, ssl=None,
|
||||
ssl_handshake_timeout=None):
|
||||
ssl_handshake_timeout=None,
|
||||
start_serving=True):
|
||||
if isinstance(ssl, bool):
|
||||
raise TypeError('ssl argument must be an SSLContext or None')
|
||||
|
||||
|
@ -302,11 +303,12 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
|
|||
raise ValueError(
|
||||
f'A UNIX Domain Stream Socket was expected, got {sock!r}')
|
||||
|
||||
server = base_events.Server(self, [sock])
|
||||
sock.listen(backlog)
|
||||
sock.setblocking(False)
|
||||
self._start_serving(protocol_factory, sock, ssl, server,
|
||||
ssl_handshake_timeout=ssl_handshake_timeout)
|
||||
server = base_events.Server(self, [sock], protocol_factory,
|
||||
ssl, backlog, ssl_handshake_timeout)
|
||||
if start_serving:
|
||||
server._start_serving()
|
||||
|
||||
return server
|
||||
|
||||
async def _sock_sendfile_native(self, sock, file, offset, count):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue