Skip to content

Commit e57b315

Browse files
committed
Support custom close code/reason in Server.close.
1 parent ba1ce05 commit e57b315

File tree

3 files changed

+41
-10
lines changed

3 files changed

+41
-10
lines changed

docs/project/changelog.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,12 @@ Improvements
4949
............
5050

5151
* Allowed setting separate limits for messages and fragments with ``max_size``.
52+
5253
* Added support for HTTP/1.0 proxies.
5354

55+
* Added support for customizing the close code and reason for connections in
56+
:meth:`Server.close <asyncio.server.Server.close>`.
57+
5458
15.0.1
5559
------
5660

src/websockets/asyncio/server.py

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -405,18 +405,25 @@ def start_connection_handler(self, connection: ServerConnection) -> None:
405405
# but before it starts executing.
406406
self.handlers[connection] = self.loop.create_task(self.conn_handler(connection))
407407

408-
def close(self, close_connections: bool = True) -> None:
408+
def close(
409+
self,
410+
close_connections: bool = True,
411+
code: CloseCode | int = CloseCode.GOING_AWAY,
412+
reason: str = "",
413+
) -> None:
409414
"""
410415
Close the server.
411416
412417
* Close the underlying :class:`asyncio.Server`.
413-
* When ``close_connections`` is :obj:`True`, which is the default,
414-
close existing connections. Specifically:
418+
* When ``close_connections`` is :obj:`True`, which is the default, close
419+
existing connections. Specifically:
415420
416421
* Reject opening WebSocket connections with an HTTP 503 (service
417422
unavailable) error. This happens when the server accepted the TCP
418423
connection but didn't complete the opening handshake before closing.
419-
* Close open WebSocket connections with close code 1001 (going away).
424+
* Close open WebSocket connections with code 1001 (going away).
425+
``code`` and ``reason`` can be customized, for example to use code
426+
1012 (service restart).
420427
421428
* Wait until all connection handlers terminate.
422429
@@ -425,16 +432,21 @@ def close(self, close_connections: bool = True) -> None:
425432
"""
426433
if self.close_task is None:
427434
self.close_task = self.get_loop().create_task(
428-
self._close(close_connections)
435+
self._close(close_connections, code, reason)
429436
)
430437

431-
async def _close(self, close_connections: bool) -> None:
438+
async def _close(
439+
self,
440+
close_connections: bool = True,
441+
code: CloseCode | int = CloseCode.GOING_AWAY,
442+
reason: str = "",
443+
) -> None:
432444
"""
433445
Implementation of :meth:`close`.
434446
435447
This calls :meth:`~asyncio.Server.close` on the underlying
436448
:class:`asyncio.Server` object to stop accepting new connections and
437-
then closes open connections with close code 1001.
449+
then closes open connections.
438450
439451
"""
440452
self.logger.info("server closing")
@@ -447,11 +459,13 @@ async def _close(self, close_connections: bool) -> None:
447459
# details. This workaround can be removed when dropping Python < 3.11.
448460
await asyncio.sleep(0)
449461

462+
# After server.close(), handshake() closes OPENING connections with an
463+
# HTTP 503 error.
464+
450465
if close_connections:
451-
# Close OPEN connections with close code 1001. After server.close(),
452-
# handshake() closes OPENING connections with an HTTP 503 error.
466+
# Close OPEN connections with code 1001 by default.
453467
close_tasks = [
454-
asyncio.create_task(connection.close(1001))
468+
asyncio.create_task(connection.close(code, reason))
455469
for connection in self.handlers
456470
if connection.protocol.state is not CONNECTING
457471
]

tests/asyncio/test_server.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,19 @@ async def test_close_server_closes_open_connections(self):
507507
"received 1001 (going away); then sent 1001 (going away)",
508508
)
509509

510+
async def test_close_server_closes_open_connections_with_code_and_reason(self):
511+
"""Server closes open connections with custom code and reason when closing."""
512+
async with serve(*args) as server:
513+
async with connect(get_uri(server)) as client:
514+
server.close(code=1012, reason="restarting")
515+
with self.assertRaises(ConnectionClosedError) as raised:
516+
await client.recv()
517+
self.assertEqual(
518+
str(raised.exception),
519+
"received 1012 (service restart) restarting; "
520+
"then sent 1012 (service restart) restarting",
521+
)
522+
510523
async def test_close_server_keeps_connections_open(self):
511524
"""Server waits for client to close open connections when closing."""
512525
async with serve(*args) as server:

0 commit comments

Comments
 (0)