From e610227012f71a2020b8b25bb5f0e3f61ce0a569 Mon Sep 17 00:00:00 2001 From: Tom Whittock Date: Sat, 5 Apr 2025 10:04:57 +0000 Subject: [PATCH 1/5] Add dynamic port binding Fixes #10665 --- CHANGES/10665.feature.rst | 1 + CONTRIBUTORS.txt | 1 + aiohttp/web_runner.py | 9 +++++++++ docs/web_advanced.rst | 22 ++++++++++++++++++++++ 4 files changed, 33 insertions(+) create mode 100644 CHANGES/10665.feature.rst diff --git a/CHANGES/10665.feature.rst b/CHANGES/10665.feature.rst new file mode 100644 index 00000000000..693317876f7 --- /dev/null +++ b/CHANGES/10665.feature.rst @@ -0,0 +1 @@ +Added `port` accessor for dynamic port allocations in `TCPSite` -- by :user:`twhittock-disguise`. diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index e3ddd3e3d6a..1179c7452f3 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -344,6 +344,7 @@ Thomas Forbes Thomas Grainger Tim Menninger Tolga Tezel +Tom Whittock Tomasz Trebski Toshiaki Tanaka Trinh Hoang Nhu diff --git a/aiohttp/web_runner.py b/aiohttp/web_runner.py index 11f692ce07e..920bb568559 100644 --- a/aiohttp/web_runner.py +++ b/aiohttp/web_runner.py @@ -113,6 +113,11 @@ def name(self) -> str: host = "0.0.0.0" if not self._host else self._host return str(URL.build(scheme=scheme, host=host, port=self._port)) + @property + def port(self) -> int: + """Return the port number the server is bound to, useful for the dynamically allocated port (0).""" + return self._port + async def start(self) -> None: await super().start() loop = asyncio.get_event_loop() @@ -127,6 +132,10 @@ async def start(self) -> None: reuse_address=self._reuse_address, reuse_port=self._reuse_port, ) + if self._port == 0: + # Port 0 means bind to any port, so we need to set the attribute + # to the port the server was actually bound to. + self._port = self._server.sockets[0].getsockname()[1] class UnixSite(BaseSite): diff --git a/docs/web_advanced.rst b/docs/web_advanced.rst index 76fc3ea57f1..4d544649d83 100644 --- a/docs/web_advanced.rst +++ b/docs/web_advanced.rst @@ -1181,6 +1181,28 @@ the middleware might use :meth:`BaseRequest.clone`. for modifying *scheme*, *host* and *remote* attributes according to ``Forwarded`` and ``X-Forwarded-*`` HTTP headers. +Deploying with a dynamic port +----------------------------- + +When deploying aiohttp in a zeroconf environment, it may be useful +to have the server bind to a dynamic port. This can be done by +using the ``0`` port number. This will cause the OS to assign a +free port to the server. The assigned port can be retrieved +using the :attr:`TCPSite.port` property after the server has started. + +For example:: + + app = web.Application() + runner = web.AppRunner(app) + await runner.setup() + site = web.TCPSite(runner, 'localhost', 0) + await site.start() + + print(f"Server started on port {site.port}") + while True: + await asyncio.sleep(3600) # sleep forever + + Swagger support --------------- From 634c3e7ccb162594ff76c1865ec026601a1dca20 Mon Sep 17 00:00:00 2001 From: Tom Whittock Date: Sat, 5 Apr 2025 11:56:19 +0000 Subject: [PATCH 2/5] Add reference docs for the TCPSite port attribute --- docs/web_reference.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/web_reference.rst b/docs/web_reference.rst index 7f661f44f71..b16a784279b 100644 --- a/docs/web_reference.rst +++ b/docs/web_reference.rst @@ -2788,6 +2788,17 @@ application on specific TCP or Unix socket, e.g.:: this flag when being created. This option is not supported on Windows. + .. attribute:: port + + The port number the server is bound to. This is useful when the port + number is not known in advance, such as when constructing with + port 0 to request an ephemeral port or when not supplying the port + to the constructor. + This attribute is read-only and should not be modified. + This attribute is only guaranteed to be correct after the server has + been started. + + .. class:: UnixSite(runner, path, *, \ shutdown_timeout=60.0, ssl_context=None, \ backlog=128) From 8eb2d47b7f35baab76742bfa9bfc51968dcc5e90 Mon Sep 17 00:00:00 2001 From: Tom Whittock Date: Sat, 5 Apr 2025 11:59:49 +0000 Subject: [PATCH 3/5] Add test for ephemeral port allocation --- tests/test_web_runner.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_web_runner.py b/tests/test_web_runner.py index d75e68ee153..60b3e13e954 100644 --- a/tests/test_web_runner.py +++ b/tests/test_web_runner.py @@ -256,9 +256,21 @@ async def test_tcpsite_empty_str_host(make_runner: _RunnerMaker) -> None: runner = make_runner() await runner.setup() site = web.TCPSite(runner, host="") + assert site.port == 8080 assert site.name == "http://0.0.0.0:8080" +async def test_tcpsite_ephemeral_port(make_runner: _RunnerMaker) -> None: + runner = make_runner() + await runner.setup() + site = web.TCPSite(runner, port=0) + + await site.start() + assert site.port != 0 + assert site.name.startswith("http://0.0.0.0:") + await site.stop() + + def test_run_after_asyncio_run() -> None: called = False From 02ac0266e3cc49ece19712b4302bf9845b9ccfa3 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Sat, 5 Apr 2025 15:12:27 +0100 Subject: [PATCH 4/5] Avoid the spelling list --- docs/web_advanced.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/web_advanced.rst b/docs/web_advanced.rst index 4d544649d83..a06777e3921 100644 --- a/docs/web_advanced.rst +++ b/docs/web_advanced.rst @@ -1184,7 +1184,7 @@ the middleware might use :meth:`BaseRequest.clone`. Deploying with a dynamic port ----------------------------- -When deploying aiohttp in a zeroconf environment, it may be useful +When deploying aiohttp with zero-configuration networking, it may be useful to have the server bind to a dynamic port. This can be done by using the ``0`` port number. This will cause the OS to assign a free port to the server. The assigned port can be retrieved From dd911b701e5f3ec172776e0d2db0a1840435ca2b Mon Sep 17 00:00:00 2001 From: Tom Whittock <136440158+twhittock-disguise@users.noreply.github.com> Date: Fri, 11 Apr 2025 15:04:51 +0100 Subject: [PATCH 5/5] Update change description Co-authored-by: J. Nick Koston --- CHANGES/10665.feature.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES/10665.feature.rst b/CHANGES/10665.feature.rst index 693317876f7..b40dec951e1 100644 --- a/CHANGES/10665.feature.rst +++ b/CHANGES/10665.feature.rst @@ -1 +1 @@ -Added `port` accessor for dynamic port allocations in `TCPSite` -- by :user:`twhittock-disguise`. +Added :py:attr:`~aiohttp.web.TCPSite.port` accessor for dynamic port allocations in :class:`~aiohttp.web.TCPSite` -- by :user:`twhittock-disguise`.