diff --git a/CHANGES/9309.bugfix.rst b/CHANGES/9309.bugfix.rst new file mode 100644 index 0000000000..73870da193 --- /dev/null +++ b/CHANGES/9309.bugfix.rst @@ -0,0 +1 @@ +Fixed assembling the :class:`~yarl.URL` for web requests when the host contains a non-default port or IPv6 address -- by :user:`bdraco`. diff --git a/aiohttp/web_request.py b/aiohttp/web_request.py index 91fc1f42bf..62a08ea248 100644 --- a/aiohttp/web_request.py +++ b/aiohttp/web_request.py @@ -431,6 +431,10 @@ def host(self) -> str: - overridden value by .clone(host=new_host) call. - HOST HTTP header - socket.getfqdn() value + + For example, 'example.com' or 'localhost:8080'. + + For historical reasons, the port number may be included. """ host = self._message.headers.get(hdrs.HOST) if host is not None: @@ -454,8 +458,10 @@ def remote(self) -> Optional[str]: @reify def url(self) -> URL: - url = URL.build(scheme=self.scheme, host=self.host) - return url.join(self._rel_url) + """The full URL of the request.""" + # authority is used here because it may include the port number + # and we want yarl to parse it correctly + return URL.build(scheme=self.scheme, authority=self.host).join(self._rel_url) @reify def path(self) -> str: diff --git a/tests/test_web_request.py b/tests/test_web_request.py index ba12d6f54e..9e613bb661 100644 --- a/tests/test_web_request.py +++ b/tests/test_web_request.py @@ -526,6 +526,16 @@ def test_url_url() -> None: assert URL("http://example.com/path") == req.url +def test_url_non_default_port() -> None: + req = make_mocked_request("GET", "/path", headers={"HOST": "example.com:8123"}) + assert req.url == URL("http://example.com:8123/path") + + +def test_url_ipv6() -> None: + req = make_mocked_request("GET", "/path", headers={"HOST": "[::1]:8123"}) + assert req.url == URL("http://[::1]:8123/path") + + def test_clone() -> None: req = make_mocked_request("GET", "/path") req2 = req.clone()